KEYENCE KV Studio Ver.9J を使った Android スマホ PLC I/Oチェッカー (2019/04/07)
KV COM+ は不要です。
KV Sudio の「一括モニタ」画面を 32 行以上表示させた状態で使用します。(「一括モニタ」の横幅は広げておいてください。)
モニタ、ビット反転対象は表示されている 32点 のみです。
■仕組み
・画面に表示中の一括モニタ画面をキャプチャし、特定のピクセル(下のスクリーンショットの赤い点)の色で、
ビットデバイスの ON/ OFF を判断しています。
・Andorid スマホとの通信は、Bluetooth 経由のシリアルポートを使っています.
あらかじめペアリング後、Bluetooth経由のシリアルポートの追加が必要です。
※先頭デバイスは取得していない(できない)ので、実際と合わない場合があります。
■Windows 側アプリ
キャプチャの尺度、範囲が合っているか、確認が必要です。
全体の尺度を 0.44444 ( 1/2.25) 程度にすると、通常の解像度でも使えると思います。
Andoroid 端末の操作を中継するだけなので、画面上に見えていなくても動きます。
■KV Studio 「一括モニタ」
この一部分をキャプチャしています。データ行は 32 行以上。横幅は広げておいてください。
■Android 側アプリ
初回起動時は、通信エラーになります。
一番上のコンボボックスから接続先のPC名を選択し、「保存」をタップし、終了して下さい。
通信ができない時は、終了し、Windows 側で [STOP] -> [START] し、再度起動してみて下さい。
・[+10K], [-10K] ... ボタンで先頭デバイスを変更します。デバイスの変更には、若干時間がかかります。
・グリッドのセルをタップすると、反転対象のデバイス番号が変わります。
■著作権・免責事項等
本ツールの著作権は、作者 f.izawa が所有し、これを主張します。
本ツールをインストール、使用したことによる事故、損害等の一切について、作者はその責を負いません。
■作者連絡先
e-mail : f.izawa@dream.com (@は小文字に変えて下さい)
URL : http://www.izawa-web.com/
■ダウンロード
・KVS_IO.zip (Winndows側アプリ EXE 本体のみ)
キャプチャの位置、尺度が合わない場合は、全体の尺度を 0.4445 程度に変えてみて下さい。
ペアリング、Bluetooth 経由の COM ポートの追加については、ネットで検索して下さい。
・KVS_IO.apk (Android 側アプリ APK 本体のみ)
// **************************** // Windows 側 // **************************** unit KV_IOUnit4; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Buttons, AdPacket, OoMisc, AdPort, AdSelCom, IniFiles; type TForm4 = class(TForm) Image1: TImage; Button3: TButton; Edit5: TEdit; Button4: TButton; Button5: TButton; GroupBox1: TGroupBox; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; Label7: TLabel; Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Edit4: TEdit; Edit8: TEdit; Edit9: TEdit; Edit10: TEdit; Edit11: TEdit; SpeedButton1: TSpeedButton; SpeedButton2: TSpeedButton; SpeedButton3: TSpeedButton; SpeedButton4: TSpeedButton; Edit7: TEdit; SpeedButton5: TSpeedButton; SpeedButton6: TSpeedButton; SpeedButton7: TSpeedButton; SpeedButton8: TSpeedButton; SpeedButton9: TSpeedButton; SpeedButton10: TSpeedButton; Timer1: TTimer; Button1: TButton; Edit12: TEdit; ApdComPort1: TApdComPort; ApdDataPacket1: TApdDataPacket; ComboBox1: TComboBox; procedure Button3Click(Sender: TObject); procedure Button4Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button5Click(Sender: TObject); procedure SpeedButton1Click(Sender: TObject); procedure SpeedButton2Click(Sender: TObject); procedure SpeedButton5Click(Sender: TObject); procedure SpeedButton7Click(Sender: TObject); procedure SpeedButton9Click(Sender: TObject); procedure SpeedButton10Click(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString); private { Private 宣言 } public { Public 宣言 } pickXOff : integer; pickXOff0 : integer; pickYOff : integer; captL : integer; captW : integer; captH : integer; shtScale : double; end; var Form4: TForm4; implementation {$R *.dfm} type TBitAry = array [0..31] of Boolean; TWordAry = array [0..1] of Word; var BitAryNew : TBitAry; BitAryOld : TBitAry; WordAryNew : TWordAry; WordAryOld : TWordAry; //****************************************** // n の k 乗 (Math ユニット不要) //****************************************** function IntPower(n, k : integer):integer; var i : integer; begin result := 1; for i := 1 to k do result := result * n; end; //****************************************** // ウィンドウのタイトル(キャプション)を得る //****************************************** function GetWindowCaption(h : HWND) : string; var Title : array [0..255] of char; begin result := ''; if GetWindowText(h, Title, 255) <> 0 then result := Title; end; //****************************************** // 他のプロセス内のコントロールの文字列を得る //****************************************** function GetWindowString(h : HWND) : string; var p : PChar; len : LongInt; begin result := ''; //ウィンドウの文字列のバイト数を取得 //終端のNULL文字を含まない文字列の長さ(バイト数) len := SendMessage(h, WM_GETTEXTLENGTH, 0, 0); if len > 0 then begin //終端のNULL文字を含むサイズを確保 GetMem(p, (len + 1) * 2); //格納するバッファの最大サイズ(終端のNULL文字を含む長さ) //文字列バッファ SendMessage(h, WM_GETTEXT, (len+1)*2, LongInt(p)); //文字列がバッファサイズより長いとき、後部がカットされる result := string(p); FreeMem(p); end; end; //****************************************** // クラス名取得 //****************************************** function GetHwndClassName(h : HWND):string; var PC : PChar; Len : Integer; Classname : string; begin ClassName := ''; if not IsWindow(h) then exit; GetMem(PC, 100); try Len := GetClassName(h, PC, 100); SetString(Classname, PC, Len); finally FreeMem(PC); end; result := Classname; end; //****************************************** // Window に文字列を送る //****************************************** function SendCharHwnd(h: HWND; const s: string):boolean; var i : integer; begin result := False; if h <> 0 then begin for i := 1 to Length(s) do SendMessage(h, WM_CHAR, Word(s[i]), 0); result := true; end; end; //****************************************** // Window に文字列を送る //****************************************** function SendTextHwnd(h: HWND; const s : string):boolean; begin result := False; if h <> 0 then begin SendMessage(h, WM_SETTEXT, 0, LPARAM(PChar(s))); result := true; end; end; //**************************************** // 画面の指定位置をBitmapに変換 //**************************************** procedure CaptureToBmp(Lf, Tp, W, H: Integer; bmp: TBitmap); const CAPTUREBLT = $40000000; var hdcScreen : HDC; begin bmp.Width := W; bmp.Height := H; hdcScreen := CreateDC('DISPLAY', nil, nil, nil); try BitBlt( bmp.Canvas.Handle, 0, 0, W, H, hdcScreen, Lf, Tp, SRCCOPY or CAPTUREBLT); finally DeleteDC(hdcScreen); end; end; //**************************************** // KEY を打つ //**************************************** procedure HwndSendKeys(h: HWND; const keystr: string); var i : integer; s: string; begin if GetForegroundWindow <> h then SetForegroundWindow(h); s := UpperCase(keystr); for i := 1 to Length(s) do begin keybd_event(Byte(s[i]), 0, 0, 0); keybd_event(Byte(s[i]), 0, KEYEVENTF_KEYUP, 0); Sleep(1); end; end; procedure TForm4.ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString); var cmd, res, s, s0, s1 : string; i, j, k0 : integer; begin cmd := Trim(string(Data)); if cmd = 'CPU' then begin ApdComPort1.PutString('KV' + #13#10); end else if cmd = 'READ' then begin s0 := ''; s1 := ''; for i := 0 to 1 do begin k0 := 0; for j := 0 to 15 do begin if BitAryNew[i * 16 + j] then k0 := k0 + IntPower(2, j); end; WordAryNew[i] := k0; s0 := s0 + IntToHex(k0, 4); s1 := s1 + IntToHex(WordAryOld[i], 4); end; res := Copy(Edit5.Text + ' ', 1, 8) ; // 先頭デバイス(8文字) res := res + s0 + s1; // 8 + 8 + 8 = 合計 24 文字 ApdComPort1.PutString(res + #13#10); WordAryOld := WordAryNew; end // ビット反転 else if Pos('BTRV', cmd) = 1 then begin s := Copy(cmd, 6); Edit11.Text := s; Button5Click(self); ApdComPort1.PutString('OK' + #13#10); end // 先頭デバイス変更 else if Pos('DEVN', cmd) = 1 then begin s := Copy(cmd, 6); Edit5.Text := s; Button3Click(self); ApdComPort1.PutString('OK' + #13#10); end else ApdComPort1.PutString('??' + #13#10); end; procedure TForm4.Button1Click(Sender: TObject); var s : string; begin if Button1.Caption = 'START' then begin Button1.Caption := 'STOP'; Button3Click(self); with ApdComPort1 do begin s := Copy(ComboBox1.Text, 4); s := Copy(s, 1, Length(s) -1); ComNumber := StrToIntDef(s, 4); Baud := 9600; StopBits := 1; DataBits := 8; Parity := TParity.pNone; SWFlowOptions := TSWFlowOptions.swfNone; end; with ApdDataPacket1 do begin Enabled := False; EndCond := [ecString]; EndString := #13#10; StartCond := scAnyData; TimeOut := 500; end; try ApdComPort1.Open := True; if ApdComPort1.Open then begin ApdDataPacket1.Enabled := True; ComboBox1.Enabled := False; end; except ShowMessage('ComPort Open Error'); end; Timer1.Enabled := True; end else begin Button1.Caption := 'START'; Timer1.Enabled := False; if ApdComPort1.Open then begin ApdComPort1.Open := False; ComboBox1.Enabled := True; end; end; end; procedure TForm4.Button3Click(Sender: TObject); // 先頭デバイス変更 var h1, h2, h3, h4, h : HWND; ARect : TRect; bmp : TBitmap; x, y : integer; pt0 : TPoint; pt : TPoint; tmFlag : boolean; i: Integer; begin tmFlag := Timer1.Enabled; if tmFlag then Timer1.Enabled := False; GetCursorPos(pt0); h1 := FindWindow(nil, '一括モニタ'); //#32770 h2 := GetWindow(h1, GW_CHILD); h3 := GetWindow(h2, GW_HWNDNEXT); h4 := GetWindow(h3, GW_CHILD); if (h4 <> 0) and IsWindowVisible(h4) then begin GetWindowRect(h4, ARect); pt.X := ARect.Left + 1; pt.Y := ARect.Top + 1; h := WindowFromPoint(pt); if h <> h4 then begin SetForegroundWindow(h1); Sleep(100); end; x := ARect.Left + Trunc((CaptL + 100) * shtScale); y := ARect.Top + Trunc((captH / 33 + pickYOff) * shtScale); SetCursorPos(x, y); mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); Sleep(200); keybd_event(VK_RETURN, 0, 0, 0); keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0); // 先頭デバイス番号を送る HwndSendKeys(h4, Edit5.Text + #13); // 反転対象のデバイスを変更 Edit11.Text := Edit5.Text; // 初期化 Edit7.Text := ''; Edit12.Text := ''; for i := 0 to 31 do BitAryNew[i] := False; BitAryOld := BitAryNew; SetCursorPos(pt0.X, pt0.Y); Sleep(500); bmp := TBitmap.Create; try bmp.PixelFormat := pf24bit; CaptureToBmp(ARect.Left + Trunc(captL * shtScale), ARect.Top, Trunc(captW * shtScale), Trunc(captH * shtScale), bmp); Image1.Picture.Bitmap.Assign(bmp); finally bmp.Free; end; end; if tmFlag then Timer1.Enabled := True; end; procedure TForm4.Button4Click(Sender: TObject); // 現在値取得 // 先頭デバイス番号は取得できない var h1, h2, h3, h4, h : HWND; ARect : TRect; bmp : TBitmap; i : integer; Pnt : PByteArray; R, G, B, R0, G0, B0 : Byte; x, y, x0 : integer; s : string; pt : TPoint; divH : double; head, dv, md, dv2, md2 : integer; begin divH := captH / 33; h1 := FindWindow(nil, '一括モニタ'); //#32770 h2 := GetWindow(h1, GW_CHILD); h3 := GetWindow(h2, GW_HWNDNEXT); h4 := GetWindow(h3, GW_CHILD); if (h4 <> 0) and IsWindowVisible(h4) then begin GetWindowRect(h4, ARect); bmp := TBitmap.Create; try bmp.PixelFormat := pf24bit;//24bit; pt.X := ARect.Left + 1; pt.Y := ARect.Top + 1; h := WindowFromPoint(pt); if h <> h4 then begin SetForegroundWindow(h1); Sleep(100); end; CaptureToBmp(ARect.Left + Trunc(captL * shtScale), ARect.Top, Trunc(captW * shtScale), Trunc(captH * shtScale), bmp); Image1.Picture.Bitmap.Assign(bmp); finally bmp.Free; end; with Image1.Picture.Bitmap do begin Canvas.Pen.Color := clRed; Canvas.Brush.Color := clRed; Canvas.Brush.Style := bsSolid; x := Trunc(pickXOff * shtScale); x0 := Trunc(pickXOff0 * shtScale); for i := 0 to 31 do begin y := Trunc((divH * (i + 1) + pickYOff) * shtScale); if y < Height then begin Pnt := ScanLine[y]; R := Pnt[x * 3 + 2]; G := Pnt[x * 3 + 1]; B := Pnt[x * 3]; R0 := Pnt[x0 * 3 + 2]; G0 := Pnt[x0 * 3 + 1]; B0 := Pnt[x0 * 3]; BitAryNew[i] := ((R = 0) and (G = 0) and (B = 0) and (R0 = $FF) and (G0 = $FF) and (B0 = $FF)) or // 青色= $0078D7 ((R = $FF) and (G = $FF) and (B = $FF) and (R0 = 0) and (G0 <> 0) and (B0 <> 0)); Canvas.Ellipse(x - 2, y - 2, x + 2, y + 2); Canvas.Ellipse(x0 - 2, y - 2, x0 + 2, y + 2); end; end; // デバイス先頭 head := StrToIntDef(Copy(Edit5.Text, 2), 0); dv := head div 100; md := head mod 100; // 比較 for i := 0 to 31 do begin if BitAryNew[i] <> BitAryOld[i] then begin dv2 := dv + i div 16; md2 := md + i mod 16; if md2 >= 16 then begin dv2 := dv2 + md2 div 16; md2 := md2 mod 16; end; s := Copy(Edit5.Text, 1, 1) + Format('%.3d', [dv2 * 100 + md2]); Edit7.Text := s; if BitAryNew[i] then Edit12.Text := 'ON' else Edit12.Text := 'OFF'; end; end; BitAryOld := BitAryNew; end; end; end; procedure TForm4.Button5Click(Sender: TObject); // 値変更 var h1, h2, h3, h4, h : integer; ARect : TRect; x, y : integer; idx : integer; pt0 : TPoint; pt : TPoint; n, m : integer; tmFlag : boolean; begin tmFlag := Timer1.Enabled; if tmFlag then Timer1.Enabled := False; // カーソル位置を取得 GetCursorPos(pt0); // 先頭デバイス m := StrToIntDef(Copy(Edit5.Text, 2), 0); // 反転デバイス n := StrToIntDef(Copy(Edit11.Text ,2), 0); // デバイス番号(相対) idx := ((n - m) div 100) * 16 + (n - m) mod 100; // 対象のウィンドウを探す h1 := FindWindow(nil, '一括モニタ'); //#32770 h2 := GetWindow(h1, GW_CHILD); h3 := GetWindow(h2, GW_HWNDNEXT); h4 := GetWindow(h3, GW_CHILD); if (h4 <> 0) and IsWindowVisible(h4) then begin // ウィンドウの位置、矩形範囲を取得 GetWindowRect(h4, ARect); pt.X := ARect.Left + 1; pt.Y := ARect.Top + 1; h := WindowFromPoint(pt); if h <> h4 then begin SetForegroundWindow(h1); Sleep(100); end; // カーソル位置を移動 x := ARect.Left + Trunc((captL + pickXOff) * shtScale); y := ARect.Top + Trunc(((captH / 33) * (idx + 1) + pickYOff) * shtScale); SetCursorPos(x, y); // セルをアクティブに mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); Sleep(100); // スペースキーを押す keybd_event(VK_SPACE, 0, 0, 0); keybd_event(VK_SPACE, 0, KEYEVENTF_KEYUP, 0); Sleep(100); { // 次の処理(現在値取得)のためにアクティブセルをデバイスの先頭に変える x := ARect.Left + Trunc((captL + pickXOff) * shtScale); y := ARect.Top + Trunc((captH / 33 + pickYOff) * shtScale); SetCursorPos(x, y); // セルをアクティブに mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0); mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0); Sleep(100); } // カーソル位置を戻す SetCursorPos(pt0.X, pt0.Y); Sleep(500); end; if tmFlag then Timer1.Enabled := True; end; procedure TForm4.FormCreate(Sender: TObject); var i : integer; ini : TIniFile; begin shtScale := 1.0; // Pixcel 取得のマス目左上基点からのオフセット pickXOff := 454; pickXOff0 := 220; pickYOff := 10; // ウィンドウ基点から「デバイス」マスの左位置 (上位置=0) captL := 270; // キャプチャ範囲の幅 captW := 475; // キャプチャ範囲の高さ captH := 1188; // 使用可能な COM ポートを列挙 AdSelCom.ShowPortsInUse := False; for i := 0 to 32 do begin if AdSelCom.IsPortAvailable(i) then ComboBox1.Items.Add (AdPort.ComName(i) + '.'); end; ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')); try Left := ini.ReadInteger('Form', 'Left', (Screen.Width - Width) div 2); Top := ini.ReadInteger('Form', 'Top' , (Screen.Height - Height) div 2); i := ini.ReadInteger('COM', 'PortIndex', 0); if ComboBox1.Items.Count > i then ComboBox1.ItemIndex := i; shtScale := ini.ReadFloat('Capt', 'Scale', 1); captW := ini.ReadInteger('Capt', 'captW', captW); captH := ini.ReadInteger('Capt', 'captH', captH); captL := ini.ReadInteger('Capt', 'captL', captL); pickXOff := ini.ReadInteger('Capt', 'pickXOff', pickXOff); pickXOff0 := ini.ReadInteger('Capt', 'pickXOff0', pickXOff0); pickYOff := ini.ReadInteger('Capt', 'pickYOff', pickYOff); finally ini.Free; end; ApdDataPacket1.Enabled := False; Edit1.Text := Format('%.6f', [ shtScale]); Edit2.Text := IntToStr(captW); Edit3.Text := IntToStr(captH); Edit4.Text := IntToStr(captL); Edit8.Text := IntToStr(pickXOff0); Edit9.Text := IntToStr(pickXOff); Edit10.Text := IntToStr(pickYOff); end; procedure TForm4.FormDestroy(Sender: TObject); var ini: TIniFile; begin if ApdComPort1.Open then ApdComPort1.Open := False; ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini')); try ini.WriteInteger('Form', 'Left', Left); ini.WriteInteger('Form', 'Top' , Top); ini.WriteInteger('COM', 'PortIndex', ComboBox1.ItemIndex); ini.WriteFloat('Capt', 'Scale', shtScale); ini.WriteInteger('Capt', 'captW', captW); ini.WriteInteger('Capt', 'captH', captH); ini.WriteInteger('Capt', 'captL', captL); ini.WriteInteger('Capt', 'pickXOff', pickXOff); ini.WriteInteger('Capt', 'pickXOff0', pickXOff0); ini.WriteInteger('Capt', 'pickYOff', pickYOff); finally ini.Free; end; end; procedure TForm4.SpeedButton10Click(Sender: TObject); // 規定値に戻す begin shtScale := 1.0; captW := 475; captH := 1188; captL := 270; pickXOff0 := 220; pickXOff := 454; pickYOff := 10; Edit1.Text := Format('%.6f', [ shtScale]); Edit2.Text := IntToStr(captW); Edit3.Text := IntToStr(captH); Edit4.Text := IntToStr(captL); Edit8.Text := IntToStr(pickXOff0); Edit9.Text := IntToStr(pickXOff); Edit10.Text := IntToStr(pickYOff); end; procedure TForm4.SpeedButton1Click(Sender: TObject); // [+10], [+1] var idx0, idx1 : integer; dv0, dv1, md1 : integer; begin idx0 := StrToIntDef(Copy(Edit5.Text, 2), 0); dv0 := idx0 div 100; idx1 := StrToIntDef(Copy(Edit11.Text, 2), 0); if idx1 - idx0 < 200 then begin dv1 := idx1 div 100; md1 := idx1 mod 100; if Sender as TSpeedButton = SpeedButton1 then begin Inc(md1); if md1 >= 16 then begin Inc(dv1); md1 := 0; end; end else begin Inc(dv1); end; if (dv1 - dv0 < 2) and (md1 < 16) then Edit11.Text := Copy(Edit5.Text, 1, 1) + Format('%.3d', [dv1 * 100 + md1]); end; end; procedure TForm4.SpeedButton2Click(Sender: TObject); // [-10], [-1] var idx0, idx1 : integer; dv0, dv1, md1 : integer; begin idx0 := StrToIntDef(Copy(Edit5.Text, 2), 0); dv0 := idx0 div 100; idx1 := StrToIntDef(Copy(Edit11.Text, 2), 0); if idx1 - idx0 < 200 then begin dv1 := idx1 div 100; md1 := idx1 mod 100; if Sender as TSpeedButton = SpeedButton2 then begin Dec(md1); if md1 < 0 then begin Dec(dv1); md1 := 15; end; end else begin Dec(dv1); end; if (dv1 >= dv0) and (md1 >= 0) then Edit11.Text := Copy(Edit5.Text, 1, 1) + Format('%.3d', [dv1 * 100 + md1]); end; end; procedure TForm4.SpeedButton5Click(Sender: TObject); // 先頭デバイス +100, +1000 var idx0 : integer; dv0, md0 : integer; begin idx0 := StrToIntDef(Copy(Edit5.Text, 2), 0); dv0 := idx0 div 100; md0 := idx0 mod 100; if Sender as TSpeedButton = SpeedButton5 then Inc(dv0) else dv0 := dv0 + 10; Edit5.Text := Copy(Edit5.Text, 1, 1) + Format('%.3d', [dv0 * 100 + md0]); // 先頭デバイス変更 Button3Click(self); end; procedure TForm4.SpeedButton7Click(Sender: TObject); // 先頭デバイス -100, -1000 var idx0 : integer; dv0, md0 : integer; begin idx0 := StrToIntDef(Copy(Edit5.Text, 2), 0); dv0 := idx0 div 100; md0 := idx0 mod 100; if Sender as TSpeedButton = SpeedButton7 then Dec(dv0) else dv0 := dv0 - 10; if (dv0 >= 0) and (md0 >= 0) then begin Edit5.Text := Copy(Edit5.Text, 1, 1) + Format('%.3d', [dv0 * 100 + md0]); // 先頭デバイス変更 Button3Click(self); end; end; procedure TForm4.SpeedButton9Click(Sender: TObject); // 設定を更新 begin shtScale := StrToFloatDef(Edit1.Text ,1.0); Edit1.Text := Format('%.6f', [shtScale]); captW := StrToIntDef(Edit2.Text, 475); captH := StrToIntDef(Edit3.Text, 1188 ); captL := StrToIntDef(Edit4.Text, 270); pickXOff0 := StrToIntDef(Edit8.Text, 220); pickXOff := StrToIntDef(Edit9.Text, 454); pickYOff := StrToIntDef(Edit10.Text, 10); end; procedure TForm4.Timer1Timer(Sender: TObject); begin Button4Click(self); end; end. // **************************** // Android 側 // **************************** unit Unit4; interface uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, System.Bluetooth, System.Bluetooth.Components, FMX.ScrollBox, FMX.Memo, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Layouts, FMX.Edit, System.Rtti, FMX.Grid.Style, FMX.Grid,{ Math,} FMX.Objects, System.UIConsts, FMX.ListBox, System.IOUtils, System.IniFiles, // for TTS Androidapi.JNI.TTS,AndroidAPI.JNIBridge, // for ToneGenerator AndroidApi.JNI.Media; type TBitAry = array [0..31] of Boolean; type TBtThread = class(TThread) private { Private 宣言 } procedure BtOpen; protected procedure Execute; override; public constructor Create; virtual; end; type TForm4 = class(TForm) ScaledLayout1: TScaledLayout; Bluetooth1: TBluetooth; Button6: TButton; Timer1: TTimer; StringGrid1: TStringGrid; StringColumn1: TStringColumn; StringColumn2: TStringColumn; StringColumn3: TStringColumn; StringColumn4: TStringColumn; StringColumn5: TStringColumn; StringColumn6: TStringColumn; StringColumn7: TStringColumn; StringColumn8: TStringColumn; StringColumn9: TStringColumn; Label1: TLabel; Label2: TLabel; Label3: TLabel; Rectangle1: TRectangle; Label4: TLabel; Label5: TLabel; Rectangle2: TRectangle; Rectangle3: TRectangle; Rectangle4: TRectangle; Rectangle5: TRectangle; Label7: TLabel; StringColumn10: TStringColumn; StringColumn11: TStringColumn; StringColumn12: TStringColumn; StringColumn13: TStringColumn; StringColumn14: TStringColumn; StringColumn15: TStringColumn; StringColumn16: TStringColumn; StringColumn17: TStringColumn; ComboBox3: TComboBox; Button2: TButton; Switch1: TSwitch; Rectangle6: TRectangle; Label8: TLabel; Rectangle7: TRectangle; Label9: TLabel; Rectangle8: TRectangle; Label6: TLabel; Rectangle10: TRectangle; Label10: TLabel; Rectangle11: TRectangle; Label12: TLabel; Label13: TLabel; Rectangle9: TRectangle; Label11: TLabel; Rectangle12: TRectangle; Label14: TLabel; procedure Button6Click(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Rectangle1Click(Sender: TObject); procedure Rectangle2Click(Sender: TObject); procedure StringGrid1DrawColumnCell(Sender: TObject; const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF; const Row: Integer; const Value: TValue; const State: TGridDrawStates); procedure StringGrid1DrawColumnHeader(Sender: TObject; const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF); procedure StringGrid1CellClick(const Column: TColumn; const Row: Integer); procedure FormDestroy(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Rectangle7Click(Sender: TObject); procedure Rectangle10Click(Sender: TObject); // TTS type TttsOnInitListener = class(TJavaLocal, JTextToSpeech_OnInitListener) private [weak] FParent : TForm4; public constructor Create(AParent : TForm4); procedure onInit(status: Integer); cdecl; end; private { private 宣言 } ttsListener : TttsOnInitListener; tts : JTextToSpeech; procedure SpeakOut(const s :string); procedure InitTTS; public { public 宣言 } BitAryOld : TBitAry; BitAryNew : TBitAry; constructor Create(AOwner : TComponent); override; destructor Destroy; override; procedure SendDeviceStartIndex; end; var Form4: TForm4; ADevice : TBluetoothDevice; ASocket : TBluetoothSocket; GThdMode : integer; GCmdMode : integer; ThBt : TBtThread; OpenNGcnt : integer; OpenMsecCnt : integer; Counter : integer; BtDeviceHead : string; // uses ... Androidapi.JNIBridge, AndroidApi.JNI.Media; ToneGenerator: JToneGenerator; const // SPP(Serial Port Profile) による通信のUUID ServiceUUID = '{00001101-0000-1000-8000-00805F9B34FB}'; thdTHSTART = 1000; thdTHTERM = 2000; cmdSCCREATE = 200; cmdSCCONNECT = 201; cmdSCNG = 202; implementation uses Androidapi.JNI.JavaTypes, FMX.Helpers.Android {$IF CompilerVersion >= 27.0} , Androidapi.Helpers {$ENDIF} ; {$R *.fmx} // n の k 乗 (Math ユニット不要) function IntPower(n, k : integer):integer; var i : integer; begin result := 1; for i := 1 to k do result := result * n; end; // ----------------------------------------------------------------------------- // Bluetooth を Open し、接続する procedure TBtThread.BtOpen; var ABluetoothManager : TBluetoothManager; APairedDevices : TBluetoothDeviceList; ADevice : TBluetoothDevice; idx, i : integer; begin GThdMODE := thdTHSTART; try try ABluetoothManager := TBluetoothManager.Current; if ABluetoothManager.ConnectionState = TBluetoothConnectionState.Connected then begin // 過去にペアリングされたデバイスの一覧から、ターゲット を探す APairedDevices := ABluetoothManager.GetPairedDevices; if APairedDevices.Count > 0 then begin idx := -1; for i := 0 to APairedDevices.Count -1 do begin Synchronize(procedure() begin with Form4.ComboBox3 do begin BeginUpdate; Items.Add(APairedDevices[i].DeviceName ); EndUpdate; end; end); if (BTDeviceHead = APairedDevices[i].DeviceName) then begin Synchronize(procedure() begin with Form4.ComboBox3 do begin ItemIndex := i; end; end); idx := i; //break; // リストアップを続ける end; end; if idx >= 0 then begin ADevice := APairedDevices[idx]; if ADevice <> nil then begin ASocket := ADevice.CreateClientSocket(StringToGUID(ServiceUUID), False); if ASocket <> nil then begin GCMDMODE := cmdSCCREATE; // 接続 ASocket.Connect; if ASocket.Connected then GCMDMODE := cmdSCCONNECT; end; end; end; end; end; except on E : Exception do begin GCMDMODE := cmdSCNG; end; end; finally // 明示的にスレッドを終了(破棄される) // スレッド実行中にアプリを終了した時エラーになるため Terminate; WaitFor; FreeAndNil(ThBt); GThdMODE := thdTHTERM; end; end; constructor TBtThread.Create; begin // スレッドを生成、直ちに実行 inherited Create(False); // スレッド終了時、スレッドオブジェクトを破棄 FreeOnTerminate := True; end; procedure TBtThread.Execute; begin BtOpen; end; // ----------------------------------------------------------------------------- procedure TForm4.InitTTS; begin tts := TJTextToSpeech.JavaClass.init(TAndroidHelper.Context, ttsListener); end; procedure TForm4.SpeakOut(const s : string); var text : JString; begin text := StringToJString(s); tts.speak(text, TJTextToSpeech.JavaClass.QUEUE_FLUSH, nil); end; { TForm4.TttsOnInitListener } constructor TForm4.TttsOnInitListener.Create(AParent: TForm4); begin inherited Create; FParent := AParent end; procedure TForm4.TttsOnInitListener.onInit(status: Integer); var Result : Integer; begin if (status = TJTextToSpeech.JavaClass.SUCCESS) then begin //result := FParent.tts.setLanguage(TJLocale.JavaClass.US); result := FParent.tts.setLanguage(TJLocale.JavaClass.JAPAN); if (result = TJTextToSpeech.JavaClass.LANG_MISSING_DATA) or (result = TJTextToSpeech.JavaClass.LANG_NOT_SUPPORTED) then ShowMessage('This Language is not supported'); end else ShowMessage('Initilization Failed!'); end; constructor TForm4.Create(AOwner: TComponent); begin inherited; ttsListener := TttsOnInitListener.Create(self); end; destructor TForm4.Destroy; begin if Assigned(tts) then begin tts.stop; tts.shutdown; tts := nil; end; end; // ----------------------------------------------------------------------------- function ASocketReceiveData(ASocket: TBluetoothSocket; ATimeout: Cardinal): string; var AData : TBytes; ReadData : TBytes; i : integer; res : string; Ticks : Cardinal; idx : integer; loop : boolean; cnt : integer; begin res := ''; cnt := 0; SetLength(ReadData, 1024); idx := 0; Ticks := TThread.GetTickCount; loop := True; while loop and (cnt < 500) do begin Sleep(1); AData := ASocket.ReceiveData; if Length(AData) > 0 then begin for i := 0 to Length(AData) - 1 do begin ReadData[idx] := AData[i]; Inc(idx); if (AData[i] = $0A) or (idx >= 1024) then begin loop := False; break; end; end; end; Inc(cnt); if loop then loop := TThread.GetTickCount - Ticks < ATimeout; end; SetLength(ReadData, idx); res := TEncoding.ANSI.GetString(ReadData); result := Trim(res); // 制御コードを含まない end; procedure TForm4.SendDeviceStartIndex; // PC へ先頭番号を送信 var AData : TBytes; res : string; ATimeout: Cardinal; i : integer; begin // PC 側へ先頭アドレスを送信するだけ if (ASocket <> nil) and ASocket.Connected then begin // 初期化 for i := 0 to 31 do BitAryNew[i] := False; BitAryOld := BitAryNew; // PC の値を変更 ATimeout := 250; // デバイス名 AData := TEncoding.ANSI.GetBytes('DEVN ' + Label8.Text + #13#10); // 送信 ASocket.SendData(AData); res := ASocketReceiveData(ASocket, ATimeout); // アドレス表示部 Rectangle4.Fill.Color := TAlphaColorRec.Black; // ON/OFF表示部 Rectangle5.Fill.Color := TAlphaColorRec.Black; end; // 反転デバイスの初期値 Label3.Text := Label8.Text; end; procedure TForm4.Button2Click(Sender: TObject); // 接続先保存 var IniFile: TMemIniFile; begin IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine( System.IOUtils.TPath.GetDocumentsPath, 'KVS_IO.ini'), TEncoding.UTF8); with IniFile do begin try with ComboBox3 do begin if ItemIndex >= 0 then begin WriteString('Target', 'PCName', Items[ItemIndex]); ShowMessage('接続先: ' + Items[ItemIndex] + 'を保存しました.' + #13#10 + '次回起動時から有効になります.' + #13#10 + 'このアプリを再起動して下さい.'); end else ShowMessage('接続先が選択されていません.'); end; IniFile.UpdateFile; finally Free; end; end; end; procedure TForm4.Button6Click(Sender: TObject); // デバイスの値をセット var AData : TBytes; res : string; ATimeout: Cardinal; begin if (ASocket <> nil) and ASocket.Connected then begin Timer1.Enabled := False; ATimeout := 250; AData := TEncoding.ANSI.GetBytes('BTRV ' + Label3.Text + #13#10); // 送信 ASocket.SendData(AData); // 受信 res := ASocketReceiveData(ASocket, ATimeout); with Label3.TextSettings do begin if res = 'ON' then FontColor := TAlphaColorRec.Red else if res = 'OFF' then FontColor := TAlphaColorRec.Lime else FontColor := TAlphaColorRec.White; end; if Switch1.IsChecked then begin // ブザー if (res = 'ON') or (res = 'OFF') or (res = 'OK') then ToneGenerator.startTone(TJToneGenerator.JavaClass.TONE_PROP_ACK) else ToneGenerator.startTone(TJToneGenerator.JavaClass.TONE_PROP_NACK); end; Timer1.Enabled := True; end; end; procedure TForm4.FormCreate(Sender: TObject); var IniFile: TMemIniFile; // uses .... System.IniFiles; i : integer; begin Label7.Text := ''; Label13.Text := ''; StringColumn1.Header := 'R'; StringColumn2.Header := '0'; StringColumn3.Header := '1'; StringColumn4.Header := '2'; StringColumn5.Header := '3'; StringColumn6.Header := '4'; StringColumn7.Header := '5'; StringColumn8.Header := '6'; StringColumn9.Header := '7'; StringColumn10.Header := '8'; StringColumn11.Header := '9'; StringColumn12.Header := '10'; StringColumn13.Header := '11'; StringColumn14.Header := '12'; StringColumn15.Header := '13'; StringColumn16.Header := '14'; StringColumn17.Header := '15'; with StringGrid1 do begin for i := 0 to 1 do Cells[0, i] := Format('%.3d', [i*100]); end; // 縦画面に固定 Application.FormFactor.Orientations := [TFormOrientation.Portrait, TFormOrientation.InvertedPortrait]; // use ..... System.IOUtils; IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine( System.IOUtils.TPath.GetDocumentsPath, 'KVS_IO.ini'), TEncoding.UTF8); with IniFile do begin try BtDeviceHead := ReadString('Target', 'PCName', ''); finally Free; end; end; // TTS InitTTS; // ブザー ToneGenerator := TJToneGenerator.JavaClass.init( TJAudioManager.JavaClass.STREAM_ALARM, TJToneGenerator.JavaClass.MAX_VOLUME); // Bruetooth スレッド Timer1.Interval := 10; Timer1.Enabled := True; ThBt := TBtThread.Create; end; procedure TForm4.FormDestroy(Sender: TObject); begin if ASocket <> nil then begin ASocket.Close; ASocket.Free; ASocket := nil; end; end; procedure TForm4.Rectangle10Click(Sender: TObject); // [-10000, -1000] var idx, i: integer; begin idx := StrToIntDef(Copy(Label8.Text, 2), 0); if Sender as TRectangle = Rectangle10 then idx := idx - 1000 else if Sender as TRectangle = Rectangle11 then idx := idx - 100 else // 12 idx := idx - 10000; if idx < 0 then idx := 0; Label8.Text := Copy(Label8.Text, 1, 1) + Format('%.3d', [idx]); with StringGrid1 do begin for i := 0 to 1 do Cells[0, i]:= Format('%.3d', [idx + i * 100]); Row := 0; Col := 1; end; // 先頭アドレスを PC に送信 SendDeviceStartIndex; end; procedure TForm4.Rectangle1Click(Sender: TObject); // [ + 1] var n, md, dv, idx : integer; begin n := StrToIntDef(Copy(Label3.Text, 2), 0); dv := n div 100; md := n mod 100; if md >= 15 then begin Inc(dv); md := 0; end else Inc(md); if dv * 100 + md <= 59915 then begin with Label3 do begin Text := Copy(Text, 1,1) + Format('%.3d', [dv * 100 + md]); TextSettings.FontColor := TAlphaColorRec.Orange; end; idx := StrToIntDef(Copy(Label8.Text, 2), 0); n := dv * 100 + md - idx; if n >= 0 then begin with StringGrid1 do begin OnCellClick := nil; Row := n div 100; Col := n mod 100 + 1; OnCellClick := StringGrid1CellClick; SetFocus; end; end; end; end; procedure TForm4.Rectangle2Click(Sender: TObject); // [ - ] var n, md, dv, idx : integer; begin n := StrToIntDef(Copy(Label3.Text, 2), 0); dv := n div 100; md := n mod 100; if md > 0 then Dec(md) else begin Dec(dv); md := 15; end; if dv < 0 then begin dv := 0; md := 0; end; with Label3 do begin Text := Copy(Text, 1, 1) + Format('%.3d', [dv * 100 + md]); TextSettings.FontColor := TAlphaColorRec.Orange; end; idx := StrToIntDef(Copy(Label8.Text, 2), 0); n := dv * 100 + md - idx; if n >= 0 then begin with StringGrid1 do begin OnCellClick := nil; Row := n div 100; Col := n mod 100 + 1; OnCellClick := StringGrid1CellClick; SetFocus; end; end; end; procedure TForm4.Rectangle7Click(Sender: TObject); //[+10000, +1000] var idx: integer; i: Integer; begin idx := StrToIntDef(Copy(Label8.Text, 2), 0); if Sender as TRectangle = Rectangle7 then idx := idx + 100 else if Sender as TRectangle = Rectangle8 then idx := idx + 1000 else //9 idx := idx + 10000; if idx > 59000 then idx := 59000; Label8.Text := Copy(Label8.Text, 1, 1) + Format('%.3d', [idx]); with StringGrid1 do begin for i := 0 to 1 do Cells[0, i]:= Format('%.3d', [idx + i * 100]); Row := 0; Col := 1; end; Label1.Text := ''; Label2.Text := ''; // 先頭アドレスを PC に送信 SendDeviceStartIndex; end; procedure TForm4.StringGrid1CellClick(const Column: TColumn; const Row: Integer); // セルクリックで、反転対象のアドレスを変更 var n : integer; begin // 出力反転の対象 n := StrToIntDef(StringGrid1.Cells[0, Row], 0) + StrToIntDef(Column.Header, 0); with Label3 do begin Text := Copy(Text, 1, 1) + Format('%.3d', [n]); TextSettings.FontColor := TAlphaColorRec.Orange; end; end; procedure TForm4.StringGrid1DrawColumnCell(Sender: TObject; const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF; const Row: Integer; const Value: TValue; const State: TGridDrawStates); // AlphaColor uses ... System.UIConsts; var s : string; n : integer; flag : boolean; idx : integer; begin if not Value.IsEmpty then s := Value.ToString else s := ''; with Canvas do begin if Column.Index = 0 then begin if s <> '' then begin Fill.Color := claSilver;//claAqua;//claSilver;//Yellow; FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Fill.Color := claBlack; Font.Size := 15; FillText(Bounds, s, False, 1.0, [], TTextAlign.Center); end; end else begin flag := False; if (Label2.Text = 'OFF') or (Label2.Text = 'ON') then begin // 先頭アドレス idx := StrToIntDef(Copy(Label8.Text, 2), 0); // 現在のアドレス n := StrToIntDef(Copy(Label1.Text,2), -1); if (n >= idx) then begin n := n - idx; if (Row = n div 100) and (Column.Index = n mod 100 + 1) then begin if Label2.Text = 'OFF' then begin Fill.Color := claGray;//Black; FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Fill.Color := claLime; end; if Label2.Text = 'ON' then begin Fill.Color := claRed; FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Fill.Color := claWhite; end; s := (n mod 100).ToString; Font.Size := 16; FillText(Bounds, s, False, 1.0, [], TTextAlign.Center); flag := true; end; end; end; if not flag and (s <> '') then begin Fill.Color := claOrange;//Red; FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Fill.Color := claWhite; Font.Size := 16; FillText(Bounds, s, False, 1.0, [], TTextAlign.Center); end; end; end; end; procedure TForm4.StringGrid1DrawColumnHeader(Sender: TObject; const Canvas: TCanvas; const Column: TColumn; const Bounds: TRectF); var s: string; begin s := Column.Header; if s <> '' then begin with Canvas do begin if Column.Index = 0 then begin Fill.Color := claLime; FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Fill.Color := claBlack; Font.Size := 18; FillText(Bounds, s, False, 1.0, [], TTextAlign.Center); end else begin Fill.Color := claSilver; FillRect(Bounds, 0, 0, AllCorners, 1, TCornerType.Round ); Fill.Color := claBlack; Font.Size := 15; FillText(Bounds, s, False, 1.0, [], TTextAlign.Center); end; end; end; end; function NumToSpeechText(const hex : string): string; var i : integer; s : string; begin result := ''; for i := 1 to hex.Length do begin s := Copy(hex, i, 1); if s = '0' then result := result + 'ゼロ' else if s = '1' then result := result + 'イチ' else if s = '2' then result := result + 'ニイ' else if s = '3' then result := result + 'サン' else if s = '4' then result := result + 'ヨン' else if s = '5' then result := result + 'ゴー' else if s = '6' then result := result + 'ロク' else if s = '7' then result := result + 'ナナ' else if s = '8' then result := result + 'ハチ' else if s = '9' then result := result + 'キュウ' else if s = 'A' then result := result + 'エイ' else if s = 'B' then result := result + 'ビイ' else if s = 'C' then result := result + 'シイ' else if s = 'D' then result := result + 'デー' else if s = 'E' then result := result + 'イイ' else if s = 'F' then result := result + 'エフ' else result := result + s; result := result + ' '; end; end; procedure TForm4.Timer1Timer(Sender: TObject); var ATimeout : Cardinal; AData : TBytes; res : string; i : integer; Ticks : Cardinal; j : integer; s : string; n, idx, stIndex : integer; flag : boolean; ttsFlag : boolean; begin ttsFlag := False; if not ((GCMDMODE = cmdSCCONNECT) and ASocket.Connected) then begin Inc(OpenMsecCnt); Label7.Text := IntToStr(OpenMsecCnt * 10) + 'msec'; if GCMDMODE = cmdSCNG then begin Inc(OpenNgCnt); if OpenNgCnt > 4 then begin Timer1.Enabled := False; ShowMessage(BTDeviceHead + ' に、接続できません.'); end; end; if OpenMsecCnt > 100 then begin Timer1.Enabled := False; ShowMessage('接続先が無効です.'); end; end; if (GCMDMODE = cmdSCCONNECT) and ASocket.Connected then begin Timer1.Interval := 250; flag := True; Timer1.Enabled := False; try Ticks := TThread.GetTickCount; ATimeout := 250; // 初回は CPU TYPE 取得のみ if Label13.Text = '' then begin AData := TEncoding.ANSI.GetBytes('CPU' + #13#10); // 送信 ASocket.SendData(AData); // 受信 res := ASocketReceiveData(ASocket, ATimeout); Label13.Text := res; flag := res <> ''; end else begin // 先頭アドレス stIndex := StrToIntDef(Copy(Label8.Text, 2), 0); if Flag then begin // デバイス一括読み出しコマンド AData := TEncoding.ANSI.GetBytes('READ' + #13#10); // 送信 ASocket.SendData(AData); // 受信 res := ASocketReceiveData(ASocket, ATimeout); flag := res <> ''; // データ格納 if res.Length >= 24 then begin for i := 0 to 1 do begin s := Copy(res, i * 4 + 9, 4); n := StrToIntDef('$' + s, 0); for j := 0 to 15 do BitAryNew[i * 16 + j] := n and IntPower(2, j) > 0; s := Copy(res, i * 4 + 9 + 8, 4); n := StrToIntDef('$' + s, 0); for j := 0 to 15 do BitAryOld[i * 16 + j] := n and IntPower(2, j) > 0; end; s := Trim(Copy(res, 1, 8)); // // 先頭デバイス番号 idx := StrToIntDef(Copy(s, 2), 0); if (stIndex <> idx) then begin stIndex := idx; // アドレス番号を変える Label8.Text := Copy(Label8.Text, 1, 1) + Format('%.3d', [stIndex]); with StringGrid1 do begin for i := 0 to 1 do Cells[0, i] := Format('%.3d', [stIndex + i * 100]); Row := 0; Col := 1; end; // 内部データを初期化 for i := 0 to 31 do BitAryNew [i] := False; BitAryOld := BitAryNew; // デバイス ON/OFF の表示を初期化 Label1.Text := ''; Label2.Text := ''; Rectangle4.Fill.Color := TAlphaColorRec.Black; Rectangle5.Fill.Color := TAlphaColorRec.Black; // 反転デバイス番号を更新 Label3.Text := Copy(Label3.Text, 1, 1) + Format('%.3d', [stIndex]); end; end; end; // 表示 with StringGrid1 do begin for i := 0 to 31 do begin if BitAryNew[i] then begin s := (i mod 16).ToString; if Cells[i mod 16 + 1, i div 16] <> s then Cells[i mod 16 + 1, i div 16] := s ; end else begin if Cells[i mod 16 + 1, i div 16] <> '' then Cells[i mod 16 + 1, i div 16] := ''; end; end; end; // 比較 for i := 0 to 31 do begin if BitAryNew[i] and not BitAryOld[i] then begin //Rectangle4.Fill.Color := TAlphaColorRec.Red; with Label1 do begin Text := Copy(Label8.Text, 1, 1) + Format('%.3d', [(i div 16) * 100 + i mod 16 + stIndex]); // TextSettings.FontColor := TAlphaColorRec.White; end; Rectangle5.Fill.Color := TAlphaColorRec.Red; with Label2 do begin Text := 'ON'; TextSettings.FontColor := TAlphaColorRec.White; end; ttsFlag := True; end else if not BitAryNew[i] and BitAryOld[i] then begin //Rectangle4.Fill.Color := TAlphaColorRec.Black; with Label1 do begin Text := Copy(Label8.Text, 1, 1) + Format('%.3d', [(i div 16) * 100 + i mod 16 + stIndex]); //TextSettings.FontColor := TAlphaColorRec.Lime; end; Rectangle5.Fill.Color := TAlphaColorRec.Black; with Label2 do begin Text := 'OFF'; TextSettings.FontColor := TAlphaColorRec.Lime; end; ttsFlag := True; end; if ttsFlag and Switch1.IsChecked then begin s := NumToSpeechText(Label1.Text); if Label2.Text = 'ON' then s := s + '。' + 'オン' else s := s + '。' + 'オフ'; SpeakOut(s); end; end; end; if flag then Label7.Text := (TThread.GetTickCount - Ticks).ToString else Label7.Text := 'PC 接続失敗'; if flag then Timer1.Enabled := True; except Label7.Text := 'PC 応答なし'; Timer1.Enabled := True; end; end; end; end.