ロトロニック (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.