ラトックシステムの REX-WF60 (Wi-Fi -> RS-232C 変換アダプター) を使ってみた (2016/10/24 update)

※2016/10/24 文字列書き込み、読み込みコードを追加しました。


iOS, Android, Windows から Wi-Fi 経由で、RS-232C 通信が行える REX-WF60 を使って、「電子天びん」のデータ受信、PLC のデータ送信、受信を行ってみました。
 同じようにワイヤレスで RS-232C 機器が使える REX-BT60 (Bluetooth -> RS-232C 変換アダプター)は、iOS には対応していませんが、
 こちらは、iOS にも対応しています。

・iOS, Android, Windows とも、アクセスポイントモード(WF60 と直接通信する)と、インフラストラクチャーモード(アクセスポイントを経由して通信を行う)に対応しています。
 ※アクセスポイントモードでは、WF60との通信専用になり、インターネット、他のPCとの通信等は行えなくなります。

※こちらの環境 (Windows)では、簡単設定ツールで WF60 が検出できず、設定時のみファイアーウォールを一時的に無効にする必要がありました。
 iPhone の場合は、そのままでOKでした。
※Wi-Fi エリアが重複している場合、手動で Wi-Fi の接続先を切り換える必要があります。


シリアル通信(仮想 COM ポート)は、ComPortLibrary。TCP 通信は、IndyTCPClient コンポーネントを使用しています。
※仮想 COM ポートは、Windows 用で、PC ごとにインストールが必要です。また、通常の RS-232C 通信プログラム同様の通信設定が必要になります。
 TCP 通信の場合は、WF60 の IP アドレスとポートを指定するだけになり、プログラムが簡単になります。



■「電子天びん」のデータを受信してみました。
 接続ケーブルは、ストレート(SD, RD, SG のみ)を使用。
 "*HELLO*"以降が、「電子天びん」からの受信データです。




■PLCと通信してみました。

 PLCのデータを読み書きするために、シリアル通信の設定を WF60 に合わせます。
 ※設定変更、書き込み後は、PLC をリセットする必要があります。←たまに、忘れます。



接続ケーブルは、クロスケーブル(SD, RD, SG のみ)を使用。
PLC への送信コマンド、レスポンスの文字列は、TCP 接続であっても通常のシリアル通信とまったく同じです。


// IOHandler.WriteLnRFC() を IOHandler.WriteLn() に変更しました (2017/ 1/ 6)

unit WF60Unit;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, CPort, IdBaseComponent,
  IdComponent, IdTCPConnection, IdTCPClient;

type
  TForm3 = class(TForm)
    ComPort1: TComPort;
    Button1: TButton;
    IdTCPClient1: TIdTCPClient;
    Button2: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

// REX-WF60 へは、アクセスポイントモードで接続
// 事前に Wi-Fi を切り替えておく必要がある
// 仮想 COM ポート で「電子天びん」のデータを受信
// ストレートケーブルで接続(接続は、SD, RD, SG のみでOK)
procedure TForm3.Button1Click(Sender: TObject);
var
  s:string;
begin
  ComPort1.Port := 'COM8';
  ComPort1.BaudRate := br9600;
  ComPort1.DataBits := dbEight;
  ComPort1.StopBits := sbOneStopBit;
  ComPort1.Parity.Bits := prNone;
  ComPort1.Timeouts.ReadInterval := 100; // デフォルト=-1;
  ComPort1.Timeouts.WriteTotalConstant := 500;
  ComPort1.Timeouts.ReadTotalConstant := 500;
  // オープン
  ComPort1.Open;
  // 実際の機器に接続されているかは、不明
  if Comport1.Connected then begin
    // 受信
    // 受信文字列:'*HELLO*'で始まる文字列(異常であっても)
    ComPort1.ReadStr(s, 256);
    Edit1.Text := s;
  end;
  // クローズ
  ComPort1.Close;
end;

// TCP で「電子天びん」のデータを受信
// ストレートケーブルで接続(接続は、SD, RD, SG のみでOK)

procedure TForm3.Button2Click(Sender: TObject);
begin
  IdTCPClient1.Host := '1.2.3.4';
  IdTCPClient1.Port := 2000;
  IdTCPClient1.ReadTimeout := 500;
  // 接続
  IdTCPClient1.Connect;
  if IdTCPClient1.Connected then begin
    // 受信
    // 受信異常:'';
    // 受信正常:'*HELLO*' で始まる文字列
    Edit2.Text := IdTCPClient1.IOHandler.ReadLn() ;
  end;
  // 切断
  IdTCPClient1.Disconnect;
end;

// 仮想 COM ポート で PLC(FX3S + FX3G-232-BD) 送受信
// クロスケーブルで接続(接続は、SD, RD, SG のみでOK)
procedure TForm3.Button3Click(Sender: TObject);
const
  STX = #$02; // テキスト開始
  ETX = #$03; // テキスト終了
  EOT = #$04; // テキスト終結
  ENQ = #$05; // 問合せ
  ACK = #$06; // 肯定応答
  CR  = #$0D; // キャリッジリターン
  LF  = #$0A; // 改行
  CL  = #$0C; // クリア
  NAK = #$15; // 否定応答
var
  cmd, res : string;
  Ticks : Cardinal;
begin
  // MC プロトコル形式4(CR,LF あり)
  cmd := ENQ;
  cmd := cmd + '00';    // 局番号(PLC側の設定による)
  cmd := cmd + 'FF';    // PC番号(FXの場合、固定)
  cmd := cmd + 'BR';    // コマンド(ビット読み出し)
  cmd := cmd + '0';     // 伝文ウェイト('0'~'F' = 0~150ms)
  cmd := cmd + 'X0000'; // 5桁で指示:X0000から
  cmd := cmd + '16';    // 16個読み出し
  cmd := cmd + CR + LF;
  Edit1.Text := cmd;
  Edit2.Text := '';

  ComPort1.Port := 'COM8';
  ComPort1.BaudRate := br9600;
  ComPort1.DataBits := dbEight;
  ComPort1.StopBits := sbOneStopBit;
  ComPort1.Parity.Bits := prNone;

  ComPort1.Timeouts.ReadInterval := 100; // デフォルト=-1;
  ComPort1.Timeouts.WriteTotalConstant := 500;
  ComPort1.Timeouts.ReadTotalConstant := 500;

  Ticks := GetTickCount;
  // オープン
  ComPort1.Open;
  // 実際の機器に接続されているかは、不明
  if Comport1.Connected then begin
    // コマンド送信
    Comport1.WriteStr(cmd);
    // レスポンス受信
    // 受信文字列:'*HELLO*'で始まる文字列(異常であっても)
    ComPort1.ReadStr(res, 1024);
    Edit2.Text := res;
  end;
  // クローズ
  ComPort1.Close;
  Caption := 'COM:' + IntToStr(GetTickCount()-Ticks);
end;

// TCP でPLC(FX3S + FX3G-232-BD) 送受信
// クロスケーブルで接続(接続は、SD, RD, SG のみでOK)
procedure TForm3.Button4Click(Sender: TObject);
const
  STX = #$02; // テキスト開始
  ETX = #$03; // テキスト終了
  EOT = #$04; // テキスト終結
  ENQ = #$05; // 問合せ
  ACK = #$06; // 肯定応答
  CR  = #$0D; // キャリッジリターン
  LF  = #$0A; // 改行
  CL  = #$0C; // クリア
  NAK = #$15; // 否定応答
var
  cmd, res : string;
  Ticks : Cardinal;
begin
  // MC プロトコル形式4(CR,LF あり)
  cmd := ENQ;
  cmd := cmd + '00';    // 局番号(PLC側の設定による)
  cmd := cmd + 'FF';    // PC番号(FXの場合、固定)
  cmd := cmd + 'BR';    // コマンド(ビット読み出し)
  cmd := cmd + '0';     // 伝文ウェイト('0'~'F' = 0~150ms)
  cmd := cmd + 'X0000'; // 5桁で指示:X0000から
  cmd := cmd + '16';    // 16個読み出し
  cmd := cmd + CR + LF;
  Edit1.Text := cmd;
  Edit2.Text := '';
 
  // アクセスポイントモードの時、IP アドレスは WF60 から自動で割り付けられる
  IdTCPClient1.Host := '1.2.3.4';
  // ポート:2000 固定
  IdTCPClient1.Port := 2000;
  IdTCPClient1.ReadTimeout := 500;

  Ticks := GetTickCount;
  // 接続
  IdTCPClient1.Connect;
  if IdTCPClient1.Connected then begin
    // コマンド送信
    IdTCPClient1.IOHandler.WriteLn(cmd);
    // while IdTCPClient1.IOHandler.CheckForDataOnSource(10) do ;
    // レスポンス受信
    // 受信異常:'';
    // 受信正常:'*HELLO*' で始まる文字列
    res := IdTCPClient1.IOHandler.ReadLn() ;
    Edit2.Text := res;
  end;
  // 切断
  IdTCPClient1.Disconnect;
  Caption := 'TCP:' + IntToStr(GetTickCount()-Ticks);
end;

end.

// *****************************************************************************
//  以下、2016/10/24追加分 REX-WF60 TCP->FX3S RS232C 交信
// *****************************************************************************

// REX-WF60 ワードデバイス 文字列書き込み
// 1ワード= 16ビット= 2バイト= 2 文字
procedure TForm1.Button10Click(Sender: TObject);
var
  cmd, res : string;
  strTExt : string;
  srcText : string;
  sndText : string;
  sndLen : integer;
  i : integer;
begin
  // 転送文字列
  srcText := Edit11.Text;//'ABCDEFG';
  // 文字列をASCIIコードに変換し、それを16進2桁の文字列にする
  // 文字列は、上位、下位で格納される
  for i := 1 to srcText.Length do
    sndText := sndText + IntToHex(Ord(srcText[i]), 2);

  // 書き込みワード数=必要ワード数
  sndLen  := srcText.Length div 2;
  if srcText.Length mod 2 > 0 then begin
    // 1文字余った時
    sndText := sndText + '00';
    Inc(sndLen);
  end;

  // MC プロトコル形式4(CR,LF あり)
  cmd := ENQ;
  cmd := cmd + '00';    // 局番号(PLC側の設定による)
  cmd := cmd + 'FF';    // PC番号(FXの場合、固定)
  cmd := cmd + 'WW';    // コマンド(ワード書き込み)
  cmd := cmd + '1';     // 伝文ウェイト('0'~'F' = 0~150ms)
  cmd := cmd + 'D0000'; // 5桁で指示:Y0000から
  cmd := cmd + IntToHex(sndLen, 2); // 書き込みワード数 2桁16進数

  // 文字列が'AB'の時、D0 の上位に'B'、下位に'A' が転送される
  cmd := cmd + sndText;   // 書き込み文字列のASCIIコード
  cmd := cmd + CR + LF; // #13#10

  Edit8.Text := cmd;
  Edit9.Text := '';

  // アクセスポイントモードの時、IP アドレスは WF60 から自動で割り付けられる
  IdTCPClient1.Host := Edit6.Text;//'1.2.3.4';
  // ポート:2000 固定
  IdTCPClient1.Port := StrToIntDef(Edit7.Text, 2000);
  IdTCPClient1.ConnectTimeout := 1000;
  IdTCPClient1.ReadTimeout := 500;
  try
    // 接続
    if not IdTCPClient1.Connected then
      IdTCPClient1.Connect;
    if IdTCPClient1.Connected then begin
      // コマンド送信
      IdTCPClient1.IOHandler.WriteLn(cmd);
      while IdTCPClient1.IOHandler.CheckForDataOnSource(10) do ;

      // レスポンス受信
      // 受信異常:'';
      // 受信正常:'*HELLO*' で始まる文字列
      // ReadLn:CRLFは含まれない
      res := IdTCPClient1.IOHandler.ReadLn() ;
      Edit9.Text := res;

      {
      正常終了時:
      '*HELLO*'+ACK(#06)+ '00FF';// CRLF(#13#10)は含まない
      異常終了時:
      '*HELLO*'NAK(#$15) + '00FF' + ERRORCODE(2文字);// + CRLF(#13#10)は含まない
      }
      if Pos('*HELLO*', res) = 1 then begin
        res := Copy(res, 8);
        if (Copy(res, 1, 1) = ACK) and (Length(res) = 5) then begin
          // 正常終了
          Edit10.Text := 'OK';
        end
        else if (Copy(res, 1, 1) = NAK) and (Length(res) = 7) then begin
          // エラーコード
          Edit10.Text := 'ErrorCode = ' + Copy(res, 6, 2);
        end;
      end
      else
        Edit10.Text := 'NoRes';
    end;
    // 切断
    IdTCPClient1.Disconnect;
  except
    // エラーメッセージ
    on E: Exception do begin
      strTExt := E.ClassName + sLineBreak + E.Message;
      Application.MessageBox(PChar(strTExt), '情報', MB_ICONINFORMATION);
    end;
  end;
end;

// REX-WF60 ワードデバイス 文字列読み込み
// 1ワード= 16ビット= 2バイト= 2 文字
procedure TForm1.Button11Click(Sender: TObject);
var
  cmd, res : string;
  strTExt : string;
  wordsCount : integer;
  readLen : integer;
  readText : string;
  s : string;
  i : integer;
begin
  // 読み込み文字列数
  readLen := Length(Edit11.Text);
  // 読み込みワード数
  wordsCount := readLen div 2;
  if readLen mod 2 > 0 then  Inc(wordsCount);

  // MC プロトコル形式4(CR,LF あり)
  cmd := ENQ;
  cmd := cmd + '00';    // 局番号(PLC側の設定による)
  cmd := cmd + 'FF';    // PC番号(FXの場合、固定)
  cmd := cmd + 'WR';    // コマンド(ワード読み出し)
  cmd := cmd + '1';     // 伝文ウェイト('0'~'F' = 0~150ms)
  cmd := cmd + 'D0000'; // 5桁で指示:Y0000から
  cmd := cmd + IntToHex(wordsCount, 2); // '06';    // 読み出し数2桁
  cmd := cmd + CR + LF;
  Edit8.Text := cmd;
  Edit9.Text := '';

  // アクセスポイントモードの時、IP アドレスは WF60 から自動で割り付けられる
  IdTCPClient1.Host := Edit6.Text; //'1.2.3.4';
  // ポート:2000 固定
  IdTCPClient1.Port := StrToIntDef(Edit7.Text, 2000);

  IdTCPClient1.ConnectTimeout := 1000;
  IdTCPClient1.ReadTimeout := 500;

  try
    // 接続
    if not IdTCPClient1.Connected then
      IdTCPClient1.Connect;

    if IdTCPClient1.Connected then begin
      // コマンド送信
      IdTCPClient1.IOHandler.WriteLn(cmd);
      while IdTCPClient1.IOHandler.CheckForDataOnSource(10) do ;
      // レスポンス受信
      // 受信異常:'';
      // 受信正常:'*HELLO*' で始まる文字列
      // ReadLn:CRLFは含まれない
      res := IdTCPClient1.IOHandler.ReadLn() ;
      Edit9.Text := res;

      {
      正常終了時:
      '*HELLO*' + STX(#02)+'00FF'+ RESULT(4文字×読み出し数) + ETX(#03);// +CRLF(#13#10)
      異常終了時:
      '*HELLO*' + NAK(#$15) + '00FF' + ERRORCODE(2文字);//  + CRLF(#13#10)
      }
      if Pos('*HELLO*', res) = 1  then begin
        res := Copy(res, 8);
        if (Copy(res, 1, 1) = STX) and (Length(res) >= 8 - 2 + wordsCount*4) then begin
          // ワードデバイスの状態
          // 左から若い順
          s := Copy(res, 6, wordsCount*4);
          for i := 1 to s.Length div 2 do
            // ASCIIコードを文字列に変換
            readText := readText + Char(StrToInt('$' + s[i * 2 - 1] + s[i * 2]));
          Edit10.Text := readText;
        end
        else if (Copy(res, 1, 1) = NAK) and (Length(res) = 9 - 2) then begin
          // エラーコード
          Edit10.Text := 'ErrorCode = ' + Copy(res, 6, 2);
        end;
      end
      else
        Edit10.Text := 'NoRes';
    end;
    // 切断
    IdTCPClient1.Disconnect;
  except
    // エラーメッセージ
    on E: Exception do begin
      strTExt := E.ClassName + sLineBreak + E.Message;
      Application.MessageBox(PChar(strTExt), '情報', MB_ICONINFORMATION);
    end;
  end;
end;