REX-BT60 を Delphi / Bluetooth で、KEYENCE KV 上位リンク通信 を試してみた (2017/ 1/ 5)
2017/ 1/15 オプション - Bluetooth を追加
2017/ 1/11 一部追加、スクリーンショットを更新
2017/ 1/ 8 一部更新
■ラトックシステムの「Bluetooth - RS-232C 変換アダプター」 REX-BT60 を、Delphi 10.1 Berlin から使ってみました。
Windows では、Bluetooth リンク経由の COM ポートを使うと Bluetooth を気にすることなく、通常のシリアル通信のプログラムで動きますが、
Android での使用を考えて、Bluetooth 通信を試してみました。
※ iOS(iPhone, iPad)も SPP (Serial Port Profile) を搭載していますが、OS にてその機能が特殊な方法でしか利用できないようになっているため、通信できません。
※ Bluetoopth LE (Low Energy) との、互換性はありません。
REX-BT60 は、+5V のACアダプターが必要ですが、D-Sub9pin コネクタのの 9 番ピンに供給することも可能です。(DIP スイッチの4 をON)
KEYENCE KV nano / KV-3000 の内蔵シリアルポート(モジュラーコネクタ 6 ピン)の 1 、2 番ピンに、+5V が出力されています。
これらを接続すると、ACアダプターは不要になります。
デバイスマネージャーで 「Bluetooth」 デバイスを見ると、「RN42-****」 (後ろ4桁は、製品個体により異なる)があります。
これが、通信先になる REX-BT60 です。

[ 大まかな手順 ]
ペアリングされたデバイス一覧の中から、「RN42-」で始まるデバイスを探しだします。
SPP(Serial Port Profile) による通信のUUID : 00001101-0000-1000-8000-00805F9B34FB
を指定し、ソケットを生成、接続します。
あとは、KV nanoに対して、通信開始コマンド 'CR' を送信。レスポンスを受信。操作するコマンドを送信-受信。
最後に、通信終了コマンド 'CQ' を送信して、KV nano との通信を終了します。
※Windows で Bluetooth リンク経由の COM ポートを使う場合は、こちら↓

■スクリーンショット
設計時のフォーム
Android でも使えるように FMX を使っていますが、Windows のフォームでも、サンプルコードは動きます

実行時の画面
REX-WF60 (Wi-Fi - RS232C) と違って、接続までに若干時間 (1, 2 秒程度) がかかります。
実用では、REX-BT60, RS-232C を占有して良い場合は、
フォーム生成時に、別スレッドで、通信開始(KV へ 'CR' 送信)まで行い、終了時に通信終了(KV へ 'CQ' 送信)する。
つまり、接続しっぱなしにすると良いと思います。

KV STUDIO のトレースモニタ画面

Android では、マニフェストファイル (AndroidManifest.xml) でパーミッションの設定を忘れるとエラー↓になります。

メニュー - [プロジェクト] - [オプション] - 「使用する権限」で 「Bluetooth」 を True にすると、AndroidManifest.xml に反映されます。
マニフェストファイルの編集は、不要です。

Delphi の場合、AndroidManifest.xml は、その元になる AndroidManifest.template.xml から自動生成されるので、
編集しても書き換えられてしまいます。手動で追加する場合は、AndroidManifest.template.xml を編集します。
(AndroidManifest.template.xml は、C:\Users\<ユーザー名>\Documents\Embarcadero\Studio\Projects フォルダにあります。)
[オプション] - 「使用する権限」 で、Bluetooth を True にした時、
<!-- This is the platform API where NativeActivity was introduced.
-->
の下に、「Bluetooth = True」の場合、
<uses-permission android:name="android.permission.BLUETOOTH"
/>
「Bluetooth 管理 = True」の場合
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
/>
が、自動で追加されるようです。
■ほぼ実用版 Android (Nexus7) のスクリーンショット
対象は、KV nano ですが、KV-3000 でも動くと思います。
・常時、デバイス 16 点の値取得、表示形式を変えての表示 (100msec 周期)
・デバイスコメントの取得(自動/手動)
・ビットデバイスの ON/OFF 反転
・ワードデバイスの値変更
・再接続(手動)
・・・ が、出来ます。デザインは別にして、デバッグ用として何とか使えるレベルです。


■サンプルコード (Android では、パーミッションの設定を忘れずに!)
// 2017/ 1/ 9 ASocketReceiveData() コメント取得時、2バイト文字が分断されるのを対策
// 2017/ 1/ 8 2バイト文字が取得できないため、TEncoding.UTF8.GetString() を、TEncoding.ANSI.GetString() に変更
unit KV_BT60_BlueToothUnit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs,
System.Bluetooth, FMX.Controls.Presentation, FMX.StdCtrls,
System.Bluetooth.Components, FMX.ScrollBox, FMX.Memo;
type
TForm1 = class(TForm)
Bluetooth1: TBluetooth;
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
Panel1: TPanel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ private 宣言 }
public
{ public 宣言 }
end;
const
// SPP(Serial Port Profile) による通信のUUID
ServiceUUID = '{00001101-0000-1000-8000-00805F9B34FB}';
var
Form1: TForm1;
implementation
{$R *.fmx}
{$R *.LgXhdpiTb.fmx ANDROID}
{$R *.Windows.fmx MSWINDOWS}
{$R *.LgXhdpiPh.fmx ANDROID}
// 2017/ 1/ 8 更新
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 TForm1.Button1Click(Sender: TObject);
var
ABluetoothManager : TBluetoothManager;
APairedDevices : TBluetoothDeviceList;
AData : TBytes;
ASocket : TBluetoothSocket;
ADevice : TBluetoothDevice;
AServices : TBluetoothServiceList;
i,idx : integer;
res : string;
//UUID : TBluetoothUUID;
//os: TOSVersion;
ATimeout: Cardinal;
Ticks : Cardinal;
begin
ATimeout := 1000;
Ticks := TThread.GetTickCount;
ABluetoothManager := Bluetooth1.CurrentManager.Current;
try
if ABluetoothManager.ConnectionState = TBluetoothConnectionState.Connected then begin
// 'IZAWA-PC-201411', 'Nexus 7' ... PC名
Memo1.Lines.Add('CurrentAdapterName : ' + ABluetoothManager.CurrentAdapter.AdapterName);
// 過去にペアリングされたデバイスの一覧から、REX-BT60 を探す
APairedDevices := ABluetoothManager.GetPairedDevices;
Memo1.Lines.Add('PairedDevicesCount : '+IntToStr(APairedDevices.Count));
if APairedDevices.Count > 0 then begin
idx := -1;
for i := 0 to APairedDevices.Count -1 do begin
// 'RN42-****' が、REX-BT60
Memo1.Lines.Add('PairedDevicesName : ' + APairedDevices[i].DeviceName);
if Pos('RN42-', APairedDevices[i].DeviceName) = 1 then begin
idx := i;
break;
end;
end;
if idx >= 0 then begin
ADevice := APairedDevices[idx];
if ADevice <> nil then begin
AServices := Device.GetServices;
if AServices.Count > 0 then begin
ASocket := ADevice.CreateClientSocket(StringToGUID(ServiceUUID), False);
if ASocket <> nil then begin
try
// 接続
ASocket.Connect;
if ASocket.Connected then begin
Memo1.Lines.Add('SocketConnect : '+ IntToStr(TThread.GetTickCount - Ticks));
Ticks := TThread.GetTickCount;
// 通信開始コマンドを送信
AData := TEncoding.ANSI.GetBytes('CR'+#13);
// 送信
ASocket.SendData(AData);
// 受信
res := ASocketReceiveData(ASocket, ATimeout);
Memo1.Lines.Add('CR -> ' + res);
Memo1.Lines.Add('Command : '+ IntToStr(TThread.GetTickCount - Ticks));
// 出力リレー R500 を ON
AData := TEncoding.ANSI.GetBytes('WR R500 1'+#13);
ASocket.SendData(AData);
res := ASocketReceiveData(ASocket, ATimeout);
Memo1.Lines.Add('WR R500 1 -> ' + res);
Sleep(500);
// 出力リレー R500 を OFF
AData := TEncoding.ANSI.GetBytes('WR R500 0'+#13);
ASocket.SendData(AData);
res := ASocketReceiveData(ASocket, ATimeout);
Memo1.Lines.Add('WR R500 0 -> ' + res);
// 通信終了コマンドを送信
AData := TEncoding.ANSI.GetBytes('CQ'+#13);
ASocket.SendData(AData);
res := ASocketReceiveData(ASocket, ATimeout);
Memo1.Lines.Add('CQ -> ' + res);
end;
finally
ASocket.Close;
ASocket.Free;
end;
end;
end;
end;
end;
end;
end;
Memo1.GoToTextEnd;
except
on E : Exception do
ShowMessage(E.Message);
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Close;
end;
end.