MELSOFT GX Works 2 / (3) で スマホ I/O チェック 2019/04/01
・2019/04/04 設定が保存されないのを手直し。尺度調整を1か所だけに変更。
・2019/04/01 初版作成
MX Component、追加機器等 は不要です。
GX Works 2, (3) がインストールされ、Bluetooth が使えるパソコンがあれば、 Android スマホで I/O チェックが出来ます。
※GX Works3 の場合 Q/L/FXシリーズ互換モードのみ使用可能です。今のところ FX5, RCPUには対応していません。
※GX Works2, (3) は画面上に見えている必要があります。 本ツールは画面上に見えていなくても動作します。
・対象デバイスは、X または Y のみ。FX: 80点、Q/L :160点 のモニタ、及び出力反転が可能です。
・状態が変化した最後のデバイス名を読み上げます。
■仕組み
・「デバイス/バッファメモリ一括モニタ」を表示させておき、そのウィンドウを 250 msec 周期でキャプチャします。
その画像から、CPUタイプ、デバイス ON/OFF 状態を判断します。
下 ↓ のスクリーンショットのマス内の赤い点。その位置の色が白であれば OFF、青であれば ON と判断。
データ行の 1 行目の左から 9 番目のマスの色が、白または青でなければ、FXCPU (8 進数アドレス)と判断。
・Android スマホへは、Bluetooth 経由の COM ポートを使って、デバイスの状態を送信しています。
あらかじめ、ペアリング後、PC 側に「Bluetooth リンク経由の標準シリアル」ポートの追加が必要です。
方法については、「Bluetooth ペアリング」、「Bluetooth COMポート 追加」等でネットで検索してみて下さい。

■注意点
・正常に動くかどうかは、画面キャプチャの位置が合うかどうかによります。上 ↑ のスクリーンショットのようであれば、ほぼ使えます。
環境によっては、微調整が必要になります。
・画面キャプチャのため、GX Works2 が最前面になり、操作がしにくくなります。作業できない場合は、キャプチャを「STOP」にしてください。
「STOP」 するとスマホとの通信が出来なくなるため、スマホアプリの再起動が必要になります。
・なるべく存在しないアドレスを設定しないでください。(エラーダイアログは自動で消えるようになっています。)
■Android アプリ
PC 側で、[START] ボタンをクリックし、画面キャプチャ、CPU 判断等 が出来ていることを確認してからアプリを起動してください。
初回起動時は、エラーになります。一番上のコンボボックスから接続先のPC名を選択し、「保存」 をタップして PC 名を保存。再起動してください。
PC と通信できない時は、PC 側で [STOP] をクリック、[START] をクリックしたあと、アプリを再度起動してみて下さい。
・セル(マス目)をタップすると、反転対象のデバイス番号が変わります。

■著作権、免責事項等
本ツール、アプリの著作権は作者 f.izawa が所有し、これを主張します。
本ツール、アプリをインストール、使用したことによる事故、損害等の一切について、作者はその責を負いません。
■作者連絡先
e-mail : f.izawa@dream.com (@は小文字にしてください)
URL : http://www.izawa-web.com/
■ダウンロード
GXW2_IO.zip (Windows 側アプリ 本体のみ)
※高解像度環境にて作成しています。他の環境では、サイズ、配置が異なる可能性があります。
設定タブにて、「全体の尺度」を 0.446 程度にすると、他の設定はそのままで使用できます。
GX Works 2 (3 )にて、「デバイス/バッファメモリ一括モニタ」をのデータ行を10行以上表示させた状態で使ってください。
GXW2_IO.apk (Android 側アプリ本体のみ)
// **********************************
// Windows 側
// **********************************
unit GXW2Unit4;unit GXW2Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.Buttons,
OoMisc, AdPort, AdSelCom, AdPacket, IniFiles, Vcl.ComCtrls;
type
// 16 点×10 行のデータを保持
TBitAry = array [0..159] of ShortInt;
TWordAry = array [0..9] of Word;
type
TForm4 = class(TForm)
PageControl1: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
Image1: TImage;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
SpeedButton3: TSpeedButton;
SpeedButton4: TSpeedButton;
SpeedButton5: TSpeedButton;
SpeedButton6: TSpeedButton;
SpeedButton7: TSpeedButton;
SpeedButton8: TSpeedButton;
SpeedButton9: TSpeedButton;
SpeedButton10: TSpeedButton;
Edit1: TEdit;
Button3: TButton;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Button4: TButton;
Edit5: TEdit;
CheckBox1: TCheckBox;
ComboBox1: TComboBox;
Timer1: TTimer;
ApdComPort1: TApdComPort;
ApdDataPacket1: TApdDataPacket;
Label3: TLabel;
Label4: TLabel;
Button1: TButton;
GroupBox1: TGroupBox;
Label5: TLabel;
Edit8: TEdit;
Label6: TLabel;
Edit9: TEdit;
Label7: TLabel;
Edit10: TEdit;
Label8: TLabel;
Edit11: TEdit;
Label9: TLabel;
Edit12: TEdit;
Button2: TButton;
Button5: TButton;
Label1: TLabel;
Edit6: TEdit;
procedure Timer1Timer(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button4Click(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 CheckBox1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString);
procedure Button2Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
var
// SpreadSheet ウィンドウ
SpreadSheetHwnd : HWND;
// SpreadSheet ウィンドウの左上座標
shtCaptLeft, shtCaptTop : integer;
// 座標補正の係数
//shtXScale, shtYScale : double;
// 列幅、行高
shtColWidth, shtRowHeight : double;
// マウスクリック、ピクセル取得の左上基点からのオフセット
pickXOff, pickYOff : integer;
// 先頭デバイス番号の Edit ウィンドウ
DeviceEditHwnd : HWND;
// 先頭デバイス番号の ComboBox ウィンドウ
DeviceComboHwnd : HWND;
// メインウィンドウ
GXW2FrameHwnd : HWND;
captL, captW, captH: integer;
// 全体の尺度
shtScale : double;
// 比較用内部データ(ShortInt)
// 今回値
BitAryNew : TBitAry;
// 前回値
BitAryOld : TBitAry;
// 常時監視の先頭デバイスが変わる判断
devHeadOld : string;
// 先頭デバイスが変わった時は比較を1回パス
passFlag : boolean;
// 先頭デバイスの変更が失敗した時に戻す
devChgOld : string;
// スマホへのデータ送信用
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;
//******************************************
// 8 進数表記 -> 整数
//******************************************
function OctToIntDef(const Value: string; Def :integer): integer;
var
i, len, n : integer;
begin
result := 0;
len := Length(Value);
for i := 1 to len do begin
n := StrToIntDef(Value[i], -1);
if (n >= 0 ) and (n < 8) then
Inc(result, n * IntPower(8, len - i))
else begin
result := Def;
break;
end;
end;
end;
//******************************************
// 整数 -> 8 進数表記
//******************************************
function IntToOct(Value: integer; digits: Integer): string;
var
rest: Longint;
oct: string;
i: Integer;
begin
oct := '';
while Value <> 0 do begin
rest := Value mod 8;
Value := Value div 8;
oct := IntToStr(rest) + oct;
end;
if Length(oct) < digits then
for i := Length(oct) + 1 to digits do oct := '0' + oct;
result := oct;
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;
//******************************************
// ウィンドウのタイトル(キャプション)を得る
//******************************************
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;
// 先頭デバイスのコンボボックスを探す
function EnumCWinProc_DeviceCombo(h: HWND; lparam: Integer): Bool; stdcall;
var
Title : array [0..255] of char;
begin
result := true;
if GetWindowText(h, Title, 255) <> 0 then begin
if Pos('デバイス/バッファメモリ一括モニタ', Title) = 1 then begin
DeviceComboHwnd := h;
Result := False;
end;
end;
end;
// デバイス一括モニタのスプレッドシートを探す
function EnumCWinProc_SpreadSheet(h: HWND; lparam: Integer): Bool; stdcall;
var
ClassName : string;
begin
result := true;
ClassName := GetHwndClassName(h);
if 'SPR32AU70_SpreadSheet' = ClassName then begin
SpreadSheetHwnd := h;
Result := False;
end;
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;
procedure TForm4.ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString);
var
cmd, res, s, s0, s1 : string;
i, j, k0 : integer;
begin
cmd := Trim(string(Data));
//Memo1.Lines.Add(cmd);
if cmd = 'CPU' then begin
if CheckBox1.Checked then res := 'FX'
else res := 'QL';
ApdComPort1.PutString(res + #13#10);
end
else if cmd = 'READ' then begin
s0 := ''; s1 := '';
for i := 0 to 9 do begin
k0 := 0;// k1 := 0;
for j := 0 to 15 do begin
if BitAryNew[i * 16 + j] = 1 then
k0 := k0 + IntPower(2, j);
end;
WordAryNew[i] := k0;
//k1 := WordAryOld[i];
s0 := s0 + IntToHex(k0, 4);
s1 := s1 + IntToHex(WordAryOld[i], 4);
end;
if CheckBox1.Checked then res := 'FX'
else res := 'QR';
res := res + Copy(Edit1.Text + ' ', 1, 6) ; // 先頭デバイス
res := res + s0 + s1; // 合計 88 文字
ApdComPort1.PutString(res + #13#10);
WordAryOld := WordAryNew;
end
// ビット反転
else if Pos('BTRV', cmd) = 1 then begin
s := Copy(cmd, 6);
Edit4.Text := s;
Button4Click(self);
ApdComPort1.PutString('OK' + #13#10);
end
// 先頭デバイス変更
else if Pos('DEVN', cmd) = 1 then begin
s := Copy(cmd, 6);
Edit5.Text := s;
Button1Click(self);
ApdComPort1.PutString('OK' + #13#10);
end
else
ApdComPort1.PutString('??' + #13#10);
end;
procedure TForm4.Button1Click(Sender: TObject);
// 先頭デバイス変更
var
x, y : integer;
pt, pt0 : TPoint;
h : HWND;
tmFlag : boolean;
begin
tmFlag := Timer1.Enabled;
if tmFlag then Timer1.Enabled := False;
if IsWindowVisible(DeviceEditHwnd) then begin
GetCursorPos(pt0);
SendTextHwnd(DeviceEditHwnd, Trim(Edit5.Text));
SetForegroundWindow(DeviceEditHwnd);
// [Enter]
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
// セル 0,0 に移動
x := shtCaptLeft + Trunc(pickXOff * shtScale);
y := shtCaptTop + Trunc(shtRowHeight + pickYOff * shtScale);
SetCursorPos(x, y);
pt.X := x; pt.Y := y;
// マウス直下の Window を取得
if WindowFromPoint(pt) = SpreadSheetHwnd then begin
// マウスクリックで Active に
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
end;
// カーソルを元に戻す
SetCursorPos(pt0.X, pt0.Y);
Sleep(200);
// 変更できない時、ダイアログを探す
h := FindWindow(nil, 'MELSOFTシリーズ GX Works2');
if h = 0 then
h := FindWindow(nil, 'MELSOFT GX Works3 Q/L/FXシリーズ互換モード');
// ダイアログを閉じる
if (h <> 0) and IsWindowVisible(h) then begin
if '#32770' = GetHwndClassName(h) then begin
SetForegroundWindow(h);
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
Sleep(200);
// 先頭番号を戻す
if devChgOld <> '' then begin
Edit5.Text := devChgOld;
end
else begin
Edit5.Text := Copy(Edit5.Text, 1, 1) + '000';
devChgOld := Edit5.Text;
end;
SendTextHwnd(DeviceEditHwnd, Trim(Edit5.Text));
SetForegroundWindow(DeviceEditHwnd);
// [Enter]
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
Sleep(200);
// セル 0,0 に移動
x := shtCaptLeft + Trunc(pickXOff * shtScale);
y := shtCaptTop + Trunc(shtRowHeight + pickYOff * shtScale);
SetCursorPos(x, y);
pt.X := x; pt.Y := y;
// マウス直下の Window を取得
if WindowFromPoint(pt) = SpreadSheetHwnd then begin
// マウスクリックで Active に
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
end;
// カーソル位置を戻す
SetCursorPos(pt0.X, pt0.Y);
Sleep(200);
end;
end;
// ダイアログを閉じる
h := FindWindow(nil, '現在値変更');
if (h <> 0) and IsWindowVisible(h) and IsWindowEnabled(h) then begin
if '#32770' = GetHwndClassName(h) then begin
SetForegroundWindow(h);
keybd_event(VK_ESCAPE, 0, 0, 0);
keybd_event(VK_ESCAPE, 0, KEYEVENTF_KEYUP, 0);
Sleep(200);
end;
end;
end;
if tmFlag then Timer1.Enabled := True;
end;
procedure TForm4.Button2Click(Sender: TObject);
begin
shtScale := StrToFloatDef(Edit6.Text, 1);
Edit6.Text := Format('%.4f', [shtScale]);
//shtYScale := StrToFloatDef(Edit7.Text, 1);
//Edit7.Text := Format('%.3f', [shtYScale]);
captW := StrToIntDef(Edit8.Text, 504);
Edit8.Text := IntToStr(captW);
captH := StrToIntDef(Edit9.Text, 372);
Edit9.Text := IntToStr(captH);
captL := StrToIntDef(Edit10.Text, 243);
Edit10.Text := IntToStr(captL);
pickXOff := StrToIntDef(Edit11.Text, 10);
Edit11.Text := IntToStr(pickXOff);
pickYOff := StrToIntDef(Edit12.Text, 8);
Edit12.Text := IntToStr(pickYOff);
end;
procedure TForm4.Button3Click(Sender: TObject);
var
s : string;
begin
if Button3.Caption = 'START' then begin
Button3.Caption := 'STOP';
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
Timer1.Enabled := False;
Button3.Caption := 'START';
if ApdComPort1.Open then begin
ApdComPort1.Open := False;
ComboBox1.Enabled := True;
end;
end;
end;
procedure TForm4.Button4Click(Sender: TObject);
// デバイス ON/OFF 反転
var
h, k, n, m : integer;
x, y : integer;
pt, pt0 : TPoint;
hnd : HWND;
tmFlag : boolean;
begin
tmFlag := Timer1.Enabled;
if tmFlag then Timer1.Enabled := False;
x := 0; y := 0;
GetCursorPos(pt0);
if CheckBox1.Checked then begin
// 先頭デバイス
h := OctToIntDef(Copy(Edit1.Text, 2), 0);
// 反転対象のデバイス
k := OctToIntDef(Copy(Edit4.Text, 2), -1);
if k >= 0 then begin
k := k - h;
n := k div 8;
m := k mod 8;
if BitAryNew[n * 16 + m] >= 0 then begin
x := shtCaptLeft + Trunc((7 - m) * shtColWidth + pickXOff * shtScale);
y := shtCaptTop + Trunc((n + 1) * shtRowHeight + pickYOff * shtScale);
end;
end;
end
else begin
// 先頭デバイス
h := StrToIntDef('$' + Copy(Edit1.Text, 2), 0);
// 反転対象のデバイス
k := StrToIntDef('$' + Copy(Edit4.Text, 2), -1);
if k >= 0 then begin
k := k - h;
n := k div 16;
m := k mod 16;
if BitAryNew[n * 16 + m] >= 0 then begin
x := shtCaptLeft + Trunc((15 - m) * shtColWidth + pickXOff * shtScale);
y := shtCaptTop + Trunc((n + 1) * shtRowHeight + pickYOff * shtScale);
end;
end;
end;
if (x > 0) and (y > 0) then begin
SetCursorPos(x, y);
pt.X := x; pt.Y := y;
// マウス直下の Window を取得
if WindowFromPoint(pt) = SpreadSheetHwnd then begin
// マウスクリックで Active に
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
Sleep(100);
// [Shift] + [Enter] キーで反転
keybd_event(VK_SHIFT, 0, 0, 0);
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
end;
Sleep(200);
hnd := FindWindow(nil, '現在値変更');
if (hnd <> 0) and IsWindowVisible(hnd) and IsWindowEnabled(hnd) then begin
if '#32770' = GetHwndClassName(hnd) then begin
SetForegroundWindow(hnd);
keybd_event(VK_ESCAPE, 0, 0, 0);
keybd_event(VK_ESCAPE, 0, KEYEVENTF_KEYUP, 0);
Sleep(200);
end;
end;
end;
// マウス位置を戻す
SetCursorPos(pt0.X, pt0.Y);
if tmFlag then Timer1.Enabled := True;
end;
procedure TForm4.Button5Click(Sender: TObject);
begin
Edit6.Text := '1.0000';
//Edit7.Text := '1.000';
Edit8.Text := '504';
Edit9.Text := '372';
Edit10.Text := '243';
Edit11.Text := '10';
Edit12.Text := '8';
end;
procedure TForm4.CheckBox1Click(Sender: TObject);
begin
if CheckBox1.Checked then begin
SpeedButton5.Caption := '+40';
SpeedButton7.Caption := '-40';
end
else begin
SpeedButton5.Caption := '+80';
SpeedButton7.Caption := '-80';
end;
end;
procedure TForm4.FormCreate(Sender: TObject);
var
i : integer;
ini : TIniFile;
begin
shtScale := 1.0;
//shtYScale := 1.0;
// デバイス SpreadSheet のセルをアクティブにする左上からオフセット
// ピクセルの色取得の位置
pickXOff := 8;
pickYOff := 6;
captL := 243;
captW := 504;
captH := 372;
// 使用可能な 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);
//shtYScale := ini.ReadFloat('Capt', 'YScale', 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);
pickYOff := ini.ReadInteger('Capt', 'pickYOff', pickYOff);
Edit6.Text := Format('%.4f', [shtScale]);
//Edit7.Text := Format('%.3f', [shtYScale]);
Edit8.Text := IntToStr(captW);
Edit9.Text := IntToStr(captH);
Edit10.Text := IntToStr(captL);
Edit11.Text := IntToStr(pickXOff);
Edit12.Text := IntToStr(pickYOff);
finally
ini.Free;
end;
ApdDataPacket1.Enabled := False;
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.WriteFloat('Capt', 'YScale', shtYScale);
ini.WriteInteger('Capt', 'captW', captW);
ini.WriteInteger('Capt', 'captH', captH);
ini.WriteInteger('Capt', 'captL', captL);
ini.WriteInteger('Capt', 'pickXOff', pickXOff);
ini.WriteInteger('Capt', 'pickYOff', pickYOff);
finally
ini.Free;
end;
end;
procedure TForm4.SpeedButton10Click(Sender: TObject);
// デバイス変更
begin
if Uppercase(Copy(Edit5.Text, 1, 1)) <> 'Y' then begin
Edit5.Text := 'Y' + Copy(Edit5.Text, 2);
Button1Click(self);
end;
end;
procedure TForm4.SpeedButton1Click(Sender: TObject);
// [+10],[+1]
var
m, n, h, i, j : integer;
begin
if CheckBox1.Checked then begin
// 先頭デバイス(基準)
n := OctToIntDef(Copy(Edit1.Text, 2), 0);
// 現在の番号
m := OctToIntDef(Copy(Edit4.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton1 then Inc(m)
else m := m + 8;
if m - n < 80 then begin
h := m - n;
i := h div 8;
j := h mod 8;
if BitAryNew[i * 16 + j] >= 0 then
Edit4.Text := Copy(Edit4.Text, 1, 1) + IntToOct(m, 3);
end;
end
else begin
n := StrToIntDef('$' + Copy(Edit1.Text, 2), 0);
m := StrToIntDef('$' + Copy(Edit4.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton1 then Inc(m)
else m := m + 16;
if m - n < 160 then begin
h := m - n;
i := h div 16;
j := h mod 16;
if BitAryNew[i * 16 + j] >= 0 then
Edit4.Text := Copy(Edit4.Text, 1, 1) + IntToHex(m, 3);
end;
end;
end;
procedure TForm4.SpeedButton2Click(Sender: TObject);
// [-10],[-1]
var
n, m : integer;
begin
if CheckBox1.Checked then begin
n := OctToIntDef(Copy(Edit1.Text, 2), 0);
m := OctToIntDef(Copy(Edit4.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton2 then Dec(m)
else m := m - 8;
if m - n >= 0 then
Edit4.Text := Copy(Edit4.Text, 1, 1) + IntToOct(m, 3);
end
else begin
n := StrToIntDef('$' + Copy(Edit1.Text, 2), 0);
m := StrToIntDef('$' + Copy(Edit4.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton2 then Dec(m)
else m := m - 16;
if m - n >= 0 then
Edit4.Text := Copy(Edit4.Text, 1, 1) + IntToHex(m, 3);
end;
end;
procedure TForm4.SpeedButton5Click(Sender: TObject);
// 先頭アドレス変更 [+80],[+100]
var
m : integer;
begin
devChgOld := Edit5.Text;
if CheckBox1.Checked then begin
m := OctToIntDef(Copy(Edit5.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton5 then
m := m + 32
else
m := m + 64;
Edit5.Text := Copy(Edit5.Text, 1, 1) + IntToOct(m, 1);
Button1Click(self);
end
else begin
m := StrToIntDef('$' + Copy(Edit5.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton5 then
m := m + $80
else
m := m + $100;
Edit5.Text := Copy(Edit5.Text, 1, 1) + IntToHex(m, 1);
Button1Click(self);
end;
end;
procedure TForm4.SpeedButton7Click(Sender: TObject);
// 先頭アドレス変更 [-80],[-100]
var
m : integer;
begin
if CheckBox1.Checked then begin
m := OctToIntDef(Copy(Edit5.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton7 then
m := m - 32
else
m := m - 64;
if m >= 0 then begin
Edit5.Text := Copy(Edit5.Text, 1, 1) + IntToOct(m, 1);
Button1Click(self);
end;
end
else begin
m := StrToIntDef('$' + Copy(Edit5.Text, 2), 0);
if Sender as TSpeedButton = SpeedButton7 then
m := m - $80
else
m := m - $100;
if m >= 0 then begin
Edit5.Text := Copy(Edit5.Text, 1, 1) + IntToHex(m, 1);
Button1Click(self);
end;
end;
end;
procedure TForm4.SpeedButton9Click(Sender: TObject);
// デバイス変更
begin
if Uppercase(Copy(Edit5.Text, 1, 1)) <> 'X' then begin
Edit5.Text := 'X' + Copy(Edit5.Text, 2);
Button1Click(self);
end;
end;
procedure TForm4.Timer1Timer(Sender: TObject);
var
h : HWND;
i, j, n : integer;
ARect : TRect;
bmp : TBitmap;
Pnt : PByteArray;
R, G, B : Byte;
x, y : integer;
pt : TPoint;
hBound : integer;
begin
// メインウィンドウを探す
GXW2FrameHwnd := FindWindow('GXW2FrameWnd', nil);
if IsWindowVisible(GXW2FrameHwnd) and not IsIconic(GXW2FrameHwnd) then begin
// メインウィンドウのキャプション
//Edit6.Text := GetWindowCaption(GXW2FrameHwnd);
// 「デバイス/バッファメモリ一括モニタ」Window を探す
EnumChildWindows(GXW2FrameHwnd, @EnumCwinProc_DeviceCombo, 0);
h := GetWindow(DeviceComboHwnd, GW_CHILD);
h := GetWindow(h, GW_CHILD);
for i := 0 to 2 do
h := GetWindow(h, GW_HWNDNEXT);
DeviceEditHwnd := GetWindow(h, GW_CHILD);
// デバイス先頭
Edit1.Text := GetWindowString(DeviceEditHwnd);
if Edit1.Text = '' then begin
SendTextHwnd(DeviceEditHwnd, 'X000');
SetForegroundWindow(DeviceEditHwnd);
// [Enter]
keybd_event(VK_RETURN, 0, 0, 0);
keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0);
Edit1.Text := GetWindowString(DeviceEditHwnd);
end;
if Edit4.Text = '' then Edit4.Text := Edit1.Text;
if Edit5.Text = '' then Edit5.Text := Edit1.Text;
// 先頭デバイスが変わったときは、1回パス
if devHeadOld <> Edit1.Text then begin
passFlag := True;
devHeadOld := Edit1.Text;
// 反転デバイスを変更
Edit4.Text := Edit1.Text;
// 先頭デバイスのアドレスを変更
Edit5.Text := Edit1.Text;
end
else
passFlag := False;
// クラス名「SPR32AU70_SpreadSheet」 を探す
EnumChildWindows(GXW2FrameHwnd, @EnumCwinProc_SpreadSheet, 0);
GetWindowRect(SpreadSheetHwnd, ARect);
// 左上座標を保持
shtCaptLeft := Trunc(ARect.Left + captL * shtScale);
shtCaptTop := ARect.Top;
shtColWidth := (captW * shtScale) / 16 ; // 16 列
shtRowHeight := (captH * shtScale) / 11 ; // 11 行 (タイトルを含む行数)
// アクティブにする
pt.X := shtCaptLeft; pt.Y := shtCaptTop;
if WindowFromPoint(pt) <> SpreadSheetHwnd then begin
SetForegroundWindow(SpreadSheetHwnd);
Sleep(100);
end;
if WindowFromPoint(pt) = SpreadSheetHwnd then begin
// キャプチャ
bmp := TBitmap.Create;
try
bmp.PixelFormat:= TPixelFormat.pf24bit; // 重要
CaptureToBmp(shtCaptLeft, shtCaptTop, Trunc(shtColWidth*16), Trunc(shtRowHeight * 11), bmp);
Image1.Picture.Assign(bmp);
finally
bmp.Free;
end;
// ON/OFF をセルの色でチェック
for i := 0 to 9 do begin
y := Trunc(shtRowHeight * (i+1) + pickYOff * shtScale);
Pnt := Image1.Picture.Bitmap.ScanLine[y];
// FXCPU であるか1行目で判断
if i = 0 then begin
// 9 個めのセルの色で判断
x := Trunc(shtColWidth * 8 + pickXOff * shtScale);
R := Pnt[x * 3 + 2]; G := Pnt[x * 3 + 1]; B := Pnt[x * 3];
// 白または青色
if ((R = 0) and (G = 0) and (B = $FF)) or ((R = $FF) and (G = $FF) and (B = $FF)) then begin
if CheckBox1.Checked then begin
CheckBox1.Checked := False;
CheckBox1Click(self);
end;
end
else begin
// FXCPU である
if not CheckBox1.Checked then begin
CheckBox1.Checked := True;
CheckBox1Click(self);
end;
end;
end;
if CheckBox1.Checked then hBound := 7
else hBound := 15;
for j := 0 to hBound do begin
x := Trunc(shtColWidth * j + pickXOff * shtScale);
R := Pnt[x * 3 + 2]; G := Pnt[x * 3 + 1]; B := Pnt[x * 3];
if (R = 0) and (G = 0) and (B = $FF) then // 青
BitAryNew[i * 16 + hBound - j] := 1
else
if (R = $FF) and (G = $FF) and (B = $FF) then // 白
BitAryNew[i * 16 + hBound - j] := 0
else // その他(グレイ)
BitAryNew[i * 16 + hBound - j] := -1;
with Image1.Picture.Bitmap.Canvas do begin
Pen.Color := clRed;
Brush.Style := bsSolid;
Brush.Color := clRed;
Ellipse(x-2, y-2, x + 2,y + 2);
end;
end;
end;
if not passFlag then begin
// 結果表示
for i:= 0 to 159 do begin
if (BitAryNew[i] >= 0) and (BitAryOld[i] >= 0) and (BitAryNew[i] <> BitAryOld[i]) then begin
if CheckBox1.Checked then begin
n := OctToIntDef(Copy(Edit1.Text, 2), 0);
Edit2.Text := Copy(Edit1.Text, 1, 1) + IntToOct(n + (i div 16) * 8 + i mod 16, 3);
end
else begin
n := StrToIntDef('$'+ Copy(Edit1.Text, 2), 0);
Edit2.Text := Copy(Edit1.Text, 1, 1) + IntToHex(n + (i div 16) * 16 + i mod 16, 3);
end;
if BitAryNew[i] = 1 then Edit3.Text := 'ON'
else Edit3.Text := 'OFF';
end;
end;
end
else begin
Edit2.Text := '';
Edit3.Text := '';
for i := 0 to 159 do BitAryNew[i] := 0;
end;
// 前回値を更新
BitAryOld := BitAryNew;
end;
end;
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..159] 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;
ComboBox1: TComboBox;
ComboBox2: TComboBox;
Label7: TLabel;
StringColumn10: TStringColumn;
StringColumn11: TStringColumn;
StringColumn12: TStringColumn;
StringColumn13: TStringColumn;
StringColumn14: TStringColumn;
StringColumn15: TStringColumn;
StringColumn16: TStringColumn;
StringColumn17: TStringColumn;
CheckBox1: TCheckBox;
ComboBox3: TComboBox;
Button2: TButton;
Switch1: TSwitch;
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 ComboBox1Change(Sender: TObject);
procedure StringGrid1CellClick(const Column: TColumn; const Row: Integer);
procedure CheckBox1Change(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button2Click(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;
GB_DeviceName : string;
GB_DeviceStartIndex : integer;
GB_fxFlag : boolean;
//GB_Busy : boolean;
constructor Create(AOwner : TComponent); override;
destructor Destroy; override;
end;
var
Form4: TForm4;
ADevice : TBluetoothDevice;
ASocket : TBluetoothSocket;
GThdMode : integer;
GCmdMode : integer;
ThBt : TBtThread;
OpenNGcnt : integer;
OpenMsecCnt : integer;
Counter : integer;
BtDeviceHead : string;
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;
// 8 進数表記 -> 整数
function OctToIntDef(const Value: string; Def :integer): integer;
var
i, len, n : integer;
begin
result := 0;
len := Length(Value);
for i := 1 to len do begin
n := StrToIntDef(Copy(Value, i, 1), -1);
if (n >= 0 ) and (n < 8) then
Inc(result, n * IntPower(8, len - i))
else begin
result := Def;
break;
end;
end;
end;
// 整数 -> 8 進数表記
function IntToOct(Value: integer; digits: Integer): string;
var
rest: Longint;
oct: string;
i: Integer;
begin
oct := '';
while Value <> 0 do begin
rest := Value mod 8;
Value := Value div 8;
oct := IntToStr(rest) + oct;
end;
if Length(oct) < digits then
for i := Length(oct) + 1 to digits do oct := '0' + oct;
result := oct;
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
// PC名
//Synchronize(procedure() begin
// Form4.Label6.Text :=
// '[' + ABluetoothManager.CurrentAdapter.AdapterName + ']'
//end);
// 過去にペアリングされたデバイスの一覧から、ターゲット を探す
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 begin
Sleep(250);
GCMDMODE := cmdSCCONNECT;
end;
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.Button2Click(Sender: TObject);
// 接続先保存
var
IniFile: TMemIniFile;
begin
IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine(
System.IOUtils.TPath.GetDocumentsPath, 'GXW2_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.CheckBox1Change(Sender: TObject);
var
i :integer;
begin
GB_fxFlag := CheckBox1.IsChecked;
// 初期に戻す
with StringGrid1 do begin
if not GB_fxFlag then
for i := 0 to 9 do Cells[0, i] := (i * 16).ToHexString(3)
else
for i := 0 to 9 do Cells[0, i] := IntToOct(i * 8, 3);
Row := 0;
Col := 1;
end;
ComboBox1.OnChange := nil;
ComboBox2.OnChange := nil;
with ComboBox2 do begin
BeginUpdate;
Items.Clear;
if not GB_fxFlag then
for i := 0 to 255 do Items.Add(IntToHex(i * $80, 3))
else
for i := 0 to 255 do Items.Add(IntToOct(i * 32, 3));
EndUpdate;
ItemIndex := 0;
end;
// X に戻す
with ComboBox1 do begin
ItemIndex := 0;
end;
ComboBox1.OnChange := ComboBox1Change;
ComboBox2.OnChange := ComboBox1Change;
Label3.Text := 'X000';
end;
procedure TForm4.ComboBox1Change(Sender: TObject);
var
AData : TBytes;
s2, s1, res : string;
ATimeout: Cardinal;
i : integer;
begin
// ここでは、StringGrid のデバイス番号を変更しない
// PC 側へ先頭アドレスを送信するだけ
if (ASocket <> nil) and ASocket.Connected then begin
Timer1.Enabled := False;
// 初期化
Label1.Text := '';
Label2.Text := '';
for i := 0 to 159 do BitAryNew[i] := False;
BitAryOld := BitAryNew;
// PC の値を変更
ATimeout := 250;
// デバイス名
with ComboBox1 do begin
if ItemIndex < 0 then ItemIndex := 0;
s1 := ListBox.Items[ItemIndex];
end;
with ComboBox2 do begin
if ItemIndex < 0 then ItemIndex := 0;
if ItemIndex < 0 then s2 := '000'
else begin
if not GB_fxFlag then s2 := IntToHex(ItemIndex * $80, 3)
else s2 := IntToOct(ItemIndex * 32, 3);
end;
end;
AData := TEncoding.ANSI.GetBytes('DEVN ' + s1 + s2 + #13#10);
// 送信
ASocket.SendData(AData);
res := ASocketReceiveData(ASocket, ATimeout);
Rectangle4.Fill.Color := TAlphaColorRec.Black;
Rectangle5.Fill.Color := TAlphaColorRec.Black;
Timer1.Enabled := True;
end;
end;
procedure TForm4.FormCreate(Sender: TObject);
var
IniFile: TMemIniFile; // uses .... System.IniFiles;
begin
GB_DeviceName := 'X';
GB_DeviceStartIndex := 0;
GB_fxFlag := True;
StringColumn1.Header := 'X';
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 := 'A';
StringColumn13.Header := 'B';
StringColumn14.Header := 'C';
StringColumn15.Header := 'D';
StringColumn16.Header := 'E';
StringColumn17.Header := 'F';
// 縦画面に固定
Application.FormFactor.Orientations :=
[TFormOrientation.Portrait, TFormOrientation.InvertedPortrait];
// use ..... System.IOUtils;
IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine(
System.IOUtils.TPath.GetDocumentsPath, 'GXW2_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;
// FX モード で起動
CheckBox1.IsChecked := True;
CheckBox1Change(self);
end;
procedure TForm4.FormDestroy(Sender: TObject);
begin
if ASocket <> nil then begin
ASocket.Close;
ASocket.Free;
ASocket := nil;
end;
end;
procedure TForm4.Rectangle1Click(Sender: TObject);
// [ + ]
var
n, m : integer;
begin
if not GB_fxFlag then begin
n := StrToIntDef('$' + Copy(Label3.Text, 2), 0);
Inc(n);
m := n - GB_DeviceStartIndex;
if m >= 0 then begin
if m div 16 < 10 then begin
with Label3 do begin
Text := Copy(Text, 1, 1) + n.ToHexString(3);
TextSettings.FontColor := TAlphaColorRec.Orange;
end;
with StringGrid1 do begin
OnCellClick := nil;
Row := m div 16;
Col := m mod 16 + 1;
OnCellClick := StringGrid1CellClick;
SetFocus;
end;
end;
end;
end
else begin
n := OctToIntDef(Copy(Label3.Text, 2), 0);
Inc(n);
m := n - GB_DeviceStartIndex;
if m >= 0 then begin
if m div 8 < 10 then begin
with Label3 do begin
Text := Copy(Text, 1, 1) + IntToOct(n, 3);
TextSettings.FontColor := TAlphaColorRec.Orange;
end;
with StringGrid1 do begin
OnCellClick := nil;
Row := m div 8;
Col := m mod 8 + 1;
OnCellClick := StringGrid1CellClick;
SetFocus;
end;
end;
end;
end;
end;
procedure TForm4.Rectangle2Click(Sender: TObject);
// [ - ]
var
n : integer;
begin
if not GB_fxFlag then begin
n := StrToIntDef('$' + Copy(Label3.Text, 2), 0);
Dec(n);
if n < 0 then n := 0;
with Label3 do begin
Text := Copy(Text, 1, 1) + n.ToHexString(3);
TextSettings.FontColor := TAlphaColorRec.Orange;
end;
n := n - GB_DeviceStartIndex;
if n >= 0 then begin
with StringGrid1 do begin
OnCellClick := nil;
Row := n div 16;
Col := n mod 16 + 1;
OnCellClick := StringGrid1CellClick;
SetFocus;
end;
end;
end
else begin
n := OctToIntDef(Copy(Label3.Text, 2), 0);
Dec(n);
if n < 0 then n := 0;
with Label3 do begin
Text := Copy(Text, 1, 1) + IntToOct(n, 3);
TextSettings.FontColor := TAlphaColorRec.Orange;
end;
n := n - GB_DeviceStartIndex;
if n >= 0 then begin
with StringGrid1 do begin
OnCellClick := nil;
Row := n div 8;
Col := n mod 8 + 1;
OnCellClick := StringGrid1CellClick;
SetFocus;
end;
end;
end;
end;
procedure TForm4.StringGrid1CellClick(const Column: TColumn;
const Row: Integer);
var
n : integer;
begin
// 出力反転の対象
if not GB_fxFlag or (GB_fxFlag and (Column.Index <= 8)) then begin
n := StrToIntDef('$' + StringGrid1.Cells[0, Row], 0) + StrToIntDef('$' + Column.Header, 0);
with Label3 do begin
Text := GB_DeviceName + n.ToHexString(3);
TextSettings.FontColor := TAlphaColorRec.Orange;
end;
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;
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
if not GB_fxFlag then n := StrToIntDef('$' + Copy(Label1.Text, 2), -1)
else n := OctToIntDef(Copy(Label1.Text, 2), -1);
if (n >= GB_DeviceStartIndex) then begin
n := n - GB_DeviceStartIndex;
if (not GB_fxFlag and (Row = n div 16) and (Column.Index = n mod 16 + 1)) or
(GB_fxFlag and (Row = n div 8) and (Column.Index = n mod 8 + 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;
if not GB_fxFlag then s := IntToHex(n mod 16, 1)
else s := IntToHex(n mod 8, 1);
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, j, k : integer;
Ticks : Cardinal;
s, s1 : string;
n, idx : integer;
flag : boolean;
fxFlag : boolean;
begin
if not ((GCMDMODE = cmdSCCONNECT) and ASocket.Connected) then begin
Inc(OpenMsecCnt);
CheckBox1.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 Label7.Text = '' then begin
AData := TEncoding.ANSI.GetBytes('CPU' + #13#10);
// 送信
ASocket.SendData(AData);
// 受信
res := ASocketReceiveData(ASocket, ATimeout);
flag := res <> '';
fxFlag := (Pos('FX', res) = 1);
if fxFlag then Label7.Text := 'FXCPU'
else Label7.Text := 'Q/LCPU';
if GB_fxFlag <> fxFlag then begin
CheckBox1.IsChecked := fxFlag;
CheckBox1Change(self);
end;
end
else begin
// デバイス一括読み出しコマンド
AData := TEncoding.ANSI.GetBytes('READ' + #13#10);
// 送信
ASocket.SendData(AData);
// 受信
res := ASocketReceiveData(ASocket, ATimeout);
flag := res <> '';
// データ格納
if res.Length >= 88 then begin
// FX FLAG
//GB_FxFlag := Copy(res, 1,2) = 'FX'; // 未使用
// 先頭デバイス
s1 := Copy(res, 3, 1);
if not GB_fxFlag then
idx := StrToIntDef('$' + Trim(Copy(res, 4, 5)), 0)
else
idx := OctToIntDef(Trim(Copy(res, 4, 5)), 0);
for i := 0 to 9 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 + 49, 4);
n := StrToIntDef('$' + s, 0);
for j := 0 to 15 do
BitAryOld[i * 16 + j] := n and IntPower(2, j) > 0;
end;
// 先頭デバイスが変わった
if (GB_DeviceName <> s1) or (GB_DeviceStartIndex <> idx) then begin
GB_DeviceName := s1;
GB_DeviceStartIndex := idx;
// イベント無効 (PC へ送り返すため)
ComboBox1.OnChange := nil;
ComboBox2.OnChange := nil;
with ComboBox1 do begin
if GB_DeviceName = 'X' then ItemIndex := 0
else ItemIndex := 1;
end;
// 先頭デバイス番号
with ComboBox2 do begin
if Items.Count > 0 then begin
if not GB_fxFlag then
ItemIndex := GB_DeviceStartIndex div $80
else
ItemIndex := GB_DeviceStartIndex div 32;
end;
end;
// イベントを戻す
ComboBox1.OnChange := ComboBox1Change;
ComboBox2.OnChange := ComboBox1Change;
// X or Y
StringColumn1.Header := GB_DeviceName;
// アドレス番号を変える
with StringGrid1 do begin
if not GB_fxFlag then begin
for i := 0 to 9 do
Cells[0, i] := (GB_DeviceStartIndex + i * 16).ToHexString(3);
end
else begin
for i := 0 to 9 do
Cells[0, i] := IntToOct(GB_DeviceStartIndex + i * 8, 3);
end;
Row := 0;
Col := 1;
end;
// デバイス ON/OFF の表示を初期化
Label1.Text := '';
Label2.Text := '';
Rectangle4.Fill.Color := TAlphaColorRec.Black;
Rectangle5.Fill.Color := TAlphaColorRec.Black;
// 反転デバイス番号を更新
if not GB_fxFlag then begin
Label3.Text := GB_DeviceName + IntToHex(GB_DeviceStartIndex, 3);
end
else begin
Label3.Text := GB_DeviceName + IntToOct(GB_DeviceStartIndex, 3);
end;
for i := 0 to 159 do BitAryOld[i] := False;
end;
end;
// 表示
with StringGrid1 do begin
for i := 0 to 9 do begin
for j := 0 to 15 do begin
if BitAryNew[i * 16 + j] then s := j.ToHexString(1)
else s := '';
if Cells[j + 1, i] <> s then Cells[j + 1, i] := s;
if GB_fxFlag and (j = 7) then break;
end;
end;
end;
// 比較
// 内部データ数 = 160
for i := 0 to 9 do begin
for j := 0 to 15 do begin
k := i * 16 + j;
if BitAryNew[k] <> BitAryOld[k] then begin
with Label1 do begin
if not GB_fxFlag then begin
idx := k + GB_DeviceStartIndex;
Text := GB_DeviceName + idx.ToHexstring(3);
end
else begin
idx := i * 8 + j + GB_DeviceStartIndex;
Text := GB_DeviceName + IntToOct(idx, 3);
end;
end;
if BitAryNew[k] then begin
Rectangle4.Fill.Color := TAlphaColorRec.Red;
Label1.TextSettings.FontColor := TAlphaColorRec.White;
Rectangle5.Fill.Color := TAlphaColorRec.Red;
with Label2 do begin
Text := 'ON';
TextSettings.FontColor := TAlphaColorRec.White;
end;
end
else begin
Rectangle4.Fill.Color := TAlphaColorRec.Black;
Label1.TextSettings.FontColor := TAlphaColorRec.Lime;
Rectangle5.Fill.Color := TAlphaColorRec.Black;
with Label2 do begin
Text := 'OFF';
TextSettings.FontColor := TAlphaColorRec.Lime;
end;
end;
if Switch1.IsChecked then begin
s := Copy(Label1.Text, 1, 1) + #13 + NumToSpeechText(Copy(Label1.Text, 2));
if Label2.Text = 'ON' then s := s + '。' + 'オン'
else s := s + '。' + 'オフ';
SpeakOut(s);
end;
end;
if GB_fxFlag and (j = 7) then break;
end;
end;
end;
if flag then
CheckBox1.Text := (TThread.GetTickCount - Ticks).ToString
else
CheckBox1.Text := 'PC 接続失敗';
if flag then
Timer1.Enabled := True;
except
CheckBox1.Text := 'PC 応答なし';
Timer1.Enabled := True;
end;
end;
end;
end.