MELSEC Q03UDECPU Ethernet を Delphi XE5 (Indy10 TCP/UDP) を使う (2015/ 1/ 3 , 2017/ 2/25 更新)

IndyTCPClientIndeyUDPServer を使って、Q03UDECPU と通信します。

PLC側のPCパラメータオープン設定:
 ・TCP MCプロトコル 0401(ポート16進表示(10進だと、1025))
 ・UDP MCプロトコル 0402(ポート16進表示(10進だと、1026))

送信する文字列、返信される文字列は、TCP/UDP とも、まったく同じです。

TCP や RS232Cは、OPEN に時間がかかります。(MX Component も)
なので、アプリ起動と同時に OPEN 、終了とともに CLOSE するのが良いと思います。
実用するには、途中、切断されたら、自動的に再接続する処理が必要です。
また、周期的にデータを取得、操作する場合は、別スレッドにしたほうが、良いと思います。

UDP の場合は、アプリ起動と同時にポート 1026 を開いて、受信待ちにします。
あとは、勝手にコマンド送信するだけです。(相手に届いているかどうかは気にしない)


// IOHandler.Write.., Read.. を訂正 (2017/ 2/25)

unit MelsecQTCPUnit1;

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, IdSocketHandle, IdUDPBase,
  IdUDPServer, IdGlobal;

type
  TForm1 = class(TForm)
    IdTCPClient1: TIdTCPClient;
    Edit1: TEdit;
    Button1: TButton;
    Edit2: TEdit;
    Button2: TButton;
    IdUDPServer1: TIdUDPServer;
    Button3: TButton;
    Edit3: TEdit;
    Edit4: TEdit;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Edit5: TEdit;
    Edit6: TEdit;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Button4: TButton;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    Label10: TLabel;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
      const AData: TIdBytes; ABinding: TIdSocketHandle);
    procedure Button4Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    // UDPにて受信文字列数確認のためのビット数
    gb_iBits : integer;
    // UDPにて受信後の処理分けのための番号
    gb_iCmdNo : integer;
    // 処理時間計測
    gb_Ticks : Cardinal;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

// *******************************
// 読み出し(TCP)
// *******************************
procedure TForm1.Button1Click(Sender: TObject);
var
  cmd, res : string;
  iBits : integer;
  n : integer;
  StrText : String;
  Ticks : Cardinal;
  B : TBytes;
begin
  Edit1.Text := '';
  Edit2.Text := '';
  EDit3.Text := '';

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

  // 送信文字列
  // QnA互換3Eフレーム
  cmd := '5000';       // サブヘッダ(PC->PLCへの伝文)
  cmd := cmd + '00';   // ネットワーク番号
  cmd := cmd + 'FF';   // PC番号
  cmd := cmd + '03FF'; // 要求先ユニットI/O番号
  cmd := cmd + '00';   // 要求先ユニット局番号
  cmd := cmd + '0018'; // 要求データ長(16進)= 24バイト=これ以下の文字数
  cmd := cmd + '0010'; // CPU監視タイマ(16進)

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

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

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

    // PLC の設定に合わせる
    // IPアドレス
    Host := '169.254.251.71';
    // ポート番号
    Port := 1025;

    // 処理時間の計測を開始
    gb_Ticks := GetTickCount;
    try
      // 接続
      if not Connected then
        Connect;
      Ticks := GetTickCount;
      if Connected then begin
        // コマンド送信
        B := TEncoding.ANSI.GetBytes(cmd);
        IOHandler.Write(TIdBytes(B), cmd.Length);

        // レスポンス受信
        SetLength(AData, 0);
        IOHandler.ReadBytes(TIdBytes(B), -1);
        res := TEncoding.ANSI.GetString(B);        

        // 受信文字列
        Edit2.Text := res;
        n := res.Length;
        // レスポンスの判断
        if (n >= 22) and (Copy(res, 1, 4) = 'D000') then begin
          if (n = 22 + iBits) and (Copy(res, 19, 4) = '0000') then
            // 正常取得(ビット単位で表示)
            // オンオフを文字列で表す
            // 文字列の左が先頭
            Edit3.Text := Copy(res, 23 ,iBits)
          else
            // エラーコード表示
            Edit3.Text := Copy(res, 19, 4);
        end;
      end;
      // OPEN-CLOSE を含まない処理時間
      Label10.Caption := IntToStr(GetTickCount - Ticks);
      // 切断
      Disconnect;
      // OPEN-CLOSE を含む時間
      Label9.Caption := IntToStr(GetTickCount - gb_Ticks);
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// *******************************
// 書き込み(TCP)
// *******************************
procedure TForm1.Button2Click(Sender: TObject);
var
  cmd, res : string;
  sBits : string;
  n : integer;
  StrText : String;
  Ticks : Cardinal;

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

  // ビット操作文字列
  // オンオフを文字列で表す
  // 文字列の左が先頭
  sBits := '1101001000111101';

  // 送信文字列
  // QnA互換3Eフレーム
  cmd := '5000';       // サブヘッダ(PC->PLCへの伝文)
  cmd := cmd + '00';   // ネットワーク番号
  cmd := cmd + 'FF';   // PC番号
  cmd := cmd + '03FF'; // 要求先ユニットI/O番号
  cmd := cmd + '00';   // 要求先ユニット局番号
  cmd := cmd + IntToHex(24 + Length(sBits), 4); //要求データ長(16進)= これ以下の文字数
  cmd := cmd + '0010';  // CPU監視タイマ(16進)

  //ビットデバイス(X,Y,M...など)を1点単位で書き込む
  cmd := cmd + '1401';     // コマンド
  cmd := cmd + '0001';     // サブコマンド
  cmd := cmd + 'X*000000'; // 先頭デバイス名(8文字)
  cmd := cmd + IntToHex(Length(sBits), 4); // 読み出し数(16進4桁)
  cmd := cmd + sBits;

  Edit1.Text := cmd;

  // Indy Version 10.6.0.5040
  with IdTCPClient1 do begin

    // タイムアウトを設定
    ConnectTimeout := 100;
    ReadTimeout := 100;

    // PLC の設定に合わせる
    // IPアドレス
    Host := '169.254.251.71';
    // ポート番号
    Port := 1025;
    // 処理時間の計測を開始
    gb_Ticks := GetTickCount;
    try
      // 接続
      if not Connected then
        Connect;
      Ticks := GetTickCount;
      if Connected then begin
        // コマンド送信
        IOHandler.WriteLn(cmd);
        try
          // レスポンス待ち
          while IOHandler.CheckForDataOnSource(10) do ;
        finally
          // レスポンス受信
          res := IOHandler.InputBufferAsString();
          // 受信文字列
          Edit2.Text := res;
          n := res.Length;

          // レスポンスの判断
          if (n >= 22) and (Copy(res, 1, 4) = 'D000') then begin
            if (Copy(res, 19, 4) = '0000') then
              // 正常終了
              Edit3.Text := 'OK'
            else
              // エラーコード表示
              Edit3.Text := Copy(res, 19, 4);
          end;
        end;
      end;
      // OPEN-CLOSE を含まない処理時間
      Label10.Caption := IntToStr(GetTickCount - Ticks);
      // 切断
      Disconnect;
      // OPEN-CLOSE を含む処理時間
      Label9.Caption := IntToStr(GetTickCount - gb_Ticks);
    except
      // エラーメッセージ
      on E: Exception do begin
        StrText := E.ClassName + sLineBreak + E.Message;
        Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
      end;
    end;
  end;
end;

// *******************************
// 読み出し(UDP)
// *******************************
procedure TForm1.Button3Click(Sender: TObject);
var
  cmd : string;
  // iBits : Integer;
  StrText : String;
begin
  Edit4.Text := '';
  Edit5.Text := '';
  Edit6.Text := '';

  // ビット単位読み出し数
  gb_iBits := 16;

  // 送信文字列
  // QnA互換3Eフレーム
  cmd := '5000';       // サブヘッダ(PC->PLCへの伝文)
  cmd := cmd + '00';   // ネットワーク番号
  cmd := cmd + 'FF';   // PC番号
  cmd := cmd + '03FF'; // 要求先ユニットI/O番号
  cmd := cmd + '00';   // 要求先ユニット局番号
  cmd := cmd + '0018'; // 要求データ長(16進)= 24バイト=これ以下の文字数
  cmd := cmd + '0010'; // CPU監視タイマ(16進)

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

  // 受信のために、送信番号を記憶
  gb_iCmdNo := 101;
  // 送信文字列
  Edit4.Text := cmd;

  // Indy Version 10.6.0.5040
  // uses に IdGlobal を追加すること
  try
    // 処理時間の計測を開始
    gb_Ticks := GetTickCount;
    // Host , Port は、PLC の設定に合わせる
    IdUDPServer1.Send('169.254.251.71', 1026, cmd, IndyTextEncoding_ASCII);
  except
    // エラーメッセージ
    on E: Exception do begin
      StrText := E.ClassName + sLineBreak + E.Message;
      Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
    end;
  end;
end;

// *******************************
// 書き込み(UDP)
// *******************************
procedure TForm1.Button4Click(Sender: TObject);
var
  cmd : string;
  sBits : string;
  StrText : String;
  //res : string;
  Host : string;
  Port : Word;
  //n : integer;
begin
  Edit4.Text := '';
  Edit5.Text := '';
  Edit6.Text := '';

  // ビット操作文字列
  // オンオフを文字列で表す
  // 文字列の左が先頭
  sBits := '1111001000111111';

  // 送信文字列
  // QnA互換3Eフレーム
  cmd := '5000';       // サブヘッダ(PC->PLCへの伝文)
  cmd := cmd + '00';   // ネットワーク番号
  cmd := cmd + 'FF';   // PC番号
  cmd := cmd + '03FF'; // 要求先ユニットI/O番号
  cmd := cmd + '00';   // 要求先ユニット局番号
  cmd := cmd + IntToHex(24 + Length(sBits), 4); //要求データ長(16進)= これ以下の文字数
  cmd := cmd + '0010';  // CPU監視タイマ(16進)

  //ビットデバイス(X,Y,M...など)を1点単位で書き込む
  cmd := cmd + '1401';     // コマンド
  cmd := cmd + '0001';     // サブコマンド
  cmd := cmd + 'X*000000'; // 先頭デバイス名(8文字)
  cmd := cmd + IntToHex(Length(sBits), 4); // 読み出し数(16進4桁)
  cmd := cmd + sBits;

  // 送信文字列
  Edit4.Text := cmd;
  // 受信のために送信番号を記憶
  gb_iCmdNo := 102;

  try
    // 処理時間の計測を開始
    gb_Ticks := GetTickCount;
    // Indy Version 10.6.0.5040
    // Host , Port は、PLC の設定に合わせる
    Host := '169.254.251.71';
    Port := 1026;
    IdUDPServer1.Send(Host, Port, cmd, IndyTextEncoding_ASCII);
  except
    // エラーメッセージ
    on E: Exception do begin
      StrText := E.ClassName + sLineBreak + E.Message;
      Application.MessageBox(PChar(StrText), '情報', MB_ICONINFORMATION);
    end;
  end;
end;

// *******************************
// 読み出し、書き込みの受信(UDP)
// *******************************
procedure TForm1.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
  const AData: TIdBytes; ABinding: TIdSocketHandle);
var
  res : string;
  n : integer;
begin
  // 受信文字列
  res := BytesToString(AData, IndyTextEncoding_ASCII);
  Edit5.Text := res;
  n := res.Length;

  // レスポンスの判断
  if (n >= 22) and (Copy(res, 1, 4) = 'D000') then begin
    if (Copy(res, 19, 4) = '0000') then begin
      // 送信番号により、処理を分ける
      if (gb_iCmdNo = 101) and (n = 22 + gb_iBits) then begin
        // 正常取得(ビット単位で表示)
        // オンオフを文字列で表す
        // 文字列の左が先頭
        Edit6.Text := Copy(res, 23 , gb_iBits)
      end
      else if gb_iCmdNo = 102 then begin
        // 書き込み正常終了
        Edit6.Text := 'OK';
      end;
    end
    else
      // エラーコード表示
      Edit6.Text := Copy(res, 19, 4);
  end;
  // 処理時間を表示
  Label9.Caption := IntToStr(GetTickCount - gb_Ticks);
  Label10.Caption := '';
end;
// *******************************
// UDP
// 起動と同時にポートをオープンし、受信を開始する
// *******************************
procedure TForm1.FormCreate(Sender: TObject);
begin
  IdUDPServer1.DefaultPort := 1026;
  IdUDPServer1.Active := True;
end;

end.