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.