Modbus/TCP スレーブ

FC03 / FC04 / FC06 に応答する Modbus スレーブを再現。Modbus マスタ(クライアント)アプリの開発・試験を実機なしで行えます。スクリプト応答で動的長応答を実装した比較用のスクリプト版(ポート 503)も同梱しています。

TCP 502スクリプト版 503MBAP 長さ前置ビッグエンディアン

概要

Modbus/TCP のファンクションコード FC03・FC04・FC06 の通信相手として振る舞うサンプルです。

MBAP ヘッダの長さ前置フレーミング、複数の構造体定義をファンクションコード(判別フィールド)で振り分ける仕組み、アドレス(フィールド条件)に応じた応答値の切り替えを試せます。スクリプト版(ポート 503)は、要求の Quantity に応じた動的長応答をインライン C# で実装した発展例です。

Modbus サンプルの構造体ルール一覧
「自動応答」タブ ②バイナリ構造応答:FC03(アドレス別)・FC04・FC06 のルールを優先度順に管理

ファイル構成

ファイル内容
modbus-tcp/modbus-tcp.commsimプロジェクト本体(構造体定義・構造体ルールを含む)
modbus-tcp/modbus-tcp.commsim-struct構造体定義のみを切り出した可搬ファイル(別プロジェクトへのインポート用)
modbus-tcp/modbus-tcp-script.commsimスクリプト応答版(動的長 FC03 応答・ポート 503)

機器構成

単一の TCP サーバセッション。Modbus マスタ(CommSim.Sample のシナリオ「Modbus」等)が接続します。

Modbus マスタテスト対象アプリ
CommSimModbus スレーブ(502 / スクリプト版 503)

通信フロー

FC03 はアドレスで値を出し分け、FC06 は要求をエコーします。

  • Master →FC03 読出 @addr0 qty2
  • ← CommSimレジスタ 100, 200
  • Master →FC03 読出 @addr10 qty2
  • ← CommSim0x1234, 0x5678
  • Master →FC04 入力レジスタ読出
  • ← CommSim250
  • Master →FC06 単一レジスタ書込
  • ← CommSim要求をエコー(同一内容)

想定プロトコル(MBAP)

Modbus/TCP の共通ヘッダ(MBAP)。Length 以降のバイト数が Length フィールドに入ります。

Txn2B Proto2B Length2B Unit1B FC1B 判別 Data可変
受信例(FC03)分解
00 01 00 00 00 06 01 03 00 00 00 02Txn=1 / FC=3 / StartAddr=0 / Quantity=2
応答 00 01 00 00 00 07 01 03 04 00 64 00 C82 レジスタ(100, 200)

使用する機能

メッセージ構造定義

FunctionCode を判別フィールドにして FC ごとに定義

バイナリ構造応答

FC で出し分け・アドレス(フィールド条件)で応答値切替

{ }

スクリプト応答

503 版:Quantity に応じた動的バイト長の FC03 応答(発展例)

設定内容

プロジェクト設定

既定データ形式RawHex(バイナリ HEX)
エンディアンBig
フレーミング方式長さ前置(LengthPrefixed)
共通ヘッダ長 / 長さ ofs / size7B / 4 / 2
長さ基準 / 補正長さフィールド以降 / 0

構造体定義(FC で判別)

定義判別値主なフィールド
FC03 ReadHoldingRegistersFunctionCode = 3StartAddress / Quantity
FC04 ReadInputRegistersFunctionCode = 4StartAddress / Quantity
FC06 WriteSingleRegisterFunctionCode = 6Address / Value

構造体ルール

優先度の昇順で評価され、最初にマッチした応答テンプレートを返します。

ルール名対象構造体フィールド条件優先度応答テンプレート(HEX)
FC03 @addr0 → 100,200FC03 ReadHoldingRegistersStartAddress = 010{field:TransactionId} 00 00 00 07 {field:UnitId} 03 04 00 64 00 C8
FC03 @addr10 → 0x1234,0x5678FC03 ReadHoldingRegistersStartAddress = 1011{field:TransactionId} 00 00 00 07 {field:UnitId} 03 04 12 34 56 78
FC03 その他アドレス → 0,0FC03 ReadHoldingRegisters(条件なし)20{field:TransactionId} 00 00 00 07 {field:UnitId} 03 04 00 00 00 00
FC04 入力レジスタ → 250FC04 ReadInputRegisters(条件なし)30{field:TransactionId} 00 00 00 05 {field:UnitId} 04 02 00 FA
FC06 単一書込 → エコー応答FC06 WriteSingleRegister(条件なし)40{field:TransactionId} 00 00 00 06 {field:UnitId} 06 {field:RegisterAddress} {field:RegisterValue}

設定の考え方

設定なぜそうするか
フレーミングを長さ前置(共通ヘッダ 7・長さオフセット 4・サイズ 2・基準「長さフィールド以降」)Modbus/TCP は MBAP ヘッダ(7 バイト)の中の Length フィールドに「UnitId+PDU のバイト数」が入る仕様だから。TCP ストリームから 1 要求を正しく切り出すには、この Length の意味に合わせて基準を選ぶ必要がある。
ファンクションコードを判別フィールドにして定義を 3 つに分ける同じヘッダでも FC ごとに後続フィールドの構成が違うから。FC で分岐させた方が分解表示も応答も正確になる。
FC03 をアドレス+優先度で 3 ルールに分ける「特定アドレスは固定値を返し、それ以外は既定値(0,0)を返す」を表現するため。先勝ち評価で『特定→既定』の順に判定できる。
応答に {field:TransactionId}{field:UnitId} をエコーModbus/TCP では応答の TransactionId・UnitId を要求と一致させる必要があるから。
FC06 は要求をそのままエコーする応答テンプレートModbus 仕様で「単一レジスタ書込の正常応答 = 要求と同一フレーム」だから。
開き方:samples/modbus-tcp/modbus-tcp.commsim(502)を「ファイル」→「ファイルから開く…」(Ctrl+O)。CommSim.Sample のシナリオ「Modbus」の「デモ送信」で FC03@0 → FC03@10 → FC04 → FC06 の 4 要求を一括確認できます。

動作確認手順

  1. CommSim で samples/modbus-tcp/modbus-tcp.commsim を開く(メニューバー「ファイル」→「ファイルから開く…」)。
  2. セッション「Modbus TCP スタブ」の「起動」ボタンをクリックし、待ち受け状態にする。
  3. クライアントから FC03@0 の要求を送る。
  4. ログペインに受信(Rx)が表示され、フィールド分解(TransactionId・UnitId・FunctionCode・StartAddress 等)が確認できる。
  5. 同タブに送信(Tx)としてレジスタ 100, 200 の応答が表示される。
  6. FC03@10・FC04・FC06 の要求を順に送り、それぞれ異なる応答が返ることを確認する。

実行ログサンプル

Rx  00 01 00 00 00 06 01 03 00 00 00 02
      [TransactionId=0x0001] [ProtocolId=0x0000] [Length=0x0006]
      [UnitId=0x01] [FunctionCode=3(ReadHoldingRegisters)] [StartAddress=0] [Quantity=2]
Tx  00 01 00 00 00 07 01 03 04 00 64 00 C8

Rx  00 02 00 00 00 06 01 03 00 0A 00 02
      [TransactionId=0x0002] [FunctionCode=3(ReadHoldingRegisters)] [StartAddress=10] [Quantity=2]
Tx  00 02 00 00 00 07 01 03 04 12 34 56 78

Rx  00 03 00 00 00 06 01 04 00 00 00 01
      [FunctionCode=4(ReadInputRegisters)]
Tx  00 03 00 00 00 05 01 04 02 00 FA

Rx  00 04 00 00 00 06 01 06 00 05 00 FF
      [FunctionCode=6(WriteSingleRegister)] [RegisterAddress=5] [RegisterValue=255]
Tx  00 04 00 00 00 06 01 06 00 05 00 FF

スクリプト応答版(503)

スクリプト応答は既定で無効です。初回はウィンドウ上部のバナー、またはプロジェクト設定の「スクリプト / プラグイン」タブで有効化してから起動してください(→ セキュリティについて)。基本的な書き方はチュートリアル「スクリプトで動的応答を作る」を参照してください。

samples/modbus-tcp/modbus-tcp-script.commsim(ポート 503)は、上記テンプレート版と同じ Modbus FC03 応答をインライン C# スクリプトで実装した比較教材です。テンプレートでは固定本数しか返せない制約を、スクリプトで動的長応答に変える実装例です。

テンプレート版との違い

テンプレート版は返すレジスタ本数(バイト数)が固定ですが、スクリプト版は受信から Quantity を読み取り、その本数分のレジスタ値を生成して MBAP 長さフィールドも正しく計算して返します。

FC03 @addr0 qty2 → 00 01 00 00 00 07 01 03 04 00 64 00 C8
FC03 @addr0 qty4 → 00 03 00 00 00 0B 01 03 08 00 64 00 C8 00 02 00 03
                                    ↑ Quantity に応じてバイト数・MBAP長が変化

既知アドレスの応答値

StartAddress返す値
0100(0x0064)
1200(0x00C8)
100x1234
110x5678
その他アドレス値そのまま

詳細は スクリプト応答 を参照してください。

← サンプル一覧に戻る