ロトロニック社温湿度プローブ HC2 / HC2A のロギング機能 (2018/04/02)

プローブ単体にロギング機能を内蔵しています。
・データ数: (温度+湿度) × 2000 データまで
・インターバル: 5 秒単位で任意
・時刻データは、ロギング開始時刻のみ記憶
 データごとの時刻は、ロギング開始時刻 + データ数× 5 秒
・ロギング中でも、ロギングデータは読み込み可能
・ロギング中でも、現在値の取得は可能(コマンド:{ 99RDD})

・ロギングを開始すると、HC2 の電源を切っても ロギング状態、計測データ保持され、
次の通電時、前回までの計測データに追加されます。
HC2 の時刻記録は開始時刻だけなのでログ中断の時刻は記録されません。

・ロギング状態の確認
 送信コマンド:{ 99LGC\}
返信文字列を';' 区切りで読みだすと、下記のデータが取得されます。( []内の数字は、0ベースのインデックス)
[1]:ログ状態 1: 実行中、0: 停止中
[2]:ログモード 1: スタートストップモード、2: ループモード
[3]:ロギングインターバル 1: 5秒 2: 10秒 ... 10: 50秒(5秒単位)
[4]:ロギング時刻の基準値(2000 年 1 月 1 日 00:00:00 からの経過秒数を 5 で割った整数値)
[5]:データ数 (状態チェックコマンド時のみ)

・ロギング開始
 送信コマンド:{ 99LGC 1;1;1;12345678;}
 1; ロギング開始
 1; ロギングモード
 1; ロギングインターバル
 123..; ロギング基準時刻

・ロギング基準時刻
 '2000/01/01 00:00:00 を基準として、経過秒数を 5 で割った整数値

・ロギング停止
 送信コマンド:{ 99LGC 0;1;1;12345678;}
 停止の場合、ロギング基準時刻は適当で良いようだが、ロギング状態を取得しその値をそのまま使う。
 
・ロギングデータの取り出し
 1つの計測データに対して 3 バイトのデータが返ってきます。アドレスを、3 バイト進めると次の計測データになります。
 返信伝文の長さは、9 + データ数×12 + 1(#13 = CR) 文字。2000 データの読みだしに 40 秒ほどかかります。

 コマンド:{ 99ERD 0; + 読みだしアドレス + ; + 読みだしバイト数 + ;}
 ※ロギングデータの開始アドレスは、2176。読みだしデータ数ではなく、バイト数。

※「神栄テクノロジー株式会社 温湿度プローブ HC2 (HygroClip2) シリーズ 通信プロトコル」を参考にしました。




■サンプルコード
 Delphi 10.2 Tokyo

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  System.DateUtils, Vcl.StdCtrls, AdPacket, OoMisc, AdPort,AdSelCom ;

type
  TForm2 = class(TForm)
    ApdComPort1: TApdComPort;
    Button2: TButton;
    ComboBox1: TComboBox;
    Memo1: TMemo;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure ApdComPort1TriggerAvail(CP: TObject; Count: Word);
  private
    { Private 宣言 }
  public
    { Public 宣言 }

    // 応答伝文を区別するため
    CmdMode: integer;

    // 読み込みデータカウンター
    LogDataCounter : integer;
    // LGC コマンドにより取得したデータ数
    LogDataCount   : integer;
    // LGC コマンドにより取得したログ周期
    LogInterval : integer;
    // LGC コマンドにより取得したログ開始時刻
    LogTimeCount : integer;
    // LGC コマンドにより取得したログ開始時刻
    LogDataStartTime : TDateTime;
    // ログモード
    LogMode : integer;
    // ロギング中
    IntLogFlag : integer;
    LogDataEndTime : TDateTime;
    // データ読み込みのインデックス
    LogReadIndex : integer;
    // 読みだし時間計測用
    Ticks : Cardinal;
    // 受信文字列
    RcvBuffer : string;

    procedure DoLogCommand;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

// TDateTime を 2000/01/01 00:00:00.00 を基準にした 5秒単位のカウント数に変換
function AirChipTimeCount(ADate: TDateTime): Integer;
// uses ... System.DateUtils;
var
  sec : cardinal;
begin
  sec := SecondsBetween(StrToDateTime('2000/01/01 00:00:00'), ADate);
  result := sec div 5;
end;

// ログ内の時間データ(2000/01/01 00:00:00.00 を基準にした 5秒単位のカウント数)を
// TDateTime に変換
function AirChipTimeCountToDateTime(ACount : integer): TDateTime;
begin
  result := StrToDateTime('2000/01/01 00:00:00') + (ACount * 5) / SecsPerDay;
end;

// ログデータ 3バイトの数値を温度、湿度データに変換
procedure AirChipDataTH(b1, b2, b3: Byte; var T: double; var H: double);
var
  v : Cardinal;
begin
  v := b1 + 256 * b2 + 65536 * b3;
  H := (v mod 1024) / 10;
  T := Trunc(v / 1024) / 20 -100;
end;

procedure TForm2.DoLogCommand;
var
  i : integer;
  sl : TStringList;
  T, H : double;
begin
  Memo1.Lines.BeginUpdate ;
  if CmdMode >= 100 then begin
    sl := TStringList.Create;
    try
      sl.Delimiter := ';';
      sl.DelimitedText := RcvBuffer;
      if (CmdMode <> 101) and (sl.Count >= 6) then begin
        // ログ中ON
        IntLogFlag := (sl[1]).ToInteger;
        // ログモード
        LogMode := (sl[2]).ToInteger;
        // ログ間隔
        LogInterval := (sl[3]).ToInteger * 5; // 5秒単位
        // ログ開始日付時刻
        LogTimeCount := (sl[4]).ToInteger;
        // それを TDateTime に変換
        LogDataStartTime := AirChipTimeCountToDateTime(LogTimeCount);
        // ログデータ数
        LogDataCount := (sl[5]).ToInteger;

        if LogDataCount = 0 then begin
          CmdMode := 0;
          Memo1.Lines.Add(RcvBuffer);
          Memo1.Lines.Add('LogCount = 0, Please retry');
        end
        else begin
          // ログデータ読みだし
          if CmdMode = 100 then begin
            // 周期、開始時刻~終了時刻、データ数を表示
           Memo1.Lines.Add(
              ' ロギング中=' + IntLogFlag.ToString +
              '  ログモード=' + LogMode.ToString +
              '  ログ周期=' + LogInterval.ToString +
              '  開始時刻=' + FormatDateTime('yyyy/mm/dd hh:mm:ss', LogDataStartTime) +
              '  データ数=' + LogDataCount.ToString);

            // ログデータ読込コマンドを送信
            CmdMode := 101;
            LogReadIndex := 0;
            // 次の 80 データ(240 バイト)を読む
            ApdComport1.PutString(
              '{ 99ERD 0;' + Format('%04d',[LogReadIndex + 2176]) + ';00240;}' + #13);
            LogReadIndex := LogReadIndex + 240;
          end
          // ログ停止
          else if CmdMode = 110 then begin
            CmdMode := 0;
            Memo1.Lines.Add(
              ' ロギング中=' + IntLogFlag.ToString +
              '  ログモード=' + LogMode.ToString +
              '  ログ周期=' + LogInterval.ToString +
              '  開始時刻=' + FormatDateTime('yyyy/mm/dd hh:mm:ss', LogDataStartTime) +
              '  データ数=' + LogDataCount.ToString);

            // LOG 停止
            ApdComport1.PutString('{ 99LGC 0;1;'+ sl[3] + ';' + sl[4] +';}' + #13);
            CmdMode := 111; // この応答伝文を無視するためのダミー

            // 周期、開始時刻~終了時刻、データ数を表示
            Memo1.LInes.Add('End Logging');
          end
          // ログ開始
          else if CmdMode = 120 then begin
            CmdMode := 0;
            Memo1.Lines.Add(
              ' ロギング中=' + IntLogFlag.ToString +
              '  ログモード=' + LogMode.ToString +
              '  ログ周期=' + LogInterval.ToString +
              '  開始時刻=' + FormatDateTime('yyyy/mm/dd hh:mm:ss', LogDataStartTime));
            // 周期、開始時刻を表示
            Memo1.Lines.Add('Begin Logging');
          end
          // ログ状態の確認
          else if CmdMode = 130  then begin
            Memo1.Lines.Add(
              ' ロギング中=' + IntLogFlag.ToString +
              '  ログモード=' + LogMode.ToString +
              '  ログ周期=' + LogInterval.ToString +
              '  開始時刻=' + FormatDateTime('yyyy/mm/dd hh:mm:ss', LogDataStartTime) +
              '  データ数=' + LogDataCount.ToString);
          end;
        end;
      end;
      // 240 + 2
      if (CmdMode = 101) and (sl.Count >= 80 * 3) then begin
        for i := 0 to 80 -1 do begin
          Inc(LogDataCounter);
          AirChipDataTH(
            StrToInt(sl[i * 3 + 1]), StrToInt(sl[i * 3 + 2]), StrToInt(sl[i * 3 + 3]), T, H);

          // SecsPerDay = 24 * 60 * 60
          Memo1.Lines.Add(
            Format('%04d: ', [LogDataCounter]) +
            FormatDateTime('yyyy/mm/dd hh:mm:ss',
              LogDataStartTime + ((LogDataCounter-1) * LogInterval) / SecsPerDay) +
            Format(' %.1f %.1f', [T, H]));

          if (LogDataCounter >= LogDataCount) or (LogDataCounter >= 2000) then begin
            LogDataEndTime := LogDataStartTime+ ((LogDataCounter-1) * LogInterval) / SecsPerDay;
            CmdMode := 0;
            Memo1.Lines.Add(Format('%.1f sec', [(GetTickCount - Ticks)/ 1000]));
            Break;
          end;
        end;
        if CmdMode = 101 then begin
          // 次の 80 データ(240 バイト)を読む
          ApdComport1.PutString(
            '{ 99ERD 0;' + Format('%04d',[LogReadIndex + 2176]) + ';00240;}' + #13);
          LogReadIndex := LogReadIndex + 240;
        end;
      end;
    finally
      // 受信文字列バッファをクリア
      RcvBuffer := '';
      sl.Free;
    end;
  end;
  Memo1.Lines.EndUpdate;
  Memo1.Repaint;
end;

// HC2 からの受信伝文
procedure TForm2.ApdComPort1TriggerAvail(CP: TObject; Count: Word);
var
  //i : integer;
  //c : AnsiChar;
  Flag : boolean;
  B: TBytes;
begin
  {
  // 1文字ずつ取得
  Flag := False;
  for i := 1 to Count do begin
    c := ApdComPort1.GetChar;
    if c = #13 then begin
      Flag := True;
      Break;
    end
    else
      RcvBuffer := RcvBuffer + string(c);
  end;
  }
  // ブロックごと取得(時間は変わらない)
  SetLength(B, Count);
  ZeroMemory(B, Count);
  ApdComPort1.GetBlock(B[0], Count);
  RcvBuffer := RcvBuffer + TEncoding.ANSI.GetString(B);
  Flag := B[Count -1] = Ord(#13);

  if Flag then DoLogCommand;
end;

// ロギング状態の確認
procedure TForm2.Button1Click(Sender: TObject);
begin
  CmdMode := 130;
  ApdComport1.PutString('{ 99LGC\}' + #13);
end;

// OPEN
procedure TForm2.Button2Click(Sender: TObject);
var
  s : string;
begin
  if ComboBox1.ItemIndex > 0 then begin
    // COM ポート番号
    s :=(ComboBox1.Items[ComboBox1.ItemIndex]).Substring(4);
    ApdComport1.ComNumber := s.ToInteger;

    // HC2 の仕様
    ApdComport1.Baud := 19200;
    ApdComport1.DataBits := 8;
    ApdComport1.StopBits := 1;
    ApdComport1.Parity := TParity.pNone;
    // HC2 の電源供給に使用のため、ON
    ApdComport1.RTS := True;

    try
      // すでにOpen していてもエラーにはならない
      ApdComPort1.Open := True;
      Memo1.Lines.BeginUpdate;
      Memo1.Lines.Add(' Connected');
      Memo1.Lines.EndUpdate;
    except
      ShowMessage('オープン失敗');
    end;
  end;
end;

// ポートクローズ
procedure TForm2.Button3Click(Sender: TObject);
begin
  // オープンされていない時は、エラーになる
  if ApdComPort1.Open then ApdComPort1.Open := False;
end;

// ログ停止
procedure TForm2.Button4Click(Sender: TObject);
begin
  CmdMode := 110;
  ApdComport1.PutString('{ 99LGC\}' + #13);
end;

// ログ開始
procedure TForm2.Button5Click(Sender: TObject);
var
  LogStartTimeCount : Integer;
begin
  Cmdmode := 0;
  // ログ開始時刻
  LogStartTimeCount := AirChipTimeCount(Now);
  // 一度ロギング 停止
  // 前回のロギングデータのログ周期、ログ開始時刻は無意味になる
  // なので、時刻は適当でも良い
  ApdComport1.PutString('{ 99LGC 0;1;1;' + LogStartTimeCount.ToString +';}' + #13);
  Sleep(100);
  // ロギング 開始
  ApdComport1.PutString('{ 99LGC 1;1;1;' + LogStartTimeCount.ToString +';}' + #13);
  Sleep(100);
  // ロギング状態の確認
  CmdMode := 120;
  ApdComport1.PutString('{ 99LGC\}' + #13);
end;

// ログデータ読みだし
procedure TForm2.Button6Click(Sender: TObject);
begin
  // OPEN後、すぐだとデータ数が0で取得される
  // ログ周期、開始日付時刻を取得するため、ロギング状態を確認
  CmdMode := 100;
  LogDataCounter := 0;
  Ticks := GetTickCount;
  ApdComport1.PutString('{ 99LGC\}' + #13);
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  i :integer;
begin
  Memo1.Lines.Clear;
  RcvBuffer := '';
  ApdComport1.AutoOpen := False;
  // 現在有効な COM ポート番号を列挙
  for i := 1 to 32 do
    if IsPortAvailable(i) then
      ComboBox1.Items.Add ('COM ' + IntToStr (i));
end;

end.