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.