FX5U TCP 通信 (2016/11/03, 2017/ 2/25 update)

・2017/ 2/25 Read / Write を訂正

・2016/11/03 バイナリ交信で、デバイス番号の指定方法が違っていたのを修正しました。
・2016/10/24a ASCII 更新で文字列書き込み、読み込みの文字列順が違うのを修正しました。
・2016/10/24 文字列書き込み、読み込みのコードを追加しました。
・2016/10/20 バイナリ交信のコードを追加しました。

■FX5U Ethernet ポートの設定
・IPアドレス、サブネットマスクを設定します。
・交信データコードは、パソコンから交信するデータに合わせます。(バイナリの時は、バイナリにします)
・相手機器接続構成設定の<詳細設定>をダブルクリックします。


・「ユニット一覧」から、「Ethernet 機器(汎用)」の「SLMP 接続機器」を赤矢印のように、ドラッグ&ドロップします。
・「SLMP接続機器」が追加されるので、プロトコルを TCP、ポート番号を 1025 に変更します。
・メニューの「設定を反映して閉じる」をクリックします。
・元の画面で、「適用」ボタンをクリックします。
・あとは、PLC へ PC パラメータを書き込み、PLC をリセットします。




■パソコン側のプログラム
・伝文フォーマットは、QCPU と同じ 「MC プロトコルの QnA 互換 3E フレーム」なので、QCPU との通信プログラムがそのまま動きます。

 Delphi 10.1 Belin Starter Edition で、Indy10 IdTCPClient コンポーネントを使用しています。


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

unit FX5UTCPUnit;

interface

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

type
  TForm1 = class(TForm)
    IdTCPClient1: TIdTCPClient;
    Button1: TButton;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Edit4: TEdit;
    Edit5: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// ASCII 交信で、読み込み
// Y0 ~ Y7
procedure TForm1.Button1Click(Sender: TObject);
var
  head, snd, cmd, res : string;
  iBits : integer;
  StrText : string;
  B : TBytes;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  EDit3.Text := '';

  // ビット単位読み出し数
  iBits := 8;

  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + '03FF'; // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  cmd := '0010'; // CPU監視タイマ(16進)
  //ビットデバイス(X,Y,M...など)を1点単位で読みだす
  cmd := cmd + '0401'; // コマンド
  cmd := cmd + '0001'; // サブコマンド
  cmd := cmd + 'Y*000000';  // 先頭デバイス名(8文字)
  cmd := cmd + IntToHex(iBits, 4); // 読み出し数(16進4桁)

  snd := head;
  snd := snd + IntToHex(cmd.Length, 4);
  snd := snd + cmd;

  // 送信文字列
  Edit1.Text := snd;

  // Indy Version 10.6.2.5341
  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        B := TEncoding.ANSI.GetBytes(snd);
        IOHandler.Write(TIdBytes(B), snd.Length);
        // レスポンス受信
        SetLength(B, 0);
        IOHandler.ReadBytes(TIdBytes(B), -1);
        res := TEncoding.ANSI.GetString(B);
        // 受信文字列
        Edit2.Text := res;
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (res.Length = 22 + iBits) and (Copy(res, 19, 4) = '0000') then
            // 正常取得(ビット単位で表示)
            // オンオフを文字列で表す
            // 文字列の左が先頭
            Edit3.Text := Copy(res, 23, iBits)
          else
            // エラーコード表示
            Edit3.Text := Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// ASCII 交信で、書き込み
// Y0 ~ Y7
procedure TForm1.Button2Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  StrText : string;
  B : TBytes;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  Edit3.Text := '';


  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + '03FF'; // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  // 送信データには、これ以降の文字列数が必要
  cmd := '0010'; // CPU監視タイマ(16進)
  //ビットデバイス(X,Y,M...など)を1点単位で書き込む
  cmd := cmd + '1401';     // コマンド
  cmd := cmd + '0001';     // サブコマンド
  cmd := cmd + 'Y*';       // デバイスコード
  cmd := cmd + '000000';   // 先頭デバイス名(6文字)
  cmd := cmd + '0008';     // デバイス点数
  cmd := cmd + '11001100'; // 書き込みデータ 0=ON, 1=OFF(デバイス点数分の文字数)

  snd := head;
  snd := snd + IntToHex(cmd.Length, 4); // 要求データ長(16進)
  snd := snd  + cmd;

  // 送信文字列
  Edit1.Text := snd;

  // Indy Version 10.6.2.5341
  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        B := TEncoding.ANSI.GetBytes(snd);
        IOHandler.Write(TIdBytes(B), snd.Length);
        // レスポンス受信
        SetLength(B, 0);
        IOHandler.ReadBytes(TIdBytes(B), -1);
        res := TEncoding.ANSI.GetString(B);
        // 受信文字列
        Edit2.Text := res;
        // 正常終了時:'D00000FF03FF0000040000'
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (Copy(res, 19, 4) = '0000') then begin
            // 正常終了
            Edit3.Text := 'OK';
          end
          else
            // エラーコード表示
            Edit3.Text := Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;

end;

// ASCII 交信で、書き込み
// Y0 ~ Y7
procedure TForm1.Button3Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  StrText : string;
  B : TBytes;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  Edit3.Text := '';


  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + '03FF'; // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  // 送信データには、これ以降の文字列数が必要
  cmd := '0010'; // CPU監視タイマ(16進)
  //ビットデバイス(X,Y,M...など)を1点単位で書き込む
  cmd := cmd + '1401';     // コマンド
  cmd := cmd + '0001';     // サブコマンド
  cmd := cmd + 'Y*';       // デバイスコード
  cmd := cmd + '000000';   // 先頭デバイス名(6文字)
  cmd := cmd + '0008';     // デバイス点数
  cmd := cmd + '00000000'; // 書き込みデータ 0=ON, 1=OFF(デバイス点数分の文字数)

  snd := head;
  snd := snd + IntToHex(cmd.Length, 4); // 要求データ長(16進)
  snd := snd  + cmd;

  // 送信文字列
  Edit1.Text := snd;

  // Indy Version 10.6.2.5341
  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        B := TEncoding.ANSI.GetBytes(snd);
        IOHandler.Write(TIdBytes(B), snd.Length);
        // レスポンス受信
        SetLength(B, 0);
        IOHandler.ReadBytes(TIdBytes(B), -1);
        res := TEncoding.ANSI.GetString(B);
        // 受信文字列
        Edit2.Text := res;
        // 正常終了時:'D00000FF03FF0000040000'
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (Copy(res, 19, 4) = '0000') then begin
            // 正常終了
            Edit3.Text := 'OK';
          end
          else
            // エラーコード表示
            Edit3.Text := Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// バイナリ交信のために、文字列のHLを反転
// ex '1234' -> '3412'
function RevStrHL(const s: string): string;
var
  i, n :integer;
begin
  n := s.Length;
  if n > 0 then begin
    for i := 0 to n div 4 - 1 do
      result := result + s[i * 4 + 3] + s[i * 4 + 4] + s[i * 4 + 1] + s[i * 4 + 2];
    if n mod 4 > 0 then
      for i := (n div 4) * 4 + 1 to n do
        result := result + s[i];
  end;
end;

// 2016/11/03 追加
// バイナリ交信のために、文字列のHLを反転
// ex '123456' -> '563412'
function RevStrHL3(const s: string): string;
var
  n :integer;
begin
  n := s.Length;
  if n = 6 then begin
    result := s[5] + s[6] + s[3] + s[4] + s[1] + s[2];
  end;
end;

// バイナリ交信で、書き込み
// Y0 ~ Y7
procedure TForm1.Button4Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  sndLen : integer;
  i : integer;
  StrText : string;
  buf: TIdBytes;
  s : string;
begin
  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + RevStrHL('03FF'); // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  // 送信データには、これ以降の文字列数が必要
  cmd := RevStrHL('0010'); // CPU監視タイマ(16進)

  //ビットデバイス(X,Y,M...など)を1点単位で書き込む
  cmd := cmd + RevStrHL('1401');     // コマンド
  cmd := cmd + RevStrHL('0001');     // サブコマンド

  // ASCII交信と順序が異なる
  // 2016/11/03 修正
  cmd := cmd + RevStrHL3(IntToHex(0, 6));   // 先頭デバイス名(6文字)、16進
  cmd := cmd + '9D';                 // 'M*'='90', 'Y*'='9D' デバイスコード

  cmd := cmd + RevStrHL('0008');     // デバイス点数
  // 書き込みデータ 0=ON, 1=OFF(デバイス点数分の文字数)
  // 文字列の左が、若い番号
 // 奇数の時は、ダミーを追加して偶数にする (Ex: '1' -> '10')
  cmd := cmd + '10000000';

  snd := head;
  // 要求データ長(16進)バイナリに変えるので、文字列長さの1/2
  snd := snd + RevStrHL(IntToHex(cmd.Length div 2 , 4));
  snd := snd  + cmd;

  // 送信データ数
  sndLen := snd.Length div 2;

  SetLength(buf, sndLen);
  // 2文字ずつバイナリに変換し、バッファに格納
  for i := 0 to snd.Length div 2 -1 do
    buf[i] := StrToInt('$' + Copy(snd, i * 2 + 1, 2));

  // 確認のためバッファを文字列として表示
  s := '';
  for i := 0 to Length(buf) -1 do
    s := s + IntToHex(buf[i], 2);

  Edit1.Text := s;

  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        IOHandler.Write(buf, sndLen);
        // レスポンス待ち
        while IOHandler.CheckForDataOnSource(10) do ;
        // 受信のため、バッファをクリア
        SetLength(buf, 0);
        // レスポンス受信
        IOHandler.ReadBytes(buf, -1);
        res := '';
        for i := 0 to Length(buf) -1 do
          res := res + IntToHex(buf[i], 2);

        // 受信文字列
        Edit2.Text := res;
        // 正常終了時:'D00000FF03FF0000040000'
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (Copy(res, 19, 4) = '0000') then begin
            // 正常終了
            Edit3.Text := 'OK';
          end
          else
            // エラーコード表示
            Edit3.Text := Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;

end;

// バイナリ交信で、書き込み
// Y0 ~ Y7
procedure TForm1.Button5Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  sndLen : integer;
  i : integer;
  StrText : string;
  buf: TIdBytes;
  s : string;
begin
  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + RevStrHL('03FF'); // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  // 送信データには、これ以降の文字列数が必要
  cmd := RevStrHL('0010'); // CPU監視タイマ(16進)

  //ビットデバイス(X,Y,M...など)を1点単位で書き込む
  cmd := cmd + RevStrHL('1401');     // コマンド
  cmd := cmd + RevStrHL('0001');     // サブコマンド

  // ASCII交信と順序が異なる
  // 2016/11/03 修正
  cmd := cmd + RevStrHL3(IntToHex(0, 6));   // 先頭デバイス名(6文字)、16進
  cmd := cmd + '9D';                 // 'M*='90'、'Y*'='9D'デバイスコード

  cmd := cmd + RevStrHL('0008');     // デバイス点数
  // 書き込みデータ 0=ON, 1=OFF(デバイス点数分の文字数)
  // 文字列の左が、若い番号
  // 奇数の時は、ダミーを追加し偶数にする (ex: '1' -> '10')
  cmd := cmd + '00000000';

  snd := head;
  // 要求データ長(16進)バイナリに変えるので、文字列長さの1/2
  snd := snd + RevStrHL(IntToHex(cmd.Length div 2 , 4));
  snd := snd  + cmd;

  // 送信データ数
  sndLen := snd.Length div 2;

  SetLength(buf, sndLen);
  // 2文字ずつバイナリに変換し、バッファに格納
  for i := 0 to snd.Length div 2 -1 do
    buf[i] := StrToInt('$' + Copy(snd, i * 2 + 1, 2));

  // 確認のためバッファを文字列として表示
  s := '';
  for i := 0 to Length(buf) -1 do
    s := s + IntToHex(buf[i], 2);

  Edit1.Text := s;

  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        IOHandler.Write(buf, sndLen);
        // レスポンス待ち
        while IOHandler.CheckForDataOnSource(10) do ;
        // 受信のため、バッファをクリア
        SetLength(buf, 0);
        // レスポンス受信
        IOHandler.ReadBytes(buf, -1);
        res := '';
        for i := 0 to Length(buf) -1 do
          res := res + IntToHex(buf[i], 2);

        // 受信文字列
        Edit2.Text := res;
        // 正常終了時:'D00000FF03FF0000040000'
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (Copy(res, 19, 4) = '0000') then begin
            // 正常終了
            Edit3.Text := 'OK';
          end
          else
            // エラーコード表示
            Edit3.Text := Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// バイナリ交信で、読み込み
// Y0 ~ Y7
procedure TForm1.Button6Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  sndLen : integer;
  i : integer;
  StrText : string;
  buf: TIdBytes;
  s : string;
  iBits : integer;
begin
  // ビット単位読み出し数
  iBits := 8;

  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + RevStrHL('03FF'); // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  // 送信データには、これ以降の文字列数が必要
  cmd := RevStrHL('0010'); // CPU監視タイマ(16進)

  //ビットデバイス(X,Y,M...など)を1点単位で読み込む
  cmd := cmd + RevStrHL('0401');     // コマンド
  cmd := cmd + RevStrHL('0001');     // サブコマンド

  // ASCII交信と順序が異なる
  // 2016/11/03 修正
  cmd := cmd + RevStrHL3(IntTiHex(0, 6));   // 先頭デバイス名(6文字) 16進表記
  cmd := cmd + '9D';                 // 'M*'='90'、'Y*'='9D'デバイスコード

  cmd := cmd + RevStrHL(IntToHex(iBits, 4));     // デバイス点数

  snd := head;
  // 要求データ長(16進)バイナリに変えるので、文字列長さの1/2
  snd := snd + RevStrHL(IntToHex(cmd.Length div 2 , 4));
  snd := snd  + cmd;

  // 送信データ数
  sndLen := snd.Length div 2;

  SetLength(buf, sndLen);
  // 2文字ずつバイナリに変換し、バッファに格納
  for i := 0 to snd.Length div 2 -1 do
    buf[i] := StrToInt('$' + Copy(snd, i * 2 + 1, 2));

  // 確認のためバッファを文字列として表示
  s := '';
  for i := 0 to Length(buf) -1 do
    s := s + IntToHex(buf[i], 2);

  Edit1.Text := s;

  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        IOHandler.Write(buf, sndLen);
        // レスポンス待ち
        while IOHandler.CheckForDataOnSource(10) do ;
        // 受信のため、バッファをクリア
        SetLength(buf, 0);
        // レスポンス受信
        IOHandler.ReadBytes(buf, -1);
        res := '';
        for i := 0 to Length(buf) -1 do
          res := res + IntToHex(buf[i], 2);

        // 受信文字列
        Edit2.Text := res;
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (res.Length = 22 + iBits) and (Copy(res, 19, 4) = '0000') then
            // 正常取得(ビット単位で表示)
            // オンオフを文字列で表す
            // 文字列の左が先頭
            Edit3.Text := Copy(res, 23, iBits)
          else
            // エラーコード表示
            Edit3.Text := Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

end.

// *************************
// 以下、2016/10/24 追加分
// *************************

// ASCII 文字列書き込み
procedure TForm1.Button12Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  strTExt : string;

  srcText, sndText : string;
  wordsCount  : integer;
  i : integer;
  s : string;
  n : integer;
  B : TBytes;
begin

  // 2文字ずつ入れ替え
  s := Edit12.Text;
  n := s.Length;
  for i := 0 to n div 2 -1 do
    srcText := srcText + Copy(s, i * 2 + 2, 1) + Copy(s, i * 2 + 1, 1);
  if n mod 2 > 0 then
    srcText := srcText + #00 + Copy(s, n, 1);

  // 文字列をASCIIコードに変換し、それを16進2桁の文字列にする
  // 文字列は、上位、下位で格納される
  for i := 1 to srcText.Length do
    sndText := sndText + IntToHex(Ord(srcText[i]), 2);

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

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


  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + '03FF'; // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  // 送信データには、これ以降の文字列数が必要
  cmd := '0010'; // CPU監視タイマ(16進)
  //ワードデバイス(D...など)を1ワード単位で書き込む
  cmd := cmd + '1401';     // コマンド
  cmd := cmd + '0000';     // サブコマンド
  cmd := cmd + 'D*';       // デバイスコード
  cmd := cmd + '000000';   // 先頭デバイス名(6文字)
  cmd := cmd + IntToHex(wordsCount, 4); // ワードデバイス点数
  cmd := cmd + sndText;     // 書き込みデータ

  snd := head;
  snd := snd + IntToHex(cmd.Length, 4); // 要求データ長(16進)
  snd := snd  + cmd;

  // 送信文字列
  Edit1.Text := snd;

  // Indy Version 10.6.2.5341
  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        B := TEncoding.ANSI.GetBytes(snd);
        IOHandler.Write(TIdBytes(B), snd.Length);
        // レスポンス受信
        SetLength(B, 0);
        IOHandler.ReadBytes(TIdBytes(B), -1);
        res := TEncoding.ANSI.GetString(B);
        // 受信文字列
        Edit2.Text := res;
        // 正常終了時:'D00000FF03FF0000040000'
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (Copy(res, 19, 4) = '0000') then begin
            // 正常終了
            Edit3.Text := 'OK';
          end
          else
            // エラーコード表示
            Edit3.Text := 'ErrorCode = ' + Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        strTExt := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(strTExt), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// ASCII 文字列読込
procedure TForm1.Button13Click(Sender: TObject);
var
  head, snd, cmd, res : string;
  strTExt : string;
  wordsCount : integer;
  readLen : integer;
  readText : string;
  src : string;
  i : integer;
  n : integer;
  s : string;
  B : TBytes;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  EDit3.Text := '';

  // 読み込み文字列数
  readLen := Length(Edit12.Text);
  // 読み込みワード数
  wordsCount := readLen div 2;
  if readLen mod 2 > 0 then  Inc(wordsCount);

  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + '03FF'; // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  cmd := '0010'; // CPU監視タイマ(16進)
  //ワードデバイス(D...など)を1ワード単位で読みだす
  cmd := cmd + '0401'; // コマンド
  cmd := cmd + '0000'; // サブコマンド
  cmd := cmd + 'D*000000';  // 先頭デバイス名(8文字)
  cmd := cmd + IntToHex(wordsCount, 4); // 読み出しワード数(16進4桁)

  snd := head;
  snd := snd + IntToHex(cmd.Length, 4);
  snd := snd + cmd;

  // 送信文字列
  Edit1.Text := snd;

  // Indy Version 10.6.2.5341
  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        B := TEncoding.ANSI.GetBytes(snd);
        IOHandler.Write(TIdBytes(B), snd.Length);
        // レスポンス受信
        SetLength(B, 0);
        IOHandler.ReadBytes(TIdBytes(B), -1);
        res := TEncoding.ANSI.GetString(B);
        // 受信文字列
        Edit2.Text := res;
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (res.Length = 22 + wordsCount * 4) and (Copy(res, 19, 4) = '0000') then begin
            // 正常取得(ビット単位で表示)
            // 文字列の左が先頭
            src := Copy(res, 23, wordsCount*4);
            for i := 1 to src.Length div 2 do
              // ASCIIコードを文字列に変換
              readText := readText + Char(StrToInt('$' + src[i * 2 - 1] + src[i * 2]));
 
           n := readText.Length;
            s := '';
            // 2文字ずつ入れ替え
            for i := 0 to n div 2 -1 do
              s := s + Copy(readText, i * 2 + 2, 1) + Copy(readText, i * 2 + 1, 1);
            if n mod 2 > 0 then
              s := s + Copy(readText, n, 1);
            Edit3.Text := s;
          end
          else
            // エラーコード表示
            Edit3.Text := 'ErrorCode = ' + Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        strTExt := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(strTExt), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// Binary 文字列書込
procedure TForm1.Button14Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  sndLen : integer;
  i : integer;
  strTExt : string;
  buf: TIdBytes;
  s : string;
  srcText, sndText : string;
  wordsCount : integer;
begin
  srcText := Edit12.Text;
  // 文字列をASCIIコードに変換し、それを16進2桁の文字列にする
  // 文字列は、上位、下位で格納される
  for i := 1 to srcText.Length do
    sndText := sndText + IntToHex(Ord(srcText[i]), 2);

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

  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号
  head := head + 'FF';   // PC番号
  head := head + RevStrHL('03FF'); // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号

  // 送信データには、これ以降の文字列数が必要
  cmd := RevStrHL('0010'); // CPU監視タイマ(16進)

  //ワードデバイス(D...など)を1ワード点単位で書き込む
  cmd := cmd + RevStrHL('1401');     // コマンド
  cmd := cmd + RevStrHL('0000');     // サブコマンド

  // ASCII交信と順序が異なる
  // 2016/11/03 修正
  cmd := cmd + RevStrHL3(IntToHex(0, 6));   // 先頭デバイス名(6文字) 16進表記
  cmd := cmd + 'A8';                 // 'D*'='A8'デバイスコード

  cmd := cmd + RevStrHL(IntToHex(wordsCount, 4));     // デバイス点数
  cmd := cmd + sndText;

  snd := head;
  // 要求データ長(16進)バイナリに変えるので、文字列長さの1/2
  snd := snd + RevStrHL(IntToHex(cmd.Length div 2 , 4));
  snd := snd  + cmd;

  // 送信データ数
  sndLen := snd.Length div 2;

  SetLength(buf, sndLen);
  // 2文字ずつバイナリに変換し、バッファに格納
  for i := 0 to snd.Length div 2 -1 do
    buf[i] := StrToInt('$' + Copy(snd, i * 2 + 1, 2));

  // 確認のためバッファを文字列として表示
  s := '';
  for i := 0 to Length(buf) -1 do
    s := s + IntToHex(buf[i], 2);

  Edit1.Text := s;

  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        IOHandler.Write(buf, sndLen);
        // レスポンス待ち
        while IOHandler.CheckForDataOnSource(10) do ;
        // 受信のため、バッファをクリア
        SetLength(buf, 0);
        // レスポンス受信
        IOHandler.ReadBytes(buf, -1);
        res := '';
        for i := 0 to Length(buf) -1 do
          res := res + IntToHex(buf[i], 2);

        // 受信文字列
        Edit2.Text := res;
        // 正常終了時:'D00000FF03FF0000040000'
        // レスポンスの判断
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (Copy(res, 19, 4) = '0000') then begin
            // 正常終了
            Edit3.Text := 'OK';
          end
          else
            // エラーコード表示
            Edit3.Text := 'ErrorCode = ' + Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        strTExt := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(strTExt), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// Binary 文字列読込
procedure TForm1.Button15Click(Sender: TObject);
var
  head, cmd, snd, res : string;
  sndLen : integer;
  i : integer;
  strTExt : string;
  buf: TIdBytes;
  s : string;
  wordsCount : integer;
  readLen : integer;
  readText : string;
begin
  // 読み込み文字列数
  readLen := Length(Edit12.Text);
  // 読み込みワード数
  wordsCount := readLen div 2;
  if readLen mod 2 > 0 then  Inc(wordsCount);

  // 送信文字列
  // QnA互換3Eフレーム
  head := '5000';   // サブヘッダ(PC->PLCへの伝文)
  head := head + '00';   // ネットワーク番号(PLC側の設定による)
  head := head + 'FF';   // PC番号(固定)
  head := head + RevStrHL('03FF'); // 要求先ユニットI/O番号
  head := head + '00';   // 要求先ユニット局番号(PLC側の設定による)

  // 送信データには、これ以降の文字列数が必要
  cmd := RevStrHL('0010'); // CPU監視タイマ(16進)

  //ワードデバイス(D...など)を1ワード単位で読み込む
  cmd := cmd + RevStrHL('0401');     // コマンド
  cmd := cmd + RevStrHL('0000');     // サブコマンド

  // ASCII交信と順序が異なる
  // 2016/11/03 修正
  cmd := cmd + RevStrHL3(IntToHex(0, 6));   // 先頭デバイス名(6文字)
  cmd := cmd + 'A8';                 // 'D*'='A8'デバイスコード

  cmd := cmd + RevStrHL(IntToHex(wordsCount, 4));     // デバイス点数

  snd := head;
  // 要求データ長(16進)バイナリに変えるので、文字列長さの1/2
  snd := snd + RevStrHL(IntToHex(cmd.Length div 2 , 4));
  snd := snd  + cmd;

  // 送信データ数
  sndLen := snd.Length div 2;

  SetLength(buf, sndLen);
  // 2文字ずつバイナリに変換し、バッファに格納
  for i := 0 to snd.Length div 2 -1 do
    buf[i] := StrToInt('$' + Copy(snd, i * 2 + 1, 2));

  // 確認のためバッファを文字列として表示
  s := '';
  for i := 0 to Length(buf) -1 do
    s := s + IntToHex(buf[i], 2);

  Edit1.Text := s;

  with IdTCPClient1 do begin
    // タイムアウトを設定
    ConnectTimeout := 1000;
    ReadTimeout := 200;

    // PLC の設定に合わせる
    // IPアドレス
    Host := Edit4.Text; //'169.254.17.243';
    // ポート番号
    Port := StrToIntDef(Edit5.Text, 1025);

    try
      // 接続
      if not Connected then
        Connect;
      if Connected then begin
        // コマンド送信
        IOHandler.Write(buf, sndLen);
        // レスポンス待ち
        while IOHandler.CheckForDataOnSource(10) do ;
        // 受信のため、バッファをクリア
        SetLength(buf, 0);
        // レスポンス受信
        IOHandler.ReadBytes(buf, -1);
        res := '';
        for i := 0 to Length(buf) -1 do
          res := res + IntToHex(buf[i], 2);

        // 受信文字列
        Edit2.Text := res;
        if (res.Length >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (res.Length = 22 + wordsCount * 4) and (Copy(res, 19, 4) = '0000') then begin
            // 正常取得
            s := Copy(res, 23, wordsCount * 4);
            for i := 1 to s.Length div 2 do
              // ASCIIコードを文字列に変換
              readText := readText + Char(StrToInt('$' + s[i * 2 - 1] + s[i * 2]));
            Edit3.Text := readText;
          end
          else
            // エラーコード表示
            Edit3.Text := 'ErrorCode = ' + Copy(res, 19, 4);
        end;
      end;
      // 切断
      Disconnect;
    except
      // エラーメッセージ
      on E: Exception do begin
        strTExt := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(strTExt), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;