Current section
Files
Jump to
Current section
Files
docs/development.md
# 開発者ガイド
ここでは、AVModbus を使ったアプリケーションの拡張と、変更後の検証方法をまとめます。
最初の通信確認は [はじめ方](getting-started.md) を参照してください。
## Client の設定
### TCP の同時要求と待機キュー
```elixir
{:ok, client} =
AVModbus.Client.start_link(
transport: :tcp,
host: "192.168.1.20",
port: 502,
timeout: 1_000,
max_pending: 4,
max_queue: 64
)
```
`max_pending` は送信済みで応答待ちの要求数、`max_queue` はまだ送っていない要求数の上限です。
Serial Client は同時に 1 件ずつ処理します。上限に達した場合は `{:error, :queue_full}` を返します。
待機時間もタイムアウトに含まれます。
### 非同期で要求を送る
```elixir
case AVModbus.Client.send_request(
client,
1,
{:read_holding_registers, 0, 2},
timeout: 500,
to: self()
) do
ref when is_reference(ref) ->
receive do
{AVModbus.Client, ^ref, result} -> result
end
{:error, reason} ->
{:error, reason}
end
```
要求の受け付けは短い同期処理ですが、応答は待ちません。
受け付けた要求の結果は reference とともに一度だけ通知します。
### 読み取り処理のリトライ
```elixir
AVModbus.Client.read_holding_registers(
client, 1, 0, 2,
timeout: 500,
retries: 2,
backoff: {100, 1_000}
)
```
`retries:` を使えるのは安全に再実行できる読み取り処理です。
書き込み・診断・カスタム要求は自動リトライしません。
Modbus 例外応答や不正な応答もリトライの対象外です。
### Supervisor から起動する
```elixir
children = [
{AVModbus.Client,
[transport: :tcp, host: "192.168.1.20", port: 502, name: :plc_client]}
]
Supervisor.start_link(children, strategy: :one_for_one)
AVModbus.Client.read_holding_registers(:plc_client, 1, 0, 2)
```
## Serial Transport を入れ替える
`AVModbus.Serial.Transport` を実装すると、既定の AtomVM UART 以外の通信路も使用できます。
Transport には `open/1`・`read`・`write`・`close` が必要です。
`open/1` はインスタンスごとの設定を受け取ります。
```elixir
{:ok, client} =
AVModbus.Client.start_link(
transport: :rtu,
serial: [peripheral: "UART2", speed: 38_400, tx: 4, rx: 5]
)
```
RS485 の送受信方向を手動で切り替える場合は、送信完了待ちと切り替えを `write/2` 内で処理します。
UART ハンドルをアプリケーションから渡す場合は、ハンドルの終了と復旧もアプリケーションの責任です。
## Server と戻り値
`AVModbus.Server` は RTU・ASCII・TCP で共通の起動 API を提供します。
```elixir
{:ok, memory} = AVModbus.Memory.start_link()
:ok = AVModbus.Memory.put(memory, :holding_register, 10, [18, 1000, 3])
{:ok, server} =
AVModbus.Server.start_link(
handler: {AVModbus.Memory, memory},
transport: :rtu,
units: [1]
)
```
主な戻り値は次のとおりです。
| 状況 | 戻り値 |
| --- | --- |
| 読み取り成功 | `{:ok, value}` |
| 書き込み成功 | `:ok` |
| Modbus 例外応答 | `{:error, {:exception, reason}}` |
| 要求に一致しない応答 | `{:error, {:invalid_response, pdu}}` |
| タイムアウト | `{:error, :timeout}` |
| 接続を使えない | `{:error, :closed}` |
| 待機キューが満杯 | `{:error, :queue_full}` |
Serial の Unit ID は `0..247` で、`0` は書き込みの broadcast に使います。
TCP では gateway を考慮して `0..255` を使用します。
### TCP Server を使う場合
```elixir
{:ok, server} =
AVModbus.Server.start_link(
handler: {AVModbus.Memory, memory},
transport: :tcp,
address: {192, 168, 10, 20},
port: 502,
connections: 4,
max_buffer: 2_048,
allow: [{{192, 168, 10, 0}, 24}],
authorize: fn _role, unit_id, _request -> unit_id == 1 end
)
```
`max_buffer` はブローカー内の未配送データだけを制限します。ソケットや処理中のデータを含む
メモリ全体の上限ではありません。接続終了時には実行中の handler を停止します。
handler に長い寿命が必要なら、別の Supervisor 管理プロセスへ委譲してください。
TCP の受信・終了処理については
[ADR 0007](https://github.com/piyopiyoex/avmodbus/blob/v0.1.0/docs/adr/0007-isolate-modbus-tcp-connections.md)
に記録しています。
対象 AtomVM は Modbus/TCP Security に必要な相互認証 TLS に対応していません。
TLS を指定すると `{:error, :tls_not_supported}` を返し、暗号化なしには切り替わりません。
**TCP Server は信頼できるネットワーク内で使用し**、`address:`・`allow:`・`authorize:` と
ファイアウォールで接続元と操作を制限してください。
## テストする
通常の開発では次を実行します。
```sh
mise install
mix deps.get
mix format --check-formatted
mix test --cover
```
プロパティテストや長時間テストは必要なときだけ実行します。
```sh
FUZZ_RUNS=10000 mix test test/avmodbus/property_test.exs
SOAK_SECONDS=600 mix test --include soak test/avmodbus/soak_test.exs
AVMODBUS_PEAK_REPORT=1 mix test test/avmodbus/tcp_server_load_test.exs
```
最後のコマンドは TCP 受信中のメモリやメールボックスの**標本化した最大値**を報告します。
AtomVM 実機や OS 全体の厳密なピーク値ではありません。
### 外部の Modbus 実装とテストする
```sh
python3 -m venv /tmp/avmodbus-pymodbus
/tmp/avmodbus-pymodbus/bin/pip install 'pymodbus==3.15.0' pyserial
PYMODBUS_PYTHON=/tmp/avmodbus-pymodbus/bin/python mix test --include interop
```
`libmodbus` が使える環境では次も実行します。
```sh
LIBMODBUS_INTEROP=1 mix test --include libmodbus test/avmodbus/libmodbus_interop_test.exs
```
外部実装とのテストはホスト上の TCP・仮想シリアル接続を使用します。
実機の RS485 通信を確認する試験ではありません。
CI の `interoperability` job の結果も、リリース前に確認してください。
## 実機で検証する
[XIAO ESP32-C5 の追加検証手順](development/hardware-tests.md) に、通信速度、
ケーブルの切断・再接続、メモリ測定、リソース回復テストの手順をまとめています。
測定値は [リソース計測](https://github.com/piyopiyoex/avmodbus/blob/v0.1.0/docs/development/resource-measurements.md)、
リリース前の確認は [チェックリスト](https://github.com/piyopiyoex/avmodbus/blob/v0.1.0/docs/development/release-checklist.md)
を参照してください。