# `AVModbus.Client`
[🔗](https://github.com/piyopiyoex/avmodbus/blob/v0.1.0/lib/avmodbus/client.ex#L1)

serial bus または TCP connection を所有する managed Modbus client です。

通常の serial application では `start_link/1` で process が UART を所有し、
request を順番に処理します。low-level transaction API は host-side tests や
UART ownership を application 側で管理する用途に利用できます。

serial client では active transaction を 1 件に制限し、送信待ちは `:max_queue` で制限します。
queue saturation は `{:error, :queue_full}` を返します。unit `0` への write は broadcast として扱い、
response を読まず configured turnaround delay の後に transaction を完了します。
TCP の unit id は `0..255` を routing identifier として扱います。

`start_link(transport: :tcp, host: host)` では複数 request を transaction id で照合しながら並行送信する
Modbus TCP client を起動します。wire 上の request は `:max_pending`、送信待ちは
`:max_queue` で制限し、queue saturation は `{:error, :queue_full}` を返します。

child specification を実装しているため supervision tree に直接追加できます。
supervised child の raw PID、`:name` で登録した atom、`start_link` が返す tagged handle の
いずれも全 managed client API で利用できます。

request function の最後の引数には `timeout:`、`retries:`、`backoff:` の keyword options を
指定できます。retry は明示的に指定した
idempotent read にだけ適用され、write は再送しません。

managed client の `:timeout` option は request ごとの既定 deadline を指定します。
request 側の timeout を省略するとこの値を使い、明示した `timeout:` は client の既定値を
上書きします。

現在の AtomVM SSL API は Modbus/TCP Security が要求する mutual authentication を提供できないため、
`tls:` または `ssl:` option は `{:error, :tls_not_supported}` を返します。

# `async_option`

```elixir
@type async_option() :: {:timeout, non_neg_integer()} | {:to, pid()}
```

# `device_identification_category`

```elixir
@type device_identification_category() :: :basic | :regular | :extended
```

# `device_identification_option`

```elixir
@type device_identification_option() ::
  retry_option() | {:category, device_identification_category()}
```

# `option`

```elixir
@type option() ::
  {:transport, :rtu | :ascii | :tcp}
  | {:host, term()}
  | {:echo, boolean()}
  | {:turnaround, pos_integer()}
  | {:silence, pos_integer()}
  | {:gap, non_neg_integer()}
  | {:serial, keyword()}
  | {:backoff, {pos_integer(), pos_integer()}}
  | {:port, 1..65535}
  | {:max_pending, 1..65536}
  | {:max_queue, non_neg_integer() | :infinity}
  | {:check_unit, boolean()}
  | {:connect_timeout, pos_integer()}
  | {:timeout, pos_integer()}
  | {:name, atom()}
```

# `request_result`

```elixir
@type request_result() :: :ok | {:ok, term()} | {:error, term()}
```

# `retry_option`

```elixir
@type retry_option() ::
  {:timeout, non_neg_integer()}
  | {:retries, non_neg_integer()}
  | {:backoff, {pos_integer(), pos_integer()}}
```

# `server`

```elixir
@type server() :: pid() | atom()
```

# `t`

```elixir
@opaque t()
```

# `custom`

```elixir
@spec custom(term(), 0..255, 1..127, binary(), [retry_option()]) :: request_result()
```

user-defined public Function Code の request を送ります。

# `diagnostics`

```elixir
@spec diagnostics(term(), 0..255, AVModbus.word(), [AVModbus.word()], [retry_option()]) ::
  request_result()
```

diagnostics sub-function を実行します。

# `encapsulated_interface_transport`

```elixir
@spec encapsulated_interface_transport(term(), 0..255, byte(), binary(), [
  retry_option()
]) ::
  request_result()
```

任意の MEI type で Encapsulated Interface Transport を実行します。

# `get_comm_event_counter`

```elixir
@spec get_comm_event_counter(term(), 0..255, [retry_option()]) :: request_result()
```

communication event counter を読み取ります。

# `get_comm_event_log`

```elixir
@spec get_comm_event_log(term(), 0..255, [retry_option()]) :: request_result()
```

communication event log を読み取ります。

# `mask_write_register`

```elixir
@spec mask_write_register(
  term(),
  0..255,
  AVModbus.address(),
  AVModbus.word(),
  AVModbus.word(),
  [retry_option()]
) :: request_result()
```

mask を使って 1 個の Holding Register を変更します。

# `read_coils`

```elixir
@spec read_coils(term(), 0..255, AVModbus.address(), pos_integer(), [retry_option()]) ::
  request_result()
```

Coil を読み取ります。

# `read_device_identification`

```elixir
@spec read_device_identification(term(), 0..255, [device_identification_option()]) ::
  {:ok, map()} | {:error, term()}
```

指定 category の device identification を全 page 読み、object map として返します。

category は `:category` option で指定し、既定値は `:basic` です。request 共通の
`:timeout`、`:retries`、`:backoff` も指定できます。

# `read_discrete_inputs`

```elixir
@spec read_discrete_inputs(term(), 0..255, AVModbus.address(), pos_integer(), [
  retry_option()
]) ::
  request_result()
```

Discrete Input を読み取ります。

# `read_exception_status`

```elixir
@spec read_exception_status(term(), 0..255, [retry_option()]) :: request_result()
```

server の exception status を読み取ります。

# `read_fifo_queue`

```elixir
@spec read_fifo_queue(term(), 0..255, AVModbus.address(), [retry_option()]) ::
  request_result()
```

FIFO queue を読み取ります。

# `read_file_record`

```elixir
@spec read_file_record(term(), 0..255, [term()], [retry_option()]) :: request_result()
```

file record group を読み取ります。

# `read_holding_registers`

```elixir
@spec read_holding_registers(term(), 0..255, AVModbus.address(), pos_integer(), [
  retry_option()
]) ::
  request_result()
```

Holding Register を読み取ります。

# `read_input_registers`

```elixir
@spec read_input_registers(term(), 0..255, AVModbus.address(), pos_integer(), [
  retry_option()
]) ::
  request_result()
```

Input Register を読み取ります。

# `read_write_multiple_registers`

```elixir
@spec read_write_multiple_registers(
  term(),
  0..255,
  AVModbus.address(),
  pos_integer(),
  AVModbus.address(),
  [AVModbus.word()],
  [retry_option()]
) :: request_result()
```

複数の Holding Register の書き込みと読み取りを 1 transaction で実行します。

# `report_server_id`

```elixir
@spec report_server_id(term(), 0..255, [retry_option()]) :: request_result()
```

server identification を読み取ります。

# `request`

```elixir
@spec request(term(), 0..255, AVModbus.PDU.request(), [retry_option()]) ::
  {:ok, term()} | :ok | {:error, term()}
```

request を送信し、result を待ちます。

最後の引数は `timeout:`、`retries:`、`backoff:` の keyword options です。
`retries:` は既定で `0` です。正の retry 回数は idempotent read
にだけ許可し、`:timeout` と `:closed` の場合だけ再送します。

# `retry_request`

```elixir
@spec retry_request(term(), 0..255, AVModbus.PDU.request(), [retry_option()]) ::
  {:ok, term()} | {:error, term()}
```

transient failure の場合だけ idempotent read request を再試行します。

`:timeout` は各 attempt の timeout、`:retries` は最初の attempt 後に許可する追加回数、
`:backoff` は attempt 間の `{first, maximum}` delay です。timeout の省略時は managed client の
startup default（既定 `1000` ms）、unmanaged UART では `1000` ms を
使います。retry と backoff の既定値は `1` 回、`{100, 1000}` ms です。

`:timeout` と `:closed` だけを retry します。write、diagnostics、custom request、
Modbus exception、invalid response は replay しません。

# `send_request`

```elixir
@spec send_request(t() | server(), 0..255, AVModbus.PDU.request(), [async_option()]) ::
  reference() | {:error, term()}
```

transaction の完了を待たず managed client に request を送ります。

request を受け付けた場合は reference を返し、recipient は後で
`{AVModbus.Client, reference, result}` を 1 回受信します。受付時点で client を利用できない場合や
queue が満杯の場合は reference を作らず `{:error, reason}` を返します。
`request/4` と同様に、受付までの時間と queue 待ち時間も timeout に含まれます。

第 4 引数には `timeout:` と `to:` の keyword options を指定できます。

# `start_link`

```elixir
@spec start_link() :: {:ok, t()} | {:error, term()}
```

transport を指定しないため `{:error, :missing_transport_option}` を返します。

`start_link(transport: :rtu)`、`start_link(transport: :ascii)`、または
`start_link(transport: :tcp, host: host)` を使用してください。

# `start_link`

```elixir
@spec start_link([option()] | term()) :: {:ok, t()} | {:error, term()}
```

client option を使って configured UART を開き、その lifecycle を所有します。

`:transport` は必須で、`:rtu`、`:ascii`、`:tcp` のいずれかを指定します。
TCP では `:host` も必須です。serial の主な option は adapter echo を除去する `:echo`、
broadcast 後の `:turnaround`、送信前の `:gap`、unknown-length response を区切る
`:silence`、instance UART 設定の `:serial`、待機 request 上限の `:max_queue`、request の既定
deadline を指定する `:timeout`、UART reopen delay を指定する `:backoff` (`{first, maximum}`) です。
時間指定は millisecond 単位です。

# `status`

```elixir
@spec status(t() | server()) :: :connected | :connecting | {:disconnected, term()}
```

managed client の現在の connection state を返します。

# `stop`

```elixir
@spec stop(t() | server()) :: :ok
```

managed client を停止し、connection または所有する UART を閉じます。

# `write_file_record`

```elixir
@spec write_file_record(term(), 0..255, [term()], [retry_option()]) ::
  request_result()
```

file record group を書き込みます。

# `write_multiple_coils`

```elixir
@spec write_multiple_coils(term(), 0..255, AVModbus.address(), [boolean()], [
  retry_option()
]) ::
  request_result()
```

複数の Coil を書き込みます。

# `write_multiple_registers`

```elixir
@spec write_multiple_registers(
  term(),
  0..255,
  AVModbus.address(),
  [AVModbus.word()],
  [retry_option()]
) ::
  request_result()
```

複数の Holding Register を書き込みます。

# `write_single_coil`

```elixir
@spec write_single_coil(term(), 0..255, AVModbus.address(), boolean(), [
  retry_option()
]) ::
  request_result()
```

1 個の Coil を書き込みます。

# `write_single_register`

```elixir
@spec write_single_register(
  term(),
  0..255,
  AVModbus.address(),
  AVModbus.word(),
  [retry_option()]
) :: request_result()
```

1 個の Holding Register を書き込みます。

---

*Consult [api-reference.md](api-reference.md) for complete listing*
