# Modbus TCP

> TCP/IP 越しの Modbus のレジスターとコイル。型付きのアドレス指定、バイト順の指定、そして上限のあるネットワークのスキャン。

Modbus TCP のドライバーは、TCP/IP 越しに Modbus のレジスターとコイルを読み書きします。相手は PLC、計器、ゲートウェイ、そして Modbus/TCP を話すあらゆるサーバーです。アドレス指定は完全に型付きです。レジスターの種類と 0 起算のレジスター番号を選べば、アプリがバス上の要求を組み立てます。書き間違えるような自由記述のアドレスはありません。

## 機器 {#device}

| 項目 | 意味 | 既定 |
| --- | --- | --- |
| **ホスト** | Modbus サーバーかゲートウェイの DNS 名、IPv4、IPv6 のアドレス。 | 空：入力されるまで機器は接続されません |
| **ポート** | TCP のポート（1 から 65535）。欄が空のときは 502 が灰色で表示され、機器はそのポートで呼び出されます。0 で保存された機器も同じように表示されます。 | 502 |
| **ユニット ID** | すべての要求が運ぶ Modbus のユニット（スレーブ）の ID で、ゲートウェイがその奥の機器へ振り分けるときに使うものです。0 から 255。 | 1 |
| **フレーム形式** | 各要求が接続をどう流れるかです。**MBAP** は要求ごとに Modbus TCP のヘッダーを付けます。**RTU** は CRC 付きの RTU フレームをそのまま送り、透過モードのシリアル-Ethernet 変換器向けです（[TCP 上の RTU](#rtu-over-tcp) を参照）。ホスト、ポート、ユニット ID、タグはどちらでも同じです。 | MBAP |
| **通信の最小間隔（ms）** | 互換のための間隔調整です。1 回のトランザクションが終わってから次が始まるまでの最小の休止で、0 から 1000 です。0 なら人為的な待ちを加えません。 | 0 |
| **読み出し周期（ms）** | 上書きしないタグに対する既定の読み出しの速さ。 | 1000 |

パネルの見出しに出る基準 URI は `modbus+tcp://host:port/unitID` で、間隔を設定していれば `?minimumTransactionIntervalMs=n` が付きます。

ホストとポートを共有する機器は、1 つのトランザクションの門も共有します。同じ接続先への要求が重なることは決してなく、その接続先の有効な機器のうち**最大**の通信の最小間隔が、その全部の間隔を決めます。ほかの機器がここで設定した値より実効の間隔を引き上げているときは、パネルがそう伝えます。この間隔が支配するのは、このアプリケーションが出す通信だけです。応答の遅いサーバーやゲートウェイのための互換の補助であって、ネットワークの不具合を直すものではありません。

接続は最大 5 秒、応答は最大 10 秒待ちます。プロトコルの水準での再試行はありません。再接続は背後の再試行の繰り返しが受け持つので、電源の入っていない機器があっても、品質の悪い読み取りが出る以外の損はありません。

## TCP 上の RTU {#rtu-over-tcp}

透過モードのシリアル-Ethernet 変換器は、自分では Modbus TCP を話しません。ソケットに届いたものをそのままシリアルバスへ出し、バスが答えたものを返すだけです。その奥の機器は、**フレーム形式**を **RTU** にした Modbus TCP の機器です。ステーションは MBAP のヘッダーを付けずに RTU フレーム（ユニット ID、要求、CRC）を送り、CRC が合う応答だけを受け入れます。このとき基準 URI のクエリには `framing=rtu` が付きます。たとえば `modbus+tcp://192.168.0.20:502/1?framing=rtu` です。**MBAP** の機器は、これまでどおりのアドレスのままです。このページのほかの内容は変わりません。レジスターの種類、機能コード、書き込みの待ち行列、タイムアウト、通信の間隔はそのまま当てはまります。

- **ユニット ID** は、変換器の奥にあるシリアルバス上の機器のアドレスで、1 から 247 です。
- 1 台の変換器の奥にある機器は、そのホストとポートを共有し、それに伴って通信の関門も共有します。一度に 1 つの要求だけで、これはシリアルバスが運べるすべてでもあります。変換器がフレームの間に休止を必要とするなら、**通信の最小間隔（ms）**を大きくしてください。
- 変換器自身のシリアルの設定（ボーレート、形式）は変換器の側で設定します。ステーションからは見えません。
- RTU フレームにはトランザクション番号がないので、ステーションは要求のたびに、変換器がすでに送ってきたものを捨てます。要求がタイムアウトした後に届いた応答が、次の要求の応答と取り違えられることはありません。

## タグのアドレス指定 {#tag-addressing}

| 項目 | 意味 | 既定 |
| --- | --- | --- |
| **レジスターの種類** | タグが指す Modbus の対象です：コイル（0xxxx）、ディスクリート入力（1xxxx）、入力レジスター（3xxxx）、保持レジスター（4xxxx）。どれもマニュアルのアドレスがその種類に付ける先頭の数字を添えているので、30001 とだけ印刷したマニュアルは入力レジスターを指していると分かります。 | 型とアクセスから導かれます。ブール値のタグはディスクリート入力（読み取り専用）かコイル（書き込める場合）、ほかの型は入力レジスター（読み取り専用）か保持レジスター（書き込める場合）。 |
| **レジスター番号（0 から数えます）** | 生のプロトコルのアドレスです。マニュアルの書き方ではなく、バス上の 0 起算のアドレスです。保持レジスターの 40001 はレジスター番号 0 です。ここに 30001 と入力しても種類は選ばれません。本当に 30000 を超えるレジスターを持つ機器があるからで、種類は上で選びます。レジスター番号は 16 ビットの数値としてバスを流れるので、この項目は 0 から 65535 を取り、それより広い値は入力したその場で拒否します。 | 0 |
| **数量（レジスター数。空欄なら自動）** | 値が占める 16 ビットレジスターの数。この入力欄は文字列のタグにだけ描かれます。ほかの型は幅が決まっていて、上書きするものがないからです。 | 型による：ブール値、Int16、UInt16 は 1、Int32 と Float は 2、文字列は 4 |
| **上位バイトを先に（MSB first、ビッグエンディアン）** | 各 16 ビットレジスターのどちらのバイトがバス上で先に来るか。括弧の中はマニュアルがこの設定に使う言葉です。数値のタグと、保持レジスターか入力レジスターに置いたブール値のタグにあります。コイルやディスクリート入力のブール値は 1 ビットだけで並べるバイトがないので、そこには描かれません。 | オン（Modbus の仕様が求める形） |
| **上位ワードを先に（high word first）** | 2 レジスターの値で、どちらのレジスターが上位の半分を運ぶか。Int32 と Float のタグだけです。レジスター 1 つの値（Boolean、Int16、UInt16）には 2 つ目のレジスターがないので、そこではこの入力欄は描かれません。2 つの入力欄の下の**値のバイト順**が、2 つが作る並びをマニュアルの文字で示します（[バイト順とワード順](#byte-and-word-order)）。 | オン |

「信号源」のカードの最後には**マニュアル上のアドレス**があり、機器のマニュアルが印刷する表記でタグがどこにあるかを示します。種類の先頭の数字に、1 から数えたレジスターを続けたものです。入力レジスター 0 は 30001、保持レジスター 100 の Float は 40101 から 40102 まで、9998 を超えるレジスターは 6 桁になります（保持レジスター 9999 は 410000）。パネルをマニュアルと 1 行ずつ見比べられます。

### 値の型と個数 {#value-types-and-quantities}

出る値の型は 6 つで、それぞれのプロトコル上の幅は決まっています。

| データ型 | レジスター | バス上の形 |
| --- | --- | --- |
| Boolean | 1 | レジスターの種類では、0 でないレジスターは true と読まれ、書き込みは 1 か 0 を送ります。`Coil` と `DiscreteInput` では、そのビットそのものです。 |
| Int16 | 1 | レジスター 1 つに収まる 16 ビットの符号付き整数。-32768 から 32767 まで。 |
| UInt16 | 1 | レジスター 1 つに収まる 16 ビットの符号なし整数。0 から 65535 まで。同じレジスターが UInt16 では 65535、Int16 では -1 と読めます。符号を決めるのはレジスターではなく、選んだデータ型です。 |
| Int32 | 2 | レジスター 2 つにまたがる 32 ビットの符号付き整数。 |
| Float | 2 | レジスター 2 つにまたがる IEEE 754 の単精度。 |
| String | 1 つ以上（既定は 4） | UTF-8 のバイトをレジスター 1 つにつき 2 つずつ詰めます。読み取りは最初の NUL バイトで止まります。書き込みは宣言した個数（レジスターあたり 2 バイト）に収まる必要があり、足りない分は 0 で埋められます。 |

文字列のタグは、機器がバイトを保持している順で読み書きされ、書き込みは常に UTF-8 を送ります。UTF-8 として正しくないバイト、つまり機器が一度も初期化していない範囲や、別の符号化で書かれた文字がそう見えるのですが、それは失敗ではなく Unicode の置換文字として読まれます。そのタグは目に見えておかしなものを見せますが、その機器のほかのタグは読み取りを続けます。

**数量（レジスター数。空欄なら自動）**を尋ねるのは文字列のタグだけです。Boolean、Int16、UInt16、Int32、Float の幅は決まっているので、タグのパネルにもタグのグリッドにも、その点では入力欄が描かれません。この上書きは、文字列のタグの大きさを決めるためにあります。

### バイト順とワード順 {#byte-and-word-order}

バイト順の入力欄は数値のタグとレジスターに置いたブール値のタグに描かれ、ワード順の入力欄はレジスター 2 つのタグ（Int32、Float）にだけ描かれます。これは独立した 2 つの問いです。各レジスターの中でどちらのバイトが先か、そして 2 つ 1 組のレジスターでどちらが先か。Modbus は文字についてはどちらも定めていないので、文字列のタグにはこれらが出ません。そのバイトは、機器が保持している順で流れます。値 `0x01020304` について、最上位のバイトを `A` とすると、4 通りの並びは次のようになります。

| 上位バイトを先に | 上位ワードを先に | バス上のバイト |
| --- | --- | --- |
| オン | オン | `AB CD`（仕様どおりの既定） |
| オン | オフ | `CD AB`（従来のワード入れ替え） |
| オフ | オン | `BA DC` |
| オフ | オフ | `DC BA` |

読み取りと書き込みは、同じ 2 つの答えを同じ順で当てるので、書き込みは、読み取りが取ってくるはずのバイトをそのまま置きます。入力欄の下の**値のバイト順**は、その並びをマニュアルと同じく一語（ABCD、CDAB、BADC、DCBA）で示します。

Int16 や UInt16 の値はレジスター 1 つなので、当てはまるのはバイト順だけです。`0x1234` なら、上位バイトを先にすると `12 34`、オフにすると `34 12` が送られます。並べる 2 つ目のレジスターがないので、ワード順の入力欄は描かれません。

### 機能コード {#function-codes}

| レジスターの種類 | 読み取り | 書き込み |
| --- | --- | --- |
| 保持レジスター（4xxxx） | 3（Read Holding Registers） | 6（単一レジスター）または 16（複数レジスター） |
| 入力レジスター（3xxxx） | 4（Read Input Registers） | 読み取り専用。書き込みは拒否されます |
| コイル（0xxxx） | 1（Read Coils） | 5（Force Single Coil） |
| ディスクリート入力（1xxxx） | 2（Read Discrete Inputs） | 読み取り専用。書き込みは拒否されます |

書き込みが成功として返るのは、サーバーのプロトコル上の応答が受け付けを確認したときだけです。保持している値が進むのも、そのときだけです。

## 機器検出 {#discovery}

スキャンは明示的で、上限があります。アプリがローカルのサブネットを推測したり、まとめて掃いたりすることはありません。範囲のフォームが取るのは次のものです。

| 項目 | 意味 | 既定 |
| --- | --- | --- |
| **IPv4 のホストまたは CIDR** | アドレス 1 つ（`192.168.1.10`）、または /24 より広くないサブネット（`192.168.1.0/24`）。ネットワークアドレスとブロードキャストアドレスは飛ばされます。 | 必須 |
| **ポート** | 探索するポート。 | 502 |
| **ユニット ID** | 値、カンマ区切りの並び、または昇順の範囲：`1`、`1,5,9`、`1, 3, 8-16`（0 から 255）。 | 1 |
| **フレーム形式** | 各探索を送るフレームです。透過モードの変換器は RTU のフレームにしか応答しないので、RTU でスキャンしてください。 | MBAP |
| **通信の最小間隔（ms）** | 同じホストで探索が終わってから次までの間隔、0 から 1000。 | 0 |

スキャンは、選んだ**フレーム形式**ですべての探索を送ります。**RTU** では各探索が素の RTU フレームになり、ユニット ID が一致して CRC が合う応答だけを数えます。これで透過モードの変換器の奥にあるユニットも、ほかと同じように見つかります。見つかったユニットは、追加するときにスキャンのフレーム形式を引き継ぎます。

1 回のスキャンが送れるのは最大で**探索 256 回**（ホスト × ユニット ID）です。それより大きな計画は、ソケットが 1 つも開く前に拒否されます。送られる要求は **Read Basic Device Identification**（機能コード 43/14）だけです。機器検出がレジスターを読んだり、レジスターの割り付けを当てたりすることはありません。完全に妥当な Modbus の例外応答は、身元は分からなくてもそのユニットの存在を示します。TCP で届くこと、タイムアウト、壊れたフレームは、示しません。同時にスキャンするホストは最大 4 つで、ホストの中では順番に行い、接続と探索のタイムアウトは 750 ms です。

結果は接続先の全体と、機器がベンダーと製品コードを報告したときはそれらから作った名前の候補を運びます。候補のタグは 0 件です。レジスターは**追加**のあとで設定します。すでに設定されているユニットはそう示され、重複を作る代わりに**開く**を出します。

## 知っておきたい限界 {#limits-worth-knowing}

- ほかの幅の整数の型（SByte、Byte、UInt32、Int64、UInt64）と Double は出ません。バス上の変換が過不足なく往復できるのは、上の 6 つの型です。
- 1 つの接続先で同時に進む要求は 1 つだけです。1 台のゲートウェイの奥にある多数の機器は、設計上その直列化を共有します。
- 書き込みは、一度に 1 つのコイルか 1 つのレジスターの連なりを対象にします。複数コイルをまとめて書く手段はありません。
