ロトロニック (Rotronic) 社 温湿度センサー HC2 を Android / Bluetooth で読む (2017/04/19)
ロトロニック社の温湿度センサー HC2 の UART 信号を RS232C にレベル変換して、ラトックシステム社の RS232C-Bluetooth
変換器を使って、
アンドロイド端末に表示させています。
2018/03/03 追記
ラトックシステム社の REX-BT60 を使っていましたが、SENA社 Parani - SD1000 でも使えました。
・ボーレートの設定が DIP スイッチで可能。
・アンテナが付いている。
市販の 2.4GHz 用のアンテナも接続可能(メス-メスプラグが必要)ですが、アンテナケーブルでかなり減衰するので、
直結タイプがお勧めです。リンクはこちら。
・Bluetooth Class 1 (10mW) なので、より飛びそう 。実測では、見通し50m程度。REX-BT60 は、Class
2 (2.5mW) 10m程度。
SD1000 用の電源が 9 番ピンに出力されない(入力は可能)ため、
HC2 用の DC3.3~5V(推奨値は 3.3V。プローブにより 3.3V 厳守)を RTS(7番ピン)に接続してみたところ動きました。
実際の電圧は 4.7V 程度になりますが、問題なく動いています。
(通常電流 <4.5mA at 3.3V / 7.5mA at DC5V。スタートアップ時の最大電流<50mA )
HC2 センサーは、単体で完結した高精度の温湿度センサーで、アナログ出力のほか、シリアル( UART )出力があります。
シリアル信号を、USB(仮想COMポート)、RS485 で通信するアプリケーションが、メーカーにて用意されていますが、
HC2 単体ではなく、HC2 を接続した表示器、変換器経由になります。
ここでは、センサー単体のシリアル信号を、RS232C のレベルに変換して、そのままパソコン、スマホに表示しています。
※秋月電子などの Bluetooth モジュールを使うと、RS232C レベルに変換することなく、そのまま接続できそうな気がします。
HC2 にコマンドを送ると、湿度、温度、露点温度(計算値)、単位、トレンド情報等が、1行の文字列として返信されます。
返信文字列を、";" 区切りで読むと、それぞれの情報が、文字列として取得できます。
アプリ側では、それを表示させるだけなので、とても簡単なものになっています。
Bluetooth 2.0 (クラシック) を使っているので、iOS では使用できませんが、
ラトックシステム社の RS232C - WiFi 変換器を使うと、iOS でも使用できる同様のアプリも簡単に作成可能です。
※Bluetooth LE は、iOS / Android とも使用可能ですが、通信データが20バイト以内と制限が有り、そのままでは、受信できません。
■アンドロイド端末のスクリーンショット
■Delphi の開発画面
// unit BtHC2UartUnit1; 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.Edit, FMX.StdCtrls, FMX.Controls.Presentation, FMX.Objects; type TBtThread = class(TThread) private { Private 宣言 } procedure BtOpen; protected procedure Execute; override; public constructor Create; virtual; end; TForm1 = class(TForm) Label1: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Timer1: TTimer; Rectangle1: TRectangle; Rectangle2: TRectangle; Rectangle3: TRectangle; Label2: TLabel; Label6: TLabel; Label7: TLabel; Label8: TLabel; Label9: TLabel; Label10: TLabel; Label11: TLabel; Label12: TLabel; Label13: TLabel; Label14: TLabel; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Timer1Timer(Sender: TObject); private { private 宣言 } public { public 宣言 } end; var Form1: TForm1; GThdMode : integer; GCmdMode : integer; GSocket:TBluetoothSocket; ThBt : TBtThread; OpenNGcnt : integer; OpenMsecCnt : integer; const BTDeviceHead = 'RN42-'; thdTHSTART = 1000; thdTHTERM = 2000; cmdSCCREATE = 200; cmdSCCONNECT = 201; cmdSCNG = 202; implementation {$R *.fmx} // 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 Form1.Label1.Text := '[' + ABluetoothManager.CurrentAdapter.AdapterName + ']' end); // 過去にペアリングされたデバイスの一覧から、REX-BT60 を探す APairedDevices := ABluetoothManager.GetPairedDevices; if APairedDevices.Count > 0 then begin idx := -1; for i := 0 to APairedDevices.Count -1 do begin // 'RN42-****' が、REX-BT60 if Pos(BTDeviceHead, APairedDevices[i].DeviceName) = 1 then begin Synchronize(procedure() begin Form1.Label1.Text := Form1.Label1.Text + ' - ' + '[' + APairedDevices[i].DeviceName + ']'; end); idx := i; break; end; end; if idx >= 0 then begin ADevice := APairedDevices[idx]; if ADevice <> nil then begin GSocket := ADevice.CreateClientSocket( StringToGUID('{00001101-0000-1000-8000-00805F9B34FB}'), False); if GSocket <> nil then begin GCMDMODE := cmdSCCREATE; // 接続 GSocket.Connect; if GSocket.Connected then begin GCMDMODE := cmdSCCONNECT; end; end; end; end; end; end; except on E : Exception do begin GCMDMODE := cmdSCNG; end; end; finally // 明示的にスレッドを終了(破棄される) // スレッド実行中にアプリを終了した時エラーになるため Terminate; ThBt := nil; GThdMODE := thdTHTERM; end; end; constructor TBtThread.Create; begin // スレッドを生成、直ちに実行 inherited Create(False); // スレッド終了時、スレッドオブジェクトを破棄 FreeOnTerminate := True; end; procedure TBtThread.Execute; begin BtOpen; end; procedure TForm1.FormCreate(Sender: TObject); begin ThBt := TBtThread.Create; Label1.Text := ''; Label3.Text := ''; Label4.Text := ''; Label5.Text := ''; Label8.Text := ''; Label9.Text := ''; Label10.Text := ''; Label11.Text := ''; Label12.Text := ''; Label13.Text := ''; Label14.Text := ''; end; procedure TForm1.FormDestroy(Sender: TObject); begin // ソケット破棄 if GSocket <> nil then begin if GSocket.Connected then GSocket.Close; FreeAndNil(GSocket); end; end; procedure TForm1.Timer1Timer(Sender: TObject); var TxData, RxData : TBytes; res: string; sl : TStrings; hu ,tm, dp : double; s : string; begin if not ((GCMDMODE = cmdSCCONNECT) and GSocket.Connected) then begin Inc(OpenMsecCnt); Label8.Text := IntToStr(OpenMsecCnt*10) + 'msec'; if GCMDMODE = cmdSCNG then begin Inc(OpenNgCnt); if OpenNgCnt > 4 then begin Timer1.Enabled := False; ShowMessage('接続できません.'); end else begin // 再接続 Sleep(500); OpenMsecCnt := 0; GCMDMODE := 0; GTHDMODE := 0; ThBt := TBtThread.Create; end; end; end; if (GCMDMODE = cmdSCCONNECT) and GSocket.Connected then begin Timer1.Interval := 500; try TxData := TEncoding.ANSI.GetBytes('{ 99RDD}' + #13); GSocket.SendData(TxData); // データがすべて揃うまで、待つ // 300msec 必要 Sleep(300); RxData := GSocket.ReceiveData; res := TEncoding.ANSI.GetString(RxData); sl := TStringList.Create; try res := StringReplace(res, ' ', '_', [rfReplaceAll]); sl.Delimiter := ';'; sl.DelimitedText := res; if sl.Count > 10 then begin // 相対湿度 hu := StrToFloatDef(StringReplace(sl[1], '_', '', [rfReplaceAll]), 0); s := Format('%.1f', [hu]); if Label3.Text <> s then Label3.Text := s; // 温度 tm := StrToFloatDef(StringReplace(sl[5], '_', '', [rfReplaceAll]), -20); s := Format('%.1f', [tm]); if Label4.Text <> s then Label4.Text := s; // 露点温度 dp := StrToFloatDef(StringReplace(sl[10], '_', '', [rfReplaceAll]), -40); s := Format('%.1f', [dp]); if Label5.Text <> s then Label5.Text := s; if Label8.Text <> '■' then Label8.Text := '■' else Label8.Text := ''; if sl[4] = '+' then begin Label9.Text := '▲'; Label12.Text := ''; end else if sl[4] = '-' then begin Label9.Text := ''; Label12.Text := '▼'; end else begin Label9.Text := '▲'; Label12.Text := '▼'; end; if sl[8] = '+' then begin Label10.Text := '▲'; Label13.Text := ''; end else if sl[8] = '-' then begin Label10.Text := ''; Label13.Text := '▼'; end else begin Label10.Text := '▲'; Label13.Text := '▼'; end; if sl[13] = '+' then begin Label11.Text := '▲'; Label14.Text := ''; end else if sl[13] = '-' then begin Label11.Text := ''; Label14.Text := '▼'; end else begin Label11.Text := '▲'; Label14.Text := '▼'; end; end else begin Label3.Text := ''; Label4.Text := ''; Label5.Text := ''; end; finally sl.Free; end; except ; end; end; end; end.