デルタオーム社(イタリア) 温湿度トランスミッター HD9817T2R (RS-232C タイプ) を使ってみました (2018/02/24)

・2018/03/02 Android Bluetooth ソースコードを追加

下の画像で、2番目が RS-232C タイプです。D-sub9 ピンのプラグが付いています。
電源は不要で、RS-232C から供給されます。(RTS を ON にする必要があるようです)
RS-232C の他、USB、RS-485 (Modbus) タイプもあります。メーカーへのリンクトは、こちらです。



精度は、温度:±0.2℃±測定値の1.5%、湿度:±1.5%RH (0~90%RH) と高精度です。
同じようにプローブ単体でシリアル出力できる製品にロトロニック社の HC2A-S プローブがあります。
こちらの精度(@23℃)は、 温度:±0.1℃(0~30℃)、湿度:±0.8%RH(0~100%RH)と、より高精度です。

 ※H2A-Sプローブは、UART 出力のため、RS232C レベルに変換する必要があります。(例えば、秋月電子のこちら を使います。アマゾンだとこちら。)
 また、接続用のHC2 丸7pin ソケットは市販されていないため、専用のケーブルを切断してハンダ付けすることになります。
 ちなみに定価は、デルタオーム社トランスミッター HD9817T2R \67,400.。ロトロニック社プローブ HC2A-S \50,000. + 接続ケーブル \20,000.

■ デルタオームのサイトから、読み込みツール↓がダウンロードできます。


■ 通信テスト用にDelphiで読み込みツールを作ってみました。


■ Delphi 開発中のスクリーンショットです。


■ ソースコード
 ※ApdComPort を使っています。メニュー 「ツール」-「GetIt パッケージマネージャのインストール」からインストール可能です。


unit Unit2;

interface

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

type
  TForm2 = class(TForm)
    ApdComPort1: TApdComPort;
    Button1: TButton;
    Edit1: TEdit;
    Timer1: TTimer;
    Button2: TButton;
    ApdStatusLight1: TApdStatusLight;
    ApdSLController1: TApdSLController;
    ApdStatusLight2: TApdStatusLight;
    Label1: TLabel;
    Label2: TLabel;
    ComboBox1: TComboBox;
    ApdDataPacket1: TApdDataPacket;
    Edit2: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure ApdComPort1TriggerAvail(CP: TObject; Count: Word);
    procedure Timer1Timer(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    buf : string;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

// 受信 (ApdComPortコンポーネントのみを使用した時)
procedure TForm2.ApdComPort1TriggerAvail(CP: TObject; Count: Word);
var
  i: Word;
  c: AnsiChar;
begin
  // 受信文字列の取得
  for i := 1 to Count do begin
    c := ApdComPort1.GetChar;
    // 終端に#13 (CR) が付加されている
    if c = #13 then
      Edit2.Text := buf
    else
      buf := buf + string(c);
  end;
end;

// 受信 (ApdDataPacketコンポーネントを使用した時)
procedure TForm2.ApdDataPacket1StringPacket(Sender: TObject; Data: AnsiString);
begin
  Edit1.Text := String(Data);
end;

// OPEN
procedure TForm2.Button1Click(Sender: TObject);
var
  comNo : integer;
begin
  with ComboBox1 do begin
    if ItemIndex >= 0 then
      comNo := StrToIntDef(Items[ItemIndex].Substring (4), -1)
    else
      comNo := -1;
  end;
  if comNo >= 0  then begin
    with ApdComport1 do begin
      ComNumber := comNo;

      Baud := 2400;
      StopBits := 1;
      DataBits := 8;
      Parity := TParity.pNone;

      RTS := True; // デフォルト
      try
        Open := True;
        // ステータスモニタを ON
        ApdSLController1.Monitoring := True;
       except
        ShowMessage('OPEN 失敗');
      end;
    end
  end
  else
    ShowMessage('有効な COM ポートが見つかりません.');;
end;

// CLOSE
procedure TForm2.Button2Click(Sender: TObject);
begin
  ApdComPort1.Open := False;
  Edit1.Text := '';
  Edit2.Text := '';
end;

// FormCreate
procedure TForm2.FormCreate(Sender: TObject);
var
  i :integer;
begin
  // 有効な COM ポートを列挙
  for i := 1 to 32 do
    if IsPortAvailable(i) then
      ComboBox1.Items.Add ( 'COM ' + IntToStr (i));
  if ComboBox1.Items.Count > 0 then
    ComboBox1.ItemIndex := 0;

  Edit1.Text := '';
  Edit2.Text := '';

  with ApdDataPacket1 do begin
    // 終端文字をセット
    EndCond := [AdPacket.ecString];
    EndString := #13;
    // 開始文字を無視
    StartCond := AdPacket.scAnyData;
  end;
end;

// 1秒周期
procedure TForm2.Timer1Timer(Sender: TObject);
begin
  if ApdComPort1.Open  then begin
    buf := '';
    // 送信
    // 型番を取得
    //ApdComPort1.Output := 'G0'+#13;
    // 温度、湿度を取得
    //ApdComPort1.Output := 'S0'+#13;
    ApdComPort1.PutString('S0'+#13);
  end;
end;

end.

■ REX-BT60 (RS232C-Bluetooth 変換アダプタ) を使って Android 端末に計測データを表示してみました。
 ・Parani-SD1000-00 でも使用可能。(アマゾンへのリンク)(マイクロテクニカへのリンク)(マイクロテクニカ説明書
 ・露点温度計算
 ・音声読み上げ
   30 秒に 1 回、計測値を読みあげます。結構便利です。
 ・トレンドグラフ表示
   計測値が安定しているかの確認用で、最新 12 分間のみの表示です。
   CSV ファイルに保存する機能を追加予定です。

 
 返信文字列数が少ないので、Bluetooth LE でも使えるかもしれません。であれば、iOS 端末でも使えますね。

■ソースコード


コード
unit Unit2;

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, FMX.Objects,
  UIConsts,
  Math, FMX.Colors,
  // for TTS
  Androidapi.JNI.TTS,AndroidAPI.JNIBridge;

type
  TBtThread = class(TThread)
  private
    { Private 宣言 }
    procedure BtOpen;

  protected
    procedure Execute; override;
  public
    constructor Create; virtual;
  end;

type
  TForm2 = class(TForm)
    Bluetooth1: TBluetooth;
    Timer1: TTimer;
    Label1: TLabel;
    Label2: TLabel;
    PaintBox1: TPaintBox;
    Label3: TLabel;
    Label4: TLabel;
    Panel1: TPanel;
    Rectangle1: TRectangle;
    Rectangle2: TRectangle;
    Rectangle3: TRectangle;
    Rectangle5: TRectangle;
    Rectangle6: TRectangle;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Switch1: TSwitch;
    Label9: TLabel;
    Switch2: TSwitch;
    Label10: TLabel;
    procedure Timer1Timer(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
    procedure FormDestroy(Sender: TObject);
    // TTS
    type
      TttsOnInitListener = class(TJavaLocal, JTextToSpeech_OnInitListener)
      private
        [weak] FParent : TForm2;
      public
        constructor Create(AParent : TForm2);
        procedure onInit(status: Integer); cdecl;
      end;
  private
    { private 宣言 }
    ttsListener : TttsOnInitListener;
    tts : JTextToSpeech;
    procedure SpeakOut(const s :string);
    procedure InitTTS;
  public
    { public 宣言 }
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
  end;

type
  TRecData = record
    Temp : double;
    Humi : double;
    Dp   : double;
    RecTime : TDateTime;
  end;

const
  RECDATA_MAX = 720;

var
  GThdMode : integer;
  GCmdMode : integer;
  GSocket:TBluetoothSocket;
  ThBt : TBtThread;
  OpenNGcnt : integer;
  OpenMsecCnt : integer;
  Counter : integer;
  BtDeviceHead : string;

const
  // SPP(Serial Port Profile) による通信のUUID
  ServiceUUID = '{00001101-0000-1000-8000-00805F9B34FB}';
  BTDeviceHead1 = 'RN42-';
  BTDeviceHead2 = 'SD1000v2';

  thdTHSTART   = 1000;
  thdTHTERM    = 2000;
  cmdSCCREATE  = 200;
  cmdSCCONNECT = 201;
  cmdSCNG = 202;

var
  Form2: TForm2;
  ASocket : TBluetoothSocket;
  FlagBTConnect : boolean;
  RecDataAry : array [0..RECDATA_MAX-1] of TRecData;
  DispIndex : integer;

implementation

{$R *.fmx}

uses Androidapi.JNI.JavaTypes, FMX.Helpers.Android
{$IF CompilerVersion >= 27.0}
, Androidapi.Helpers
{$ENDIF}
;

function CalcPws(Td : double):double;
// uses ... Math;
var
  S, Pws, T : double;
begin
   // 絶対温度 [K]
  T := Td + 273.15;
  // 飽和水蒸気圧 [hPa]
  // Vaisala --------------------------------------------------------------
  S := T - (
       -0.12743214E-7 * Power(T, 3) +
        0.13746454E-4 * Power(T, 2) +
       -0.46094296E-2 * Power(T, 1) +
        0.49313580    * Power(T, 0)
      );
  Pws := Exp(
           -0.14452093E-7 * Power(S, 3) +
            0.41764768E-4 * Power(S, 2) +
           -0.48640239E-1 * Power(S, 1) +
            0.13914993E01 * Power(S, 0) +
           -0.58002206E04 * Power(S, -1)+
            6.5459673 * Ln(S)
           ) / 100;
  result := Pws;
end;

function CalcDewPoint(Td, Hu : double): double;
var
  Pw, Pws, Tdp : double;
begin
  Pws := CalcPws(td);
    // 水蒸気圧 [hPa]
  Pw := Pws * Hu / 100;
  // 露点温度 [℃DP]
  Tdp := 237.30 / (7.5000 / Log10(Pw / 6.1078) - 1);
  result := Tdp;
end;

// -----------------------------------------------------------------------------
// 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
            Form2.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
                  Form2.Label1.Text := Form2.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(ServiceUUID), 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;
    WaitFor;
    FreeAndNil(ThBt);
    GThdMODE := thdTHTERM;
  end;
end;

constructor TBtThread.Create;
begin
  // スレッドを生成、直ちに実行
  inherited Create(False);
  // スレッド終了時、スレッドオブジェクトを破棄
  FreeOnTerminate := True;
end;

procedure TBtThread.Execute;
begin
  BtOpen;
end;

// -----------------------------------------------------------------------------

procedure TForm2.InitTTS;
begin
  tts := TJTextToSpeech.JavaClass.init(TAndroidHelper.Context, ttsListener);
end;
procedure TForm2.SpeakOut(const s : string);
var
  text : JString;
begin
  text := StringToJString(s);
  tts.speak(text, TJTextToSpeech.JavaClass.QUEUE_FLUSH, nil);
end;
{ TForm1.TttsOnInitListener }

constructor TForm2.TttsOnInitListener.Create(AParent: TForm2);
begin
  inherited Create;
  FParent := AParent
end;

procedure TForm2.TttsOnInitListener.onInit(status: Integer);
var
  Result : Integer;
begin
  if (status = TJTextToSpeech.JavaClass.SUCCESS) then
  begin
   //result := FParent.tts.setLanguage(TJLocale.JavaClass.US);
   result := FParent.tts.setLanguage(TJLocale.JavaClass.JAPAN);
   if (result = TJTextToSpeech.JavaClass.LANG_MISSING_DATA) or
      (result = TJTextToSpeech.JavaClass.LANG_NOT_SUPPORTED) then
     ShowMessage('This Language is not supported')
   else
   begin
     //FParent.Button1.Enabled := true;  // SayMe
     //FParent.button2.Enabled := false; // Init TTS
   end;
  end
  else
    ShowMessage('Initilization Failed!');
end;

// -----------------------------------------------------------------------------

function ASocketReceiveData(ASocket: TBluetoothSocket; term: Char; ATimeout: Cardinal): string;
var
  AData : TBytes;
  ReadData : TBytes;
  i : integer;
  res : string;
  Ticks : Cardinal;
  idx : integer;
  loop : boolean;
  cnt : integer;
begin
  res := '';
  cnt := 0;
  SetLength(ReadData, 1024);
  idx := 0;
  Ticks := TThread.GetTickCount;
  loop := True;
  while loop and (cnt < 500) do begin
    Sleep(1);
    AData := ASocket.ReceiveData;
    if Length(AData) > 0 then begin
      for i := 0 to Length(AData) - 1 do begin
        ReadData[idx] := AData[i];
        Inc(idx);
        // CR であれば、抜ける
        if (AData[i] = Ord(term)) or (idx >= 1024) then begin
          loop := False;
          break;
        end;
      end;
    end;
    Inc(cnt);
    if loop then
      loop := TThread.GetTickCount - Ticks < ATimeout;
  end;
  SetLength(ReadData, idx);
  res := TEncoding.ANSI.GetString(ReadData);
  result := Trim(res);
end;

// -----------------------------------------------------------------------------

constructor TForm2.Create(AOwner: TComponent);
begin
  inherited;
  ttsListener := TttsOnInitListener.Create(self);
end;

destructor TForm2.Destroy;
begin
  if Assigned(tts) then begin
    tts.stop;
    tts.shutdown;
    tts := nil;
  end;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  i : integer;
begin
  Timer1.Interval := 10;
  ThBt := TBtThread.Create;
  Label1.Text := '';
  Label2.Text := '';
  Label3.Text := '';
  Label5.Text := '';

  for i := 0 to RECDATA_MAX -1 do begin
    RecDataAry[i].Temp := -99;
    RecDataAry[i].Humi := -99;
    RecDataAry[i].Dp := -99;
    RecDataAry[i].RecTime := 0;
  end;
  DispIndex := -1;
  BtDeviceHead := BTDeviceHead1;
  // TTS
  InitTTS;

end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
  // ソケット破棄
  if GSocket <> nil then begin
    if GSocket.Connected then
      GSocket.Close;
    FreeAndNil(GSocket);
  end;
end;

procedure TForm2.PaintBox1Paint(Sender: TObject; Canvas: TCanvas);
var
  xsc , ysc, ybase : double;
  i : integer;
  stX, stY, edX, edY : single;
  aLeft, aTop, aRight, aBottom : single;
  aStep : integer;
  pbox : TPaintBox;
begin
  pBox := Sender as TPaintBox;
  with Canvas do begin
    BeginScene;

    xsc := pbox.Width / RECDATA_MAX;
    ysc := pbox.Height/ 100;

    ybase := 0.0;
    aLeft := 0;
    aTop := 0;
    aRight := aLeft + pbox.Width;
    aBottom := aTop + pbox.Height;
    aStep := RECDATA_MAX div 6;

    Fill.Kind := TBrushKind.Solid ;
    Fill.Color := TAlphaColors.Black;//claBlack;
    FillRect(RectF(aLeft, aTop, aRight + 1, aBottom), 0, 0, AllCorners, 1.0);


    Stroke.Color := claBlue; // 線色
    Stroke.Dash := TStrokeDash.Dash; // 線種
    Stroke.Kind := TBrushKind.Solid;
    Stroke.Thickness := 1 ; // 線幅

    // 横目盛り
    for i := 1 to 9 do begin
        //if i * 10 = 50 then
        //  Stroke.Color := claRed
        //else
        //  Stroke.Color := claBlue;

        // 横線
        DrawLine(
          PointF(aLeft + 1, aBottom - i * 10 * ysc),
          PointF(aRight, aBottom - i * 10 * ysc),
          1.0);

        Fill.Color := claWhite;
        Font.Size := 10;
        // 左側の目盛り
        FillText(
          RectF(aLeft, aBottom - i * 10 * ysc - 6,
                aLeft + 20, aBottom - i * 10 * ysc - 6 + 16),
          IntToStr(i*10),
          True, 1.0, [], TTextAlign.Leading, TTextAlign.Leading);

        // 右側の目盛り
        FillText(
          RectF(aRight - 20, aBottom - i * 10 * ysc - 6,
                 aRight, aBottom - i * 10 * ysc - 6 + 16),
          IntToStr(i*5),
          True, 1.0, [], TTextAlign.Trailing, TTextAlign.Leading);
    end;

    Fill.Color := claWhite;
    FillText(RectF(aLeft + 1, aTop, aLeft + 40, aTop + 16),
      '[%RH]',
      True, 1.0, [], TTextAlign.Leading, TTextAlign.Leading);

    FillText(RectF(aRight - 20, aTop, aRight, aTop + 16),
      '[℃]',
      True, 1.0, [], TTextAlign.Trailing, TTextAlign.Leading);
    // 縦目盛り
    for i := 1 to 5 do begin
        DrawLine(
          PointF(aLeft + i * aStep * xsc, aBottom),
          PointF(aLeft + i * aStep * xsc, aTop),
          0.75);
    end;
    // 時刻
    FillText(
      RectF(aLeft, aBottom - 16, aLeft + 40, aBottom),
      FormatDateTime('hh:mm', RecDataAry[0].RecTime),
      True, 1.0, [], TTextAlign.Leading, TTextAlign.Leading);
    for i := 1 to 6 do begin
      if RecDataAry[i * aStep-1].RecTime > 0 then begin
        FillText(
          RectF(aLeft + i * aStep * xsc, aBottom - 16,
                aLeft + i * aStep * xsc + 40, aBottom),
          FormatDateTime('hh:mm', RecDataAry[i * aStep - 1].RecTime),
          True, 1.0, [], TTextAlign.Leading, TTextAlign.Leading);
      end;
    end;
    // 現在の値
    FillText(
      RectF(aLeft + 40, aTop + 1,
            aLeft + 40 + 400, aTop + 17),
      FormatDateTime('[ yyyy/mm/dd hh:mm:ss ]  ', Now) +
      Format('%.1f %%RH: %.1f ℃: %.1f ℃DP',
        [RecDataAry[DispIndex].Humi, RecDataAry[DispIndex].Temp, RecDataAry[DispIndex].Dp]),
      True, 1.0, [], TTextAlign.Leading, TTextAlign.Leading);

    // 計測データ
    Stroke.Dash := TStrokeDash.Solid;
    Stroke.Thickness := 1 ; // 線幅
    // 相対湿度
    Stroke.Color := claYellow;
    stX := 0; stY := 0; edX := 0; edY := 0;

    for i := 0 to RECDATA_MAX - 2 do begin
      if RecDataAry[i].Humi >= 0 then begin
        stX := aLeft + i * xsc;
        stY := aBottom - (RecDataAry[i].Humi + ybase) * ysc;
      end;
      if RecDataAry[i+1].Humi >= 0 then begin
        edX := aLeft + (i + 1) * xsc;
        edY := aBottom - (RecDataAry[i + 1].Humi + ybase) * ysc;
      end;
      if (stX > 0) and (stY > 0) and (edX > 0) and (edY > 0) then
        DrawLine(PointF(stX + 1, stY), PointF(edX + 1, edY), 1.0);
    end;

    // 温度
    ysc := pbox.Height / 50;
    Stroke.Color := claLime;
    stX := 0; stY := 0; edX := 0; edY := 0;

    for i := 0 to RECDATA_MAX - 2 do begin
      if RecDataAry[i].Temp >= 0 then begin
        stX := aLeft + i * xsc;
        stY := aBottom - (RecDataAry[i].Temp + ybase) * ysc;
      end;
      if RecDataAry[i+1].Temp >= 0 then begin
        edX := aLeft + (i + 1) * xsc;
        edY := aBottom - (RecDataAry[i + 1].Temp + ybase) * ysc;
      end;
      if (stX > 0) and (stY > 0) and (edX > 0) and (edY > 0) then
        DrawLine(PointF(stX + 1, stY), PointF(edX + 1, edY), 1.0);
    end;
    // 露点温度
    if Switch1.IsChecked  then begin
      Stroke.Color := claAqua;
      stX := 0; stY := 0; edX := 0; edY := 0;
      for i := 0 to RECDATA_MAX - 2 do begin
        if RecDataAry[i].Dp >= 0 then begin
          stX := aLeft + i * xsc;
          stY := aBottom - (RecDataAry[i].Dp + ybase) * ysc;
        end;
        if RecDataAry[i+1].Dp >= 0 then begin
          edX := aLeft + (i + 1) * xsc;
          edY := aBottom - (RecDataAry[i + 1].Dp + ybase) * ysc;
        end;
        if (stX > 0) and (stY > 0) and (edX > 0) and (edY > 0) then
          DrawLine(PointF(stX + 1, stY), PointF(edX + 1, edY), 1.0);
      end;
    end;
    EndScene;
  end;
end;

procedure TForm2.Timer1Timer(Sender: TObject);
var
  res : string;
  T, H, Tdp : double;
  i : integer;
  TxData : TBytes;
begin
  if not ((GCMDMODE = cmdSCCONNECT) and GSocket.Connected) then begin
    Inc(OpenMsecCnt);
    Label4.Text := IntToStr(OpenMsecCnt * 10) + 'msec';
    if GCMDMODE = cmdSCNG then begin
      Inc(OpenNgCnt);
      if OpenNgCnt > 4 then begin
        Timer1.Enabled := False;
        ShowMessage('接続できません.');
      end
      else begin
        // 再接続
        // ターゲットを変えてみる
        if BtDeviceHead = BTDeviceHead1 then
          BtDeviceHead := BTDeviceHead2
        else
          BtDeviceHead := BTDeviceHead1;

        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('S0' + #13);
      GSocket.SendData(TxData);
      // 受信
      res := ASocketReceiveData(GSocket, #13, 500);
      T := StrToFloatDef(res.Substring(0, 6), -20);
      H := StrToFloatDef(res.Substring(11), 0);

      Label2.Text := Format('%.1f', [T]);
      Label3.Text := Format('%.1f', [H]);
      if (T > -20) and (H > 0) then begin
        Tdp := CalcDewPoint(T,H);
        Label5.Text := Format('%.1f', [Tdp]);
      end
      else begin
        Tdp := 0;
        Label5.Text := '';
      end;

      Inc(DispIndex);
      if DispIndex >=  RECDATA_MAX - 1 then begin
        DispIndex := RECDATA_MAX - 121;
        for i := 0 to RECDATA_MAX - 121  do
          RecDataAry[i] := RecDataAry[i + 121];
        for i := RECDATA_MAX - 120 to RECDATA_MAX - 1 do begin
          RecDataAry[i].Temp := -99;
          RecDataAry[i].Humi := -99;
          RecDataAry[i].Dp := -99;
          RecDataAry[i].RecTime := 0;
        end;
      end;

      RecDataAry[DispIndex].Humi := H;
      RecDataAry[DispIndex].Temp := T;
      RecDataAry[DispIndex].Dp := Tdp;
      RecDataAry[DispIndex].RecTime := Now;

      // 描画
      PaintBox1.Repaint;

      // テキストスピーチ
      Inc(Counter);
      if Switch2.IsChecked then begin
        if Counter = 10 then
          SpeakOut('温度:' + Label2.Text)
        else if Counter = 20 then
          SpeakOut('湿度:' + Label3.Text)
        else if Counter = 30 then
          SpeakOut('露点温度:' + Label5.Text);
      end;
      if Counter >= 30 then Counter := 0;
    except
      ;
    end;
  end;
end;

end.