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