Delphi OMRON FINS/TCP 通信 2020.05.03

非常に分かりにくいですが、サンプルコードがあったので、何とかなりました。
FINS/TCP では、接続後FINS ノードアドレス情報の取得が必要で、取得後コネクション確立となり、切断まで有効となるようです。


unit OmronCJ2MTestUnit7;

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
  TForm7 = class(TForm)
    Memo1: TMemo;
    Button2: TButton;
    IdTCPClient1: TIdTCPClient;
    procedure Button2Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form7: TForm7;

implementation

{$R *.dfm}

{
  下記マニュアルは必須
  ・SYSMAC CS/CJシリーズ Ethernetユニット ユーザーズマニュアル アプリケーション構築編 SBCD-330
   「7-4 FINS/TCP 方式」
  ・SYSMAC CS/CJ/CPシリーズ SYSMAC One NSJシリーズ 通信コマンドリファレンスマニュアル SBCA-304
    「5 FINSコマンドリファレンス」
  注意
  ・「FINS コマンドフレームの作成」は接続時に1回だけ必要
   FINS/TCP クライアントとしてコネクションが確立した後、再び送信してはいけない
}

procedure TForm7.Button2Click(Sender: TObject);
// uses ... , IdGlobal;
var
  AData, BData : TBytes;
  i: integer;
  s : string;
  ret : Boolean;
  cli_node_no, srv_node_no : Byte;
  sid : Byte;
begin
  sid := 0;

  IdTCPClient1.Host := '192.168.250.1';
  IdTCPClient1.Port := 9600;

  IdTCPClient1.ConnectTimeout := 2000;
  IdTCPClient1.ReadTimeout := 200;

  IdTCPClient1.Connect;
  if IdTCPClient1.Connected then begin
    Memo1.Lines.Clear;
    Memo1.Lines.Add('Connected');

    // -------------------------------------
    // FINS ノードアドレス情報送信コマンド
    // クライアント(PC) -> サーバ(PLC )
    // -------------------------------------
    // FINS/TCP クライアントとしてコネクションが確立した後、
    // このコマンドを再び送信してはいけない!

    SetLength(AData, 20);
    // ヘッダ (4バイト):'FINS'
    AData[0] := $46;
    AData[1] := $49;
    AData[2] := $4E;
    AData[3] := $53;

    // コマンド以降のデータ長 (4バイト)
    // $0C = 12 バイト
    AData[4] := $00;
    AData[5] := $00;
    AData[6] := $00;
    AData[7] := $0C;

    // コマンド (4バイト)
    AData[8]  := $00;
    AData[9]  := $00;
    AData[10] := $00;
    AData[11] := $00;

    // エラーコード (4バイト): 未使用
    AData[12] := $00;
    AData[13] := $00;
    AData[14] := $00;
    AData[15] := $00;


    // FINS/TCP クラインアントの FINS ノードアドレス
    // 0 = 自動取得
    AData[16] := $00;
    AData[17] := $00;
    AData[18] := $00;
    AData[19] := $00;

    // 送信
    IdTCPClient1.IOHandler.Write(TIdBytes(AData), 20);

    // レスポンス受信
    SetLength(AData, 0);
    IdTCPClient1.IOHandler.ReadBytes(TIdBytes(Adata), -1);

    // 受信データ数 = 24
    ret := (Length(AData) = 24) and (AData[8] = 0) and (AData[9] = 0) and (AData[10] = 0) and (AData[11] = 1);
    if not ret then Memo1.Lines.Add('TCP receive error');

    if ret then begin
      // ノードアドレスを保持
      cli_node_no := AData[19]; // PC
      srv_node_no := AData[23]; // PLC
      Memo1.Lines.Add('FINS/TCP client Node No.= ' + cli_node_no.ToHexString);
      Memo1.Lines.Add('FINS/TCP server Node No.= ' + srv_node_no.ToHexString);


      // 必ず「FINS フレーム送信コマンド」を FINS フレームの先頭に付加する
      // 「FINS フレーム送信コマンド」内のデータ長から、その後に続く TCP データから、
      // FINSフレームが切り出される

      // -------------------------------------
      // FINS コマンドフレームの作成
      // -------------------------------------
      SetLength(AData, 16);
      // ヘッダ:'FINS'
      AData[0] := $46;
      AData[1] := $49;
      AData[2] := $4E;
      AData[3] := $53;

      // コマンド以降のデータ長 (4バイト)
      // $0C = 12 バイト
      AData[4] := $00;
      AData[5] := $00;
      AData[6] := $00;
      AData[7] := 8 + 18; // コマンド以降 FINS フレーム末端までのデータ長

      // コマンド (4バイト)
      AData[8]  := $00;
      AData[9]  := $00;
      AData[10] := $00;
      AData[11] := $02;

      // エラーコード (4バイト) 未使用
      AData[12] := $00;
      AData[13] := $00;
      AData[14] := $00;
      AData[15] := $00;

      // FINS/TCP コマンドの送信
      IdTCPClient1.IOHandler.Write(TIdBytes(AData), 16);

      // -------------------------------------
      // 変数エリアの読み出しコマンド
      // -------------------------------------
      SetLength(AData, 18);

      AData[0] := $80; // ICF(インフォメーション・コントロール・フィールド)
      AData[1] := $00; // RSV(システム予約項目) 固定
      AData[2] := $02; // GCT(許容ゲートウェイ通過数)固定

      AData[3] := $00; // DNA (送信先 FINS ネットワークアドレス)00 Hex :自ネットワーク
      AData[4] := srv_node_no; // DA1: Ethernetユニットの FINS ノードアドレス
      AData[5] := $00; // DA2 (送信先号機アドレス)00 Hex :PLC 本体(CPU ユニット)

      AData[6] := $00; // SNA (送信元 FINS ネットワークアドレス)
      AData[7] := cli_node_no; // 自動取得した PC 側の FINS ノードアドレス
      AData[8]  := $00; // SA2 (送信元 FINS ネットワークアドレス)

      // SID: 発信元のプロセス識別子 00~FF Hex の任意のデータを指定
      // レスポンスを返信するノードはコマンドの「SID」と同じ値を返すので、
      // 同一ユニットに連続してコマンドを発行したときなどに、レスポンスの判定用に使用
      Inc(sid);
      AData[9]  := sid; // SID

      // FINS コマンドコード 0101 :連続した I/O メモリエリアの内容の読み出し
      AData[10] := $01; // MRC
      AData[11] := $01; // SRC

      AData[12] := $82; // 変数種別:DM

      // 読み出し開始アドレス(3バイト)
      // 2 バイト(16 進 4 桁)がチャネル指定、1 バイト(16 進 2 桁)がビット指定
      // 計 3 バイト(16 進 6 桁)
      AData[13] := $00; // 読み出し開始アドレス(3バイト):100CH
      AData[14] := $64;
      AData[15] := $00;

      AData[16] := $00; // 読み出しCH 数(2バイト):150CH
      AData[17] := $96;

      s := '';
      for i := 0 to Length(AData) -1 do
        s := s + IntToHex(AData[i], 2) + ' ';
      Memo1.Lines.Add('cmd:' + #13#10 + s);

      // FINS コマンドフレームの送信
      IdTCPClient1.IOHandler.Write(TIdBytes(AData), 18);

      // レスポンス受信
      SetLength(BData, 0);
      IdTCPClient1.IOHandler.ReadBytes(TIdBytes(Bdata), -1);

      // 返信データ数は 2バイト(1CH) × 150CH = 300バイト +固定部分(30バイト)
      Memo1.Lines.Add('resLength=' + Length(BData).ToString); // 150CH:330bytes

      s := '';
      for i := 0 to Length(BData) -1 do
        s := s + IntToHex(BData[i], 2) + ' ';
      Memo1.Lines.Add('res:' + #13#10 + s);

      // 受信データには、コマンドフレーム(16バイト)と FINS レスポンスフレーム(14バイト以上)が含まれる
      if Length(BData) < (16 + 14) then begin
        Memo1.Lines.Add('FINS length error');
      end
      else if (AData[3] <> BData[16 + 6]) or (AData[4] <> BData[16 + 7]) or (AData[5] <> BData[16 + 8]) then begin
        Memo1.Lines.Add('illegal source address error');
        s := '';
        for i := 3 to 5 do
          s := s + IntToHex(AData[i], 2) + ':'+ IntToHex(BData[i + 16 + 3], 2) + ' ';
        Memo1.Lines.Add('address= ' + s);
      end
      else if (AData[9] <> BData[9 + 16]) then begin
        Memo1.Lines.Add('illegal SID error');
      end
      else begin
        // 正常受信

      end;
    end;
    // 切断
    IdTCPClient1.Disconnect;
  end;
end;

end.