KEYENCE KV COM+ I/O チェッカー (2019/03/13, 24)
2019/03/24 Android アプリで手動操作のときのブザー音を追加
2019/03/13 初版作成
現場での I/O チェックに特化したツールです。KEYENCE KV COM+ を使っています。
パソコンでの単体使用のほか、パソコンに内蔵の Bluetooth 経由で Android スマホからでも利用可能です。
パソコン <-> KV 間の通信は、KV COM + で行い、250 msec 周期でポーリングを行い、先頭アドレスから 160
点のビットデータを記憶します。
Android へは、Bluetooth でそのデータを転送しています。このため、Android 単体での使用はできません。
「KV STUDIO」 との併用も可能です。
・状態変化のあった最後のデバイスの ON/OFF を表示。音声合成で読み上げ。
・操作可能なデバイスの ON/OFF 反転。
・あらかじめコメントファイルを作成しておくことで、たコメントの表示、音声合成で読み上げ。
以上のことが、パソコン、Android 端末で利用可能です。
使用するには
・パソコンに KV COM+ library、 または KV COM+ for EXCEL のインストールが必要です。
こちらで使用しているバージョンは、Ver.1.35 です。初期のバージョンでは使えない可能性があります。
KV COM+ for EXCEL の60分体験版でも使用可能と思いますが、検証はしていません。
Android の場合、上記に加えて、
・内蔵または外付けの Bluetooth で、「Bluetoothデバイスの追加」、「Bluetooth 経由のシリアルポート」の追加が必要です。
※仮想シリアルポートの追加は 「Bluetooth の設定」 から行えます。
・使用前に一度だけペアリングが必要です。
使い方
・下のスクリーンショットの2つのコンボボックスを設定。
左側:「Bluetooth 経由のシリアルポート」の COM ポート番号を選択。
右側:通信先の KV のシリーズ名を選択(すべて USB 接続です)
・[OPEN] ボタンをクリックすると、KVと繋がりポーリングを開始します。
・デバイスの先頭番号の変更は、[+10000], [-10000], [+1000], [-1000] ボタンで行います。
・手動操作の対象デバイスは、[+100], [-100], [+1], [-1] ボタンで行い、[ 反転 ] ボタンで ON/OFF が反転します。
グリッドの対象セルをクリックしても、手動操作対象のデバイス番号が変わります。
モニタ中の160点以外のデバイスでも指定可能です。
※割りつけられている入力デバイスは、ON/OFF できません。
・音声、コメントのチェックボックスは、音声合成読み上げ用です。
(Windows 8.1/ 10 では、音声合成が利用可能でした。)
※音声が終わるまで、次の処理ができませんので、作業効率が悪くなります。

Android 側の使い方
・あらかじめ、パソコンと Android 端末をペアリングしておいてください。
・パソコン側のアプリで、KV と通信中にしておいてください。
・始めて起動すると、接続に失敗します。
↓のスクリーンショットで、一番うえのコンボボックスから接続先のパソコンを選択して、[保存]ボタンをタップし、終了します。
2回目以降は、設定したパソコン名が見つかると、それに接続されます。
・接続に失敗する場合は、一度終了し、パソコン側で [CLOSE] -> [OPEN] 後、実行してください。
・右上のスイッチは、音声合成読み上げ用です。

■著作権、免責事項等
本ツールはフリーウェア扱いですが、著作権は作者 f.izawa が所有し、これを主張します。
本ツールをインストール、使用したことによる事故、損害等の一切について、作者はその責を負いません。
■作者連絡先
・e-mail : f.izawa@dream.com (@を小文字に変えて下さい)
・URL : http://www.izawa-web.com/
■開発、動作確認環境
・Windows 10 64bit / Delphi 10.2.3 Community Edition
・Keyence KV-5000, KV-N14 他
■ダウンロード
・KvCom_IO.zip Windows用EXE 本体のみ 2019/03/13
・KVCOM_IO.apk Android 用 APK 本体のみ 2019/03/24(手動操作時のブザー音追加版)
// ----------------------
// Windows側
// ----------------------
unit KvComPlusUnit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.OleCtrls,
DATABUILDERAXLibEx_TLB, Vcl.OleServer, Vcl.Buttons,
Vcl.Grids, Vcl.ExtCtrls, OoMisc, AdPort, AdSelCom, AdPacket, IniFiles,
Vcl.ComCtrls, Vcl.ExtDlgs, System.UITypes, ClipBrd,
SpeechLib_TLB, ComObj;
type
TWordAry = array [0.. 9] of Word;
TBoolAry = array [0.. 159] of boolean;
type
TForm4 = class(TForm)
DBCommManager1: TDBCommManager;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
Timer1: TTimer;
ApdComPort1: TApdComPort;
ApdDataPacket1: TApdDataPacket;
Label1: TLabel;
Edit1: TEdit;
SpeedButton2: TSpeedButton;
SpeedButton1: TSpeedButton;
SpeedButton3: TSpeedButton;
SpeedButton4: TSpeedButton;
ComboBox1: TComboBox;
Button2: TButton;
Button3: TButton;
ComboBox2: TComboBox;
Edit3: TEdit;
Edit4: TEdit;
Edit2: TEdit;
Button1: TButton;
StringGrid1: TStringGrid;
StringGrid2: TStringGrid;
Button4: TButton;
OpenTextFileDialog1: TOpenTextFileDialog;
Edit5: TEdit;
Button5: TButton;
SaveTextFileDialog1: TSaveTextFileDialog;
Button6: TButton;
Edit6: TEdit;
SpeedButton5: TSpeedButton;
SpeedButton6: TSpeedButton;
SpeedButton7: TSpeedButton;
SpeedButton8: TSpeedButton;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Edit7: TEdit;
Edit8: TEdit;
Label5: TLabel;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton3Click(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
procedure StringGrid1Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString);
procedure Edit1Change(Sender: TObject);
procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure StringGrid2KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure Button6Click(Sender: TObject);
procedure Edit7Change(Sender: TObject);
procedure Edit8Change(Sender: TObject);
procedure SpeedButton7Click(Sender: TObject);
procedure SpeedButton8Click(Sender: TObject);
procedure FormShow(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
GB_SgTextScale : double;
GB_SgWidthScale : double;
procedure ReadCommentFile(const FileName : TFileName);
procedure SaveCommentFile(const FileName : TFileName);
function GetDeviceComment(const devStr : string): string;
end;
var
Form4: TForm4;
WordAryNew : TWordAry;
WordAryOld : TWordAry;
BoolAryNew : TBoolAry;
BoolAryOld : TBoolAry;
SpVoice: OleVariant;
TTSFlag : boolean;
const
DEV_RLY_B = $00;
DEV_RLY_W = $19;
DEV_CR_W = $64;
DEV_CR = $0A;
implementation
{$R *.dfm}
uses KvComPlusUnit5;
// 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;
// *****************************
// StringGrid でのキー操作
// *****************************
procedure SgKeyDown(SG: TSTringGrid; var Key: Word; Shift: TShiftState);
var
i, j, k, n : integer;
sl : TStringList;
s, s1 : string;
xflag : boolean;
begin
if Key = VK_DELETE then begin
with SG do begin
if (Selection.Top <> Selection.Bottom) or
(Selection.Left <> Selection.Right) then begin
Key := 0;
for i := Selection.Top to Selection.Bottom do
for j := Selection.Left to Selection.Right do
Cells[j, i] := '';
end;
end;
end;
if ssCtrl in Shift then begin
if true then begin
xflag := (Key = Ord('X')) or (Key = Ord('x'));
if (Key = Ord('C')) or (Key = Ord('c')) or xflag then begin
Key := 0;
Clipboard.AsText := '';
with SG do begin
for i := Selection.Top to Selection.Bottom do begin
for j := Selection.Left to Selection.Right do begin
Clipboard.AsText := Clipboard.AsText + Cells[j, i];
if j < Selection.Right then
Clipboard.AsText := Clipboard.AsText + #9
else Clipboard.AsText :=
Clipboard.AsText + #13#10;
end;
end;
if xflag then begin
for i := Selection.Top to Selection.Bottom do
for j := Selection.Left to Selection.Right do
Cells[j, i] := '';
end;
end;
end
else if (Key = Ord('V')) or (Key = Ord('v')) then begin
//with SG do
// if EditorMode then EditorMode := false;
Key := 0;
with SG do begin
sl := TStringList.Create;
try
s := Clipboard.AsText;
while true do begin
k := Pos(#13#10, s);
if k = 0 then break
else begin
sl.Add(Copy(s, 1, k - 1));
Delete(s, 1, k + 1);
end;
end;
for i := 0 to sl.Count-1 do begin
s := SL[i];
j := 0;
while true do begin
k := Pos(#9, s);
if k = 0 then
s1 := Copy(s, 1, Length(s))
else begin
s1 := Copy(s, 1, k - 1);
Delete(s, 1, k);
end;
Cells[Selection.Left + j, Selection.Top + i] := s1;
n := 1;
while true do begin
if Selection.Bottom < Selection.Top + i + (sl.Count * n) then
break
else
Cells[Selection.Left + j, Selection.Top + i + (sl.Count * n)] := s1;
Inc(n);
end;
if k = 0 then break;
Inc(j);
end;
end;
finally
sl.Free;
end;
end;
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.ReadCommentFile(const FileName : TFileName);
// コメントファイル読み込み
var
sl : TStringList;
cnt : integer;
i, n : integer;
s, s1, s2 :string;
begin
cnt := 0;
sl := TStringList.Create;
try
sl.LoadFromFile(FileName);
for i := 0 to sl.Count - 1 do begin
n := Pos(',', sl[i]);
s1 := Copy(sl[i], 1, n- 1);
s2 := Copy(sl[i], n + 1);
s := Uppercase(Copy(s1, 1, 1));
with StringGrid2 do begin
Inc(cnt);
if RowCount <= cnt then
RowCount := RowCount + 1;
Cells[0, cnt] := s1;
Cells[1, cnt] := s2;
end;
end;
finally
sl.Free;
end;
with StringGrid2 do begin
if cnt > 0 then begin
if cnt < RowCount then
RowCount := cnt;
end
else begin
RowCount := 2;
Cells[0, 1] := '';
Cells[1, 1] := '';
end;
end;
end;
procedure TForm4.SaveCommentFile(const FileName : TFileName);
// コメントファイル保存
var
sl : TStringList;
i : integer;
begin
sl := TStringList.Create;
try
with StringGrid2 do begin
for i := 1 to RowCount -1 do
sl.Add(Cells[0, i] + ',' + Cells[1, i]);
end;
sl.SaveToFile(FileName);
finally
sl.Free;
end;
end;
function TForm4.GetDeviceComment(const devStr : string): string;
// Gdid2 からコメントを取得
var
i, n : integer;
s : string;
begin
result := '';
n := StrToIntDef(devStr, 0);
with StringGrid2 do begin
for i := 1 to RowCount - 1 do begin
s := Cells[0, i];
if (s <> '') and (n = StrToIntDef(s, 0)) then begin
result := Cells[1, i];
break;
end;
end;
end;
end;
procedure TForm4.ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString);
// スマホ Bluetooth から受信
var
cmd, s, res : string;
i, ret, ret1 : integer;
begin
cmd := Trim(string(Data));
Label5.Caption := cmd;
if cmd = 'CPU' then begin
res := ComboBox1.Text;
ApdComPort1.PutString(res + #13#10);
end
else if cmd = 'RD' then begin
res := '';
// 今回値
for i := 0 to 9 do
res := res + WordAryNew[i].ToHexString(4);
// 前回値
for i := 0 to 9 do
res := res + WordAryOld[i].ToHexString(4);
// 先頭番号
res := res + ' ' + Edit1.Text;
// コメント
// 表示中のアドレス
if Edit2.Text <> '' then begin
s := GetDeviceComment(Edit2.Text);
if s <> '' then res := res + ' ' + s;
end;
ApdComPort1.PutString(res + #13#10);
//Caption := res;
// 前回値を更新
WordAryOld := WordAryNew;
end
else if Pos('WR ', cmd) = 1 then begin
s := Copy(cmd, 4);
ret := DBCommManager1.ReadDevice(DEV_RLY_B, s);
DBCommManager1.WriteDevice(DEV_RLY_B, s, abs(ret - 1));
ret1 := DBCommManager1.ReadDevice(DEV_RLY_B, s);
if ret1 = 1 then res := 'ON'
else res := 'OFF';
ApdComPort1.PutString(res + #13#10);
end
else if Pos('CF ', cmd) = 1 then begin
Edit1.Text := Copy(cmd, 4);
ApdComPort1.PutString('OK' + #13#10);
end
else
ApdComPort1.PutString('??' + #13#10);
end;
procedure TForm4.Button1Click(Sender: TObject);
// 手動反転操作
var
ret, idx, ans : integer;
s : string;
begin
try
if DBCommManager1.Active then begin
idx := StrToIntDef(Edit4.Text, 0);
ret := DBCommManager1.ReadDevice(DEV_RLY_B, idx.Tostring);
DBCommManager1.WriteDevice(DEV_RLY_B, idx.Tostring, abs(ret - 1));
ans := DBCommManager1.ReadDevice(DEV_RLY_B, idx.Tostring);
if ret = ans then
Edit4.Font.Color := clYellow
else if ans = 1 then
Edit4.Font.Color := clRed
else
Edit4.Font.Color := clLime;
end;
except
on E: Exception do begin
s := E.ClassName + sLineBreak + E.Message;
Application.MessageBox(PChar(s), '情報', MB_ICONINFORMATION);
end;
end;
end;
procedure TForm4.Button2Click(Sender: TObject);
// 接続
var
plcType : integer;
s : string;
begin
with ApdComPort1 do begin
s := Copy(ComboBox2.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;
ComboBox2.Enabled := False;
end;
except
ShowMessage('ComPort Open Error');
end;
// KV Com +
plcType := -1;
with ComboBox1 do begin
if ItemIndex >= 0 then
plcType := Integer(Items.Objects[ItemIndex]);
end;
if plcType >= 0 then begin
DBCommManager1.Peer := 'USB';
DBCommManager1.PLC := plcType;
try
// 接続
DBCommManager1.Connect;
ComboBox1.Enabled := not DBCommManager1.Active;
Timer1.Enabled := DBCommManager1.Active;
except
on E: Exception do begin
s := E.ClassName + sLineBreak + E.Message;
Application.MessageBox(PChar(s), '情報', MB_ICONINFORMATION);
end;
end;
end;
end;
procedure TForm4.Button3Click(Sender: TObject);
// 切断
begin
// KV Com + 切断
if DBCommManager1.Active then begin
DBCommManager1.Disconnect;
ComboBox1.Enabled := not DBCommManager1.Active;
end;
// Bluetooth SPP 通信(仮想 COM ポート)切断
if ApdComPort1.Open then begin
ApdComPort1.Open := False;
ComboBox2.Enabled := True;
end;
end;
procedure TForm4.Button4Click(Sender: TObject);
// コメントファイル読み込み
begin
OpenTextFileDialog1.InitialDir := ExtractFileDir(Edit5.Text);
if OpenTextFileDialog1.Execute then begin
Edit5.Text := OpenTextFileDialog1.FileName;
ReadCommentFile(Edit5.Text);
end;
end;
procedure TForm4.Button5Click(Sender: TObject);
// コメントファイル保存
var
fname : TFileName;
flag : boolean;
begin
if (StringGrid2.Cells[0, 1] <> '') then begin
SaveTextFileDialog1.InitialDir := ExtractFileDir(Edit5.Text);
if SaveTextFileDialog1.Execute then begin
fname := SaveTextFileDialog1.FileName;
if ExtractFileExt(fname) = '' then fname := fname + '.csv';
flag := True;
if FileExists(fname) then
flag := MessageDlg('すでにファイルが存在します.上書きしますか?', mtInformation, [mbYes, mbNo], 0) = mrYes;
if flag then begin
SaveCommentFile(fname);
Edit5.Text := fname;
end;
end;
end;
end;
procedure TForm4.Button6Click(Sender: TObject);
// コメント用デバイス番号作成
var
ret, st, ed, i, j, cnt : integer;
begin
with Form5 do begin
ret := ShowModal;
if ret <> mrCancel then begin
st := StrToInt(Edit1.Text);
ed := StrToInt(Edit2.Text);
cnt := 0;
with Form4.StringGrid2 do begin
RowCount := ((ed - st) div 100 + 1) * 16 + 1;
for i := st div 100 to ed div 100 do begin
for j := 0 to 15 do begin
Cells[0, cnt + 1] := Format('%.4d', [i * 100 + j]);
Inc(cnt);
end;
end;
end;
if ret = mrYes then begin
st := StrToInt(Edit3.Text);
ed := StrToInt(Edit4.Text);
with Form4.StringGrid2 do begin
RowCount := RowCount + ((ed - st) div 100 + 1) * 16;
for i := st div 100 to ed div 100 do begin
for j := 0 to 15 do begin
Cells[0, cnt + 1] := Format('%.4d', [i * 100 + j]);
Inc(cnt);
end;
end;
end;
end;
end;
end;
end;
procedure TForm4.Edit1Change(Sender: TObject);
// 先頭アドレス変更された
var
i, idx : integer;
begin
idx := StrToIntDef(Edit1.Text, 0);
with StringGrid1 do begin
for i := 0 to 9 do
Cells[0, i +1] := Format('%.4d', [idx + i*100]);
end;
// 初期化
for i := 0 to 9 do WordAryNew[i] := 0;
WordAryOld := WordAryNew;
for i := 0 to 159 do BoolAryNew[i] := False;
BoolAryOld := BoolAryNew;
Edit2.Text := '';
Edit3.Text := '';
end;
procedure TForm4.Edit7Change(Sender: TObject);
// Grid1 文字サイズの調整
begin
GB_SgTextScale := StrToFloatDef(Edit7.Text, 2.0);
StringGrid1.Repaint;
end;
procedure TForm4.Edit8Change(Sender: TObject);
// Grid2 列幅の調整
begin
GB_SgWidthScale := StrToFloatDef(Edit8.Text, 1.0);
StringGrid2.ColWidths[1] := Trunc(250 * GB_SgWidthScale);
StringGrid2.Repaint;
end;
procedure TForm4.FormCreate(Sender: TObject);
// フォーム生成
var
i : integer;
ini : TIniFile;
begin
GB_SgTextScale := 2.0;
GB_SgWidthScale := 2.0;
Edit5.Text := '';
Edit6.Text := '';
Label2.Caption := '';
Label5.Caption := '';
AdSelCom.ShowPortsInUse := False;
for i := 0 to 32 do
if AdSelCom.IsPortAvailable(i) then
ComboBox2.Items.Add (AdPort.ComName(i) + '.');
with ComboBox1 do begin
Items.AddObject('KV-7000', TObject(StrToInt('$020D')));
Items.AddObject('KV-7000 <XYM>', TObject(StrToInt('$120D')));
Items.AddObject('KV-5500/ 5000/ 3000', TObject(StrToInt('$0203')));
Items.AddObject('KV-5500/ 5000/ 3000 <XYM>', TObject(StrToInt('$1203')));
Items.AddObject('KV-1000/ 700', TObject(StrToInt('$0007')));
Items.AddObject('KV-1000 <XYM>', TObject(StrToInt('$1007')));
Items.AddObject('KV Nano [KV-N*]', TObject(StrToInt('$0208')));
Items.AddObject('KV Nano [KV-N*] <XYM>', TObject(StrToInt('$1208')));
end;
with StringGrid1 do begin
Cells[0, 0] := 'R';
for i := 0 to 15 do
Cells[i +1, 0] := i.ToString;
for i := 0 to 9 do
Cells[0, i +1] := Format('%.4d', [i*100]);
end;
with StringGrid2 do begin
ColWidths[1] := Trunc(250 * GB_SgWidthScale);
Cells[0, 0] := ' デバイス';
Cells[1, 0] := ' コメント';
end;
ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
try
i := ini.ReadInteger('COM', 'PortIndex', 0);
if ComboBox2.Items.Count > i then ComboBox2.ItemIndex := i;
i := ini.ReadInteger('KVCOM', 'PLCIndex', 0);
if ComboBox1.Items.Count > i then ComboBox1.ItemIndex := i;
Edit5.Text := ini.ReadString('Comment', 'FileName', '');
Edit7.Text := ini.ReadString('Grid', 'TextScale', GB_SgTextScale.ToString);
Edit8.Text := ini.ReadString('Grid', 'WidthScale', GB_SgWidthScale.ToString);
finally
ini.Free;
end;
TTSFlag := False;
try
SpVoice := CreateOleObject('SAPI.SpVoice');
TTSFlag := True;
CheckBox1.Enabled := True;
CheckBox2.Enabled := True;
except
;
end;
end;
procedure TForm4.FormDestroy(Sender: TObject);
// フォーム破棄
var
ini: TIniFile;
begin
if DBCommManager1.Active then DBCommManager1.Disconnect;
if ApdComPort1.Open then ApdComPort1.Open := False;
ini := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
try
ini.WriteInteger('COM', 'PortIndex', ComboBox2.ItemIndex);
ini.WriteInteger('KVCOM', 'PLCIndex', ComboBox1.ItemIndex);
ini.WriteString('Comment', 'FileName', Edit5.Text);
ini.WriteString('Grid', 'TextScale', Edit7.Text);
ini.WriteString('Grid', 'WidthScale', Edit8.Text);
finally
ini.Free;
end;
end;
procedure TForm4.FormShow(Sender: TObject);
begin
if (Edit5.Text <> '') and FileExists(Edit5.Text) then begin
if MessageDlg(
'前回終了時のコメントファイル' + #13 +
Edit5.Text + #13 + 'を、読み込みますか?',
mtInformation, [mbYes, mbNo], 0) = mrYes then
ReadCommentFile(Edit5.Text)
else if MessageDlg(
'前回終了時のコメントファイル名' + #13 +
Edit5.Text + #13 + 'を、削除しますか?',
mtInformation, [mbYes, mbNo], 0) = mrYes then
Edit5.Text := '';
end;
end;
procedure TForm4.SpeedButton1Click(Sender: TObject);
// 先頭アドレス +10000, +1000
var
idx : integer;
begin
idx := StrToIntDef(Edit1.Text, 0);
if Sender as TSpeedButton = SpeedButton1 then
idx := idx + 1000
else
idx := idx + 10000;
if idx > 59000 then idx := 59000;
Edit1.Text := Format('%.4d', [idx]);
end;
procedure TForm4.SpeedButton3Click(Sender: TObject);
// 先頭アドレス -10000, -1000
var
idx : integer;
begin
idx := StrToIntDef(Edit1.Text, 0);
if Sender as TSpeedButton = SpeedButton3 then
idx := idx - 1000
else
idx := idx - 10000;
if idx < 0 then idx := 0;
Edit1.Text := Format('%.4d', [idx]);
end;
procedure TForm4.SpeedButton7Click(Sender: TObject);
// 反転対象 + 100, +1
var
idx : integer;
dv, md : integer;
begin
idx := StrToIntDef(Edit4.Text, 0);
dv := idx div 100;
md := idx mod 100;
if Sender as TSpeedButton = SpeedButton7 then begin
Inc(dv);
if dv > 599 then dv := 599;
end
else begin
Inc(md);
if md > 15 then begin
Inc(dv);
if dv > 599 then begin
dv := 599;
md := 15;
end
else begin
md := 0;
end;
end;
end;
Edit4.Text := Format('%.4d', [dv * 100 + md]);
Edit4.Font.Color := clWhite;
end;
procedure TForm4.SpeedButton8Click(Sender: TObject);
// 反転対象 -100, -1
var
idx : integer;
dv, md : integer;
begin
idx := StrToIntDef(Edit4.Text, 0);
dv := idx div 100;
md := idx mod 100;
if Sender as TSpeedButton = SpeedButton8 then begin
Dec(dv);
if dv < 0 then dv := 0;
end
else begin
Dec(md);
if (md < 0) and (dv > 0) then begin
md := 15;
Dec(dv);
if dv < 0 then begin
dv := 0;
md := 0;
end;
end;
end;
if dv < 0 then dv := 0;
if md < 0 then md := 0;
Edit4.Text := Format('%.4d', [dv * 100 + md]);
Edit4.Font.Color := clWhite;
end;
procedure TForm4.StringGrid1Click(Sender: TObject);
// 手動反転操作の対象を変える
var
idx : integer;
begin
idx := StrToIntDef(Edit1.Text, 0);
with StringGrid1 do
Edit4.Text := Format('%.4d', [idx + (Row - 1) * 100 + Col -1]);
Edit4.Font.Color := clWhite;
end;
procedure TForm4.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
// Grid 描画
var
ARect : TRect;
s : string;
scale : double;
flag : boolean;
n : integer;
idx : integer;
begin
flag := False;
scale := GB_SgTextScale;
ARect := Rect;
ARect.Top := Rect.Top + 1;
ARect.Bottom := Rect.Bottom - 1;
ARect.Left := Rect.Left + 1;
ARect.Right := Rect.Right - 1;
with StringGrid1 do begin
s := Cells[ACol, ARow];
if (ARow = 0) or (ACol = 0) then begin
if (ARow = 0) and (ACol = 0) then begin
Canvas.Brush.Color := clLime;
Canvas.FillRect(Rect);
Canvas.Font.Height := Trunc(20 * scale);
Canvas.Font.Color := clBlack;
end
else begin
Canvas.Brush.Color := clSilver;
Canvas.FillRect(Rect);
Canvas.Font.Height := Trunc(20 * scale);
Canvas.Font.Color := clGray;
end;
DrawText(Canvas.Handle, PChar(s), Length(s), ARect, DT_CENTER);
end
else begin
if (Edit3.Text = 'ON') or (Edit3.Text = 'OFF') then begin
idx := StrToIntDef(Edit1.Text, 0);
n := StrToIntDef(Edit2.Text, -1);
if n >= idx then begin
n := n - idx;
if (ARow = n div 100 + 1) and (ACol = n mod 100 + 1) then begin
flag := True;
if Edit3.Text = 'ON' then begin
Canvas.Brush.Color := clRed;
Canvas.FillRect(ARect);
Canvas.Font.Height := Trunc(20 * scale);
Canvas.Font.Color := clWhite;
end
else begin
Canvas.Brush.Color := clLime;
Canvas.FillRect(ARect);
Canvas.Font.Height := Trunc(20 * scale);
Canvas.Font.Color := clBlack;
s := Edit2.Text;
end;
end;
end;
end;
if not flag and (s <> '') then begin
Canvas.Brush.Color := RGB($FF, $A5, $00);
Canvas.FillRect(ARect);
Canvas.Font.Height := Trunc(20 * scale);
Canvas.Font.Color := clWhite;
end;
if s <> '' then
DrawText(Canvas.Handle, PChar(s), Length(s), ARect, DT_CENTER);
end;
end;
end;
procedure TForm4.StringGrid2KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
// コメント編集でのキー操作
begin
SgKeyDown(StringGrid2, Key, Shift);
end;
procedure TForm4.Timer1Timer(Sender: TObject);
// ポーリング
var
i, idx : integer;
j: Integer;
s , devNo : string;
SpeechFlag : boolean;
begin
SpeechFlag := False;
Timer1.Enabled := False;
try
if DBCommManager1.Active then begin
idx := StrToIntDef(Edit1.Text, 0);
for i := 0 to 9 do
WordAryNew[i] := DBCommManager1.ReadDevice(DEV_RLY_W, Format('%.4d', [idx + i * 100]));
// 内部データに
for i := 0 to 9 do begin
for j := 0 to 15 do
BoolAryNew[i * 16 + j] := WordAryNew[i] and IntPower(2, j) > 0;
end;
// 比較
for i := 0 to 159 do begin
devNo := Format('%.4d', [idx + (i div 16) * 100 + i mod 16]);
// OFF -> ON
if not BoolAryOld[i] and BoolAryNew[i] then begin
Edit2.Text := devNo;
Edit3.Text := 'ON';
Edit2.Font.Color := clRed;
Edit3.Font.Color := clRed;
Edit6.Text := GetDeviceComment(devNo);
SpeechFlag := True;
end
// ON -> OFF
else if BoolAryOld[i] and not BoolAryNew[i] then begin
Edit2.Text := devNo;
Edit3.Text := 'OFF';
Edit2.Font.Color := clLime;
Edit3.Font.Color := clLime;
Edit6.Text := GetDeviceComment(devNo);
SpeechFlag := True;
end
else begin
with StringGrid1 do begin
if BoolAryNew[i] then
Cells[i mod 16 +1, i div 16 +1] := devNo
else
Cells[i mod 16 +1, i div 16 +1] := '';
end;
end;
end;
// 前回値を更新
BoolAryOld := BoolAryNew;
if Label2.Caption = '' then Label2.Caption := '■'
else Label2.Caption := '';
// テキストスピーチ
if CheckBox1.Checked and SpeechFlag and TTSFlag then begin
Repaint;
StringGrid1.Repaint;
Application.ProcessMessages;
s := NumToSpeechText(Edit2.Text);
if CheckBox2.Checked then s := s + Edit6.Text + #13;
if Edit3.Text = 'ON' then s := s + '、オオン'
else s := s + '、オフ';
SpVoice.Speak(s, SVSFDefault);
end;
end;
Timer1.Enabled := True;
except
on E: Exception do begin
s := E.ClassName + sLineBreak + E.Message;
Application.MessageBox(PChar(s), '情報', MB_ICONINFORMATION);
end;
end;
end;
end.
// ------------------
// Android 側
// ------------------
// Android 側
// 2019/02/17 FX5U にてテスト
// 2019/02/20 PC 名のコンボボックスを追加
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;
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;
Label7: TLabel;
StringColumn10: TStringColumn;
StringColumn11: TStringColumn;
StringColumn12: TStringColumn;
StringColumn13: TStringColumn;
StringColumn14: TStringColumn;
StringColumn15: TStringColumn;
StringColumn16: TStringColumn;
StringColumn17: TStringColumn;
Label11: TLabel;
Rectangle9: TRectangle;
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;
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;
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] = Ord(#10)) 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 159 do BitAryNew[i] := False;
BitAryOld := BitAryNew;
// PC の値を変更
ATimeout := 250;
// デバイス名
AData := TEncoding.ANSI.GetBytes('CF ' + Label8.Text + #13#10);
// 送信
ASocket.SendData(AData);
res := ASocketReceiveData(ASocket, ATimeout);
// アドレス表示部
Rectangle4.Fill.Color := TAlphaColorRec.Black;
// ON/OFF表示部
Rectangle5.Fill.Color := TAlphaColorRec.Black;
end;
end;
procedure TForm4.Button2Click(Sender: TObject);
// 接続先保存
var
IniFile: TMemIniFile;
begin
IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine(
System.IOUtils.TPath.GetDocumentsPath, 'MXC4_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('WR ' + 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;
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 9 do
Cells[0, i] := Format('%.4d', [i*100]);
end;
// 縦画面に固定
Application.FormFactor.Orientations :=
[TFormOrientation.Portrait, TFormOrientation.InvertedPortrait];
// use ..... System.IOUtils;
IniFile := TMemIniFile.Create(System.IOUtils.TPath.Combine(
System.IOUtils.TPath.GetDocumentsPath, 'MXC4_IO.ini'), TEncoding.UTF8);
with IniFile do begin
try
BtDeviceHead := ReadString('Target', 'PCName', '');
finally
Free;
end;
end;
// TTS
InitTTS;
// 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 := Label8.Text.ToInteger();
if Sender as TRectangle = Rectangle10 then
idx := idx - 1000
else
idx := idx - 10000;
if idx < 0 then idx := 0;
Label8.Text := Format('%.4d', [idx]);
with StringGrid1 do begin
for i := 0 to 9 do
Cells[0, i]:= Format('%.4d', [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 := Label3.Text.ToInteger();
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 := Format('%.4d', [dv * 100 + md]);
TextSettings.FontColor := TAlphaColorRec.Orange;
end;
idx := Label8.Text.ToInteger();
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 := Label3.Text.ToInteger;
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 := Format('%.4d', [dv * 100 + md]);
TextSettings.FontColor := TAlphaColorRec.Orange;
end;
idx := Label8.Text.ToInteger();
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 := Label8.Text.ToInteger();
if Sender as TRectangle = Rectangle7 then
idx := idx + 10000
else
idx := idx + 1000;
if idx > 59000 then idx := 59000;
Label8.Text := Format('%.4d', [idx]);
with StringGrid1 do begin
for i := 0 to 9 do
Cells[0, i]:= Format('%.4d', [idx + i * 100]);
Row := 0;
Col := 1;
end;
Label1.Text := '';
Label2.Text := '';
Label11.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 := Format('%.4d', [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 := Label8.Text.ToInteger();
// 現在のアドレス
n := StrToIntDef(Label1.Text, -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 := Label8.Text.ToInteger();
if Flag then begin
// デバイス一括読み出しコマンド
AData := TEncoding.ANSI.GetBytes('RD' + #13#10);
// 送信
ASocket.SendData(AData);
// 受信
res := ASocketReceiveData(ASocket, ATimeout);
flag := res <> '';
// データ格納
if res.Length >= 80 then begin // 10 x 4 = 40, 40 x 2 = 80
for i := 0 to 9 do begin
s := Copy(res, i * 4 + 1, 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 + 1 + 40, 4);
n := StrToIntDef('$' + s, 0);
for j := 0 to 15 do
BitAryOld[i * 16 + j] := n and IntPower(2, j) > 0;
end;
s := Copy(res, 82); // スペース1個ある
if s <> '' then begin
// デバイス番号
n := Pos(' ', s);
if n = 0 then begin
idx := StrToIntDef(s, 0);
Label11.Text := ''; // コメント
end
else begin
// 先頭デバイス番号(PC からの応答は 10 進表記)
idx := StrToIntDef(Copy(s, 1, n - 1), 0);
// コメント
Label11.Text := Copy(s, n + 1);
end;
if (stIndex <> idx) then begin
stIndex := idx;
// アドレス番号を変える
Label8.Text := stIndex.ToString;
with StringGrid1 do begin
for i := 0 to 9 do
Cells[0, i] := Format('%.4d', [stIndex + i * 100]);
Row := 0;
Col := 1;
end;
// 内部データを初期化
for i := 0 to 159 do BitAryNew [i] := False;
BitAryOld := BitAryNew;
// デバイス ON/OFF の表示を初期化
Label1.Text := '';
Label2.Text := '';
Rectangle4.Fill.Color := TAlphaColorRec.Black;
Rectangle5.Fill.Color := TAlphaColorRec.Black;
// 反転デバイス番号を更新
Label3.Text := Format('%.4d', [stIndex]);
end;
end;
end;
end;
// 表示
with StringGrid1 do begin
for i := 0 to 159 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;
// 比較
// 内部データ数 = 128, FX は先頭 64 データのみ表示される
for i := 0 to 159 do begin
//idx := i + stIndex;
if BitAryNew[i] and not BitAryOld[i] then begin
Rectangle4.Fill.Color := TAlphaColorRec.Red;
with Label1 do begin
Text := Format('%.4d', [(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 := Format('%.4d', [(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 then begin
s := NumToSpeechText(Label1.Text);
if Switch1.IsChecked then
s := s + '。' + Label11.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.