Delphi 10.1 Berlin で、iOS / Android Bluetooth LE (BLE) Serial 通信 (2018/08/09)
・2018/08/09 試作品のイメージ、配線資料を追加しました。
・2017/04/05 iOS のサンプルコードを追加しました。
浅草ギ研のBLE モジュール BLE-Serial3 を使って、Android / iPhone からシリアル通信を試してみました。
Bluetooth Classic と違って、iOS からでも使用できます。
BLE-Serial3 のボーレートは、9600 bps 固定です。ロット(100個)単位であれば、変更可能だそうです。
ボーレート等変更が必要な場合は、マイクロテクニカ BLE-STICK が使えますが、設定が必要です。
RS232C へのレベル変換は、当初 マイコンキットドットコム の MK-205 を使用していましたが、コンパクトな秋月電子通商の AE-ADM3202 に変更しました。
試作品↓(左: BLE-Serial3、右: AE-ADM3202 基板の裏に樹脂板を入れてグルーガンで仮固定。その後、熱収縮チューブ)
配線等の資料 (PDF) は、こちら。
BLE-Serial3、BLE-STICK はアマゾンでも購入可能です。
PLC と通信する場合は、通信設定、データ数の制限から、上位リンクではなく、無手順通信を使うしかないようです。
※送信、受信は、1 パケット 20 バイト以内と、制限があります。
Delphi Android で、多くのデータを送信する場合は、BeginReliableWrite および ExecuteReliableWrite
を使うようですが、
うまく動きませんでした。
http://docwiki.embarcadero.com/Libraries/Seattle/ja/System.Bluetooth.Components.TBluetoothLE.BeginReliableWrite
送信は、15 バイトごとに区切って、20 msec の遅延で繰り返すと、何とか送信できます。
・KEYENCE KV へ送信してみました
制御する場合は、無手順通信を使用します。
■ iOS (2017/04/05)
unit Unit1; 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) BluetoothLE1: TBluetoothLE; Button1: TButton; Memo1: TMemo; Button2: TButton; Button3: TButton; procedure Button1Click(Sender: TObject); procedure BluetoothLE1ServicesDiscovered(const Sender: TObject; const AServiceList: TBluetoothGattServiceList); procedure BluetoothLE1DiscoverLEDevice(const Sender: TObject; const ADevice: TBluetoothLEDevice; Rssi: Integer; const ScanResponse: TScanResponse); procedure BluetoothLE1EndDiscoverDevices(const Sender: TObject; const ADeviceList: TBluetoothLEDeviceList); procedure BluetoothLE1Connect(Sender: TObject); procedure BluetoothLE1CharacteristicRead(const Sender: TObject; const ACharacteristic: TBluetoothGattCharacteristic; AGattStatus: TBluetoothGattStatus); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { private 宣言 } fBleSerialDevice : TBluetoothLEDevice; fBleSerialService : TBluetoothGattService; public { public 宣言 } end; var Form1: TForm1; const // BLESerial サービス UUID BleSerialService: TBluetoothUUID = '{FEED0001-C497-4476-A7ED-727DE7648AB1}'; // BLESerial 受信 UUID (Notify) BleSerialRx : TBluetoothUUID = '{FEEDAA03-C497-4476-A7ED-727DE7648AB1}'; // BLESerial 送信 UUID (write without response) BleSerialTx: TBluetoothUUID = '{FEEDAA02-C497-4476-A7ED-727DE7648AB1}'; implementation {$R *.fmx} // 受信 procedure TForm1.BluetoothLE1CharacteristicRead(const Sender: TObject; const ACharacteristic: TBluetoothGattCharacteristic; AGattStatus: TBluetoothGattStatus); var s : string; begin // 受信した文字列 s := ACharacteristic.GetValueAsString(0, true); Memo1.Lines.Add('read:'+s); end; procedure TForm1.BluetoothLE1Connect(Sender: TObject); begin Memo1.Lines.Add('Connect'); end; // BLESerial を発見 procedure TForm1.BluetoothLE1DiscoverLEDevice(const Sender: TObject; const ADevice: TBluetoothLEDevice; Rssi: Integer; const ScanResponse: TScanResponse); begin Memo1.Lines.Add(ADevice.DeviceName); if Pos('BLESerial_', ADevice.DeviceName) > 0 then begin // デバイスを保持 fBleSerialDevice := ADevice; // 検索を終了 BluetoothLE1.CancelDiscovery; end; end; // BLE デバイスの検索終了 procedure TForm1.BluetoothLE1EndDiscoverDevices(const Sender: TObject; const ADeviceList: TBluetoothLEDeviceList); begin if fBleSerialDevice <> nil then begin if not fBleSerialDevice.DiscoverServices then begin ShowMessage('サービスは使用できません.'); end; end else begin ShowMessage('BLESerial が見つかりません.'); end; end; // サービスを発見 procedure TForm1.BluetoothLE1ServicesDiscovered(const Sender: TObject; const AServiceList: TBluetoothGattServiceList); var i :integer; RxCharact: TBluetoothGattCharacteristic; begin if AServiceList.Count > 0 then begin // サービスを探す for i := 0 to AServiceList.Count -1 do begin // サービスを保持 fBleSerialService := AServiceList[i]; break; end; if fBleSerialService <> nil then begin // キャラクリスティックを取得 for i := 0 to fBleSerialService.Characteristics.Count -1 do begin // SerialRx の UUID if fBleSerialService.Characteristics[i].UUID = BleSerialRx then begin RxCharact := fBleSerialService.Characteristics[i]; break; end; end; if RxCharact <> nil then begin // Notify の受信要求 fBleSerialDevice.SetCharacteristicNotification(RxCharact, true); end; end; end; end; // BLESerial を探す procedure TForm1.Button1Click(Sender: TObject); begin fBleSerialDevice := nil; fBleSerialService := nil; // BLESerial の GUID を指定して検索 BluetoothLE1.DiscoverDevices(1000, [BleSerialService]); end; // 送信 procedure TForm1.Button2Click(Sender: TObject); var s : string; TxCharact : TBluetoothGattCharacteristic; begin if (fBleSerialDevice <> nil) and fBleSerialDevice.IsConnected then begin TxCharact := fBleSerialService.GetCharacteristic(BleSerialTx); if TxCharact <> nil then begin // 送信文字列 s := 'ABCDEFGH'; TxCharact.SetValueAsString(s, true); if fBleSerialDevice.WriteCharacteristic(TxCharact) then begin ShowMessage('送信しました : ' + s); end; end; end else begin ShowMessage('接続されていません.'); end; end; // 切断 procedure TForm1.Button3Click(Sender: TObject); begin if (fBleSerialDevice <> nil) and fBleSerialDevice.IsConnected then begin if fBleSerialDevice.Disconnect then begin ShowMessage('切断しました.'); fBleSerialDevice := nil; fBleSerialService := nil; end; end else begin ShowMessage('接続されていません.'); end; end; end.
■ Android (2017/03/10)
unit AndBLETestUnit1; 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.ScrollBox, FMX.Memo, FMX.Controls.Presentation, FMX.StdCtrls, System.Bluetooth.Components, FMX.Edit; type TForm1 = class(TForm) BluetoothLE1: TBluetoothLE; Memo1: TMemo; Button3: TButton; Label1: TLabel; Label2: TLabel; Label3: TLabel; Edit1: TEdit; Timer1: TTimer; procedure BluetoothLE1Connect(Sender: TObject); procedure BluetoothLE1CharacteristicRead(const Sender: TObject; const ACharacteristic: TBluetoothGattCharacteristic; AGattStatus: TBluetoothGattStatus); procedure BluetoothLE1EndDiscoverServices(const Sender: TObject; const AServiceList: TBluetoothGattServiceList); procedure BluetoothLE1CharacteristicWrite(const Sender: TObject; const ACharacteristic: TBluetoothGattCharacteristic; AGattStatus: TBluetoothGattStatus); procedure BluetoothLE1DescriptorWrite(const Sender: TObject; const ADescriptor: TBluetoothGattDescriptor; AGattStatus: TBluetoothGattStatus); procedure Button3Click(Sender: TObject); procedure BluetoothLE1EndDiscoverDevices(const Sender: TObject; const ADeviceList: TBluetoothLEDeviceList); procedure Timer1Timer(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private { private 宣言 } public { public 宣言 } end; var Form1: TForm1; GBleSerialService : TBluetoothGattService; GBleSerialDevice : TBluetoothLEDevice; GTmCount : integer; GSuccessCount : integer; const // BLESerial サービス UUID UUID_BLESERIAL_SERVICE = '{FEED0001-C497-4476-A7ED-727DE7648AB1}'; // BLESerial 受信 UUID (Notify) UUID_BLESERIAL_RX = '{FEEDAA03-C497-4476-A7ED-727DE7648AB1}'; // BLESerial 送信 UUID (write without response) UUID_BLESERIAL_TX = '{FEEDAA02-C497-4476-A7ED-727DE7648AB1}'; // キャラクタリスティック設定 UUID // Notification の UUID CLIENT_CHARACTERRISTIC_CONFIG = '{00002902-0000-1000-8000-00805f9b34fb}'; implementation {$R *.fmx} // 受信 procedure TForm1.BluetoothLE1CharacteristicRead(const Sender: TObject; const ACharacteristic: TBluetoothGattCharacteristic; AGattStatus: TBluetoothGattStatus); var s : string; begin // 受信した文字列 s := ACharacteristic.GetValueAsString(0,True); Memo1.Lines.Add('Read:' + s); end; // 送信後 procedure TForm1.BluetoothLE1CharacteristicWrite(const Sender: TObject; const ACharacteristic: TBluetoothGattCharacteristic; AGattStatus: TBluetoothGattStatus); var s : string; begin // 送信した文字列 s := ACharacteristic.GetValueAsString(0, True); Memo1.Lines.Add('Write:' + s); end; // 接続 procedure TForm1.BluetoothLE1Connect(Sender: TObject); begin Timer1.Enabled := False; Label3.Text := 'Connect ... '; Application.ProcessMessages; end; // Notification の有効化 procedure TForm1.BluetoothLE1DescriptorWrite(const Sender: TObject; const ADescriptor: TBluetoothGattDescriptor; AGattStatus: TBluetoothGattStatus); begin if (TBluetoothGattStatus.Success = AGattStatus) then begin Inc(GSuccessCount); if GSuccessCount >= 2 then begin Label3.Text := 'Connect Success'; Application.ProcessMessages; // ShowMessage('操作可能です'); end; end; end; // BLE デバイスの検索終了 procedure TForm1.BluetoothLE1EndDiscoverDevices(const Sender: TObject; const ADeviceList: TBluetoothLEDeviceList); var i : integer; begin GBleSerialDevice := nil; if ADeviceList.Count > 0 then begin for i := 0 to Pred(ADeviceList.Count) do begin if Pos('BLESerial_', ADeviceList[i].DeviceName) > 0 then begin GBleSerialDevice := ADeviceList[i]; Label2.Text := ADeviceList[i].DeviceName + ' (' + ADeviceList[i].Address + ')'; Application.ProcessMessages; Break; end; end; end; if GBleSerialDevice <> nil then begin // サービスを検索 GBleSerialDevice.DiscoverServices; end; end; procedure TForm1.BluetoothLE1EndDiscoverServices(const Sender: TObject; const AServiceList: TBluetoothGattServiceList); var i: integer; AService : TBluetoothGattService; ACharact: TBluetoothGattCharacteristic; ADescriptor : TBluetoothGattDescriptor; AData :TBytes; begin // サービスの一覧取得終了 if AServiceList.Count > 0 then begin for i := 0 to Pred(AServiceList.Count) do begin if AServiceList[i].UUID.ToString = UUID_BLESERIAL_SERVICE then begin // シリアルサービスを取得 AService := AserviceList[i]; GBleSerialService := AserviceList[i]; Break; end; end; end; if AService <> nil then begin // RX にNotificate をセット // キャラクタリスティックを取得 ACharact := AService.GetCharacteristic(StringToGUID(UUID_BLESERIAL_RX)); if ACharact = nil then begin // 再取得 for i := 0 to Pred(AService.Characteristics.Count) do begin if AService.Characteristics[i].UUID.ToString = UUID_BLESERIAL_RX then begin ACharact := AService.Characteristics[i]; Break; end; end; end; if ACharact <> nil then begin // Notify の受信要求(iOS では、不要らしい) GBleSerialDevice.SetCharacteristicNotification(ACharact, True); // GATT に RX の Notify を設定 ADescriptor := ACharact.GetDescriptor(StringToGUID(CLIENT_CHARACTERRISTIC_CONFIG)); SetLength(AData, 2); AData[0] := $01; AData[1] := $00; // Notification の有効化 ADescriptor.SetValue(AData); GBleSerialDevice.WriteDescriptor(ADescriptor); // DescriptorWrite イベントが2回無いと通信出来ない // 実際に有効になるまで、時間がかかる Timer1.Enabled := False; end; end; end; // 文字列送信 procedure TForm1.Button3Click(Sender: TObject); var ACharact: TBluetoothGattCharacteristic; begin if (GBleSerialDevice <> nil) and GBleSerialDevice.IsConnected then begin ACharact := GBleSerialService.GetCharacteristic(StringToGUID(UUID_BLESERIAL_TX)); if ACharact <> nil then begin ACharact.SetValueAsString(Edit1.Text, True); GBleSerialDevice.WriteCharacteristic(ACharact); end; end; end; // 終了処理 procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin if (GBleSerialDevice <> nil) and GBleSerialDevice.IsConnected then begin GBleSerialDevice.Disconnect; GBleSerialDevice := nil; GBleSerialService := nil; end; end; procedure TForm1.FormCreate(Sender: TObject); begin Label1.Text := ''; Label2.Text := ''; Label3.Text := ''; Memo1.Lines.Clear; GSuccessCount := 0; BluetoothLE1.Enabled := True; // 端末の名称 Label1.Text := BluetoothLE1.GetCurrentAdapter.AdapterName; // BLE デバイスを検索 BluetoothLE1.DiscoverDevices(1000); GTmCount := 0; Timer1.Enabled := True; end; procedure TForm1.Timer1Timer(Sender: TObject); begin Inc(GTmCount); Label3.Text := IntToStr(GTmCount * 10) + ' msec'; if GTmCount > 200 then begin Timer1.Enabled := False; ShowMessage('接続失敗'); end; end; end.