hex CI license REUSE

Modbuzz icon

Yet another MODBUS library, supporting both TCP and RTU, providing gateway functionality.

Index

Usage

30-second overview

Modbuzz provides one consistent public API for:

  • Acting as a MODBUS client to read and write data from/to MODBUS devices over TCP or RTU.
  • Acting as a MODBUS server to provide data and receive commands over TCP or RTU.
  • Building MODBUS gateways that connect any combination of TCP and RTU transports.

If you are evaluating this library, start from Quick Start to see a basic client request, then explore Server setup and Gateway recipes based on your use case.

Quick Start (3 minutes)

This flow assumes a real MODBUS device is available:

  1. Start a TCP client or RTU client.
  2. Send one request to the device.
  3. Stop the client.
:ok = Modbuzz.start_tcp_client(:demo_tcp_client, {192, 168, 0, 10}, 502)

alias Modbuzz.PDU.WriteSingleCoil

req = %WriteSingleCoil.Req{output_address: 0, output_value: true}

{:ok, _res} = Modbuzz.request(:demo_tcp_client, req)

:ok = Modbuzz.stop_tcp_client(:demo_tcp_client)

Client usage (primary use case)

TCP client

Modbuzz.start_tcp_client/3 starts a TCP client instance.

Modbuzz.request/2 or Modbuzz.request/3 requests synchronously. Modbuzz.request_async/2 or Modbuzz.request_async/3 requests asynchronously. The 2nd argument, unit_id, can be omitted. If omitted, its value defaults to 0.

Common setup:

iex> :ok = Modbuzz.start_tcp_client(:your_tcp_client, {192, 168, 0, 10}, 502)
iex> alias Modbuzz.PDU.WriteSingleCoil
iex> req = %WriteSingleCoil.Req{output_address: 0, output_value: true}

Synchronous request:

iex> {:ok, _res} = Modbuzz.request(:your_tcp_client, req)

Asynchronous request:

iex> :ok = Modbuzz.request_async(:your_tcp_client, req)
iex> flush()
{:modbuzz, :your_tcp_client, 0, _request, {:ok, _response}}

RTU client

Modbuzz.start_rtu_client/3 starts an RTU client instance.

Modbuzz.request/2 or Modbuzz.request/3 requests synchronously. Modbuzz.request_async/2 or Modbuzz.request_async/3 requests asynchronously. The 2nd argument, unit_id, can be omitted. If omitted, its value defaults to 0.

Common setup:

iex> :ok = Modbuzz.start_rtu_client(:your_rtu_client, "ttyUSB0", [speed: 9600])
iex> alias Modbuzz.PDU.WriteSingleCoil
iex> req = %WriteSingleCoil.Req{output_address: 0, output_value: true}

Synchronous request:

iex> {:ok, _res} = Modbuzz.request(:your_rtu_client, 1, req)

Asynchronous request:

iex> :ok = Modbuzz.request_async(:your_rtu_client, 1, req)
iex> flush()
{:modbuzz, :your_rtu_client, 1, _request, {:ok, _response}}

Data server as value source

Modbuzz.start_data_server/1 starts a data server instance. Data server lets you expose your own application data (for example, sensor values) as Modbus values through TCP/RTU servers.

iex> :ok = Modbuzz.start_data_server(:your_data_server)
iex> alias Modbuzz.PDU.WriteSingleCoil
iex> req = %WriteSingleCoil.Req{output_address: 0, output_value: true}
iex> res = %WriteSingleCoil.Res{output_address: 0, output_value: true}
iex> :ok = Modbuzz.create_unit(:your_data_server, 1)
iex> :ok = Modbuzz.upsert(:your_data_server, 1, req, res)
iex> :ok = Modbuzz.start_tcp_server(:your_tcp_server, {192, 168, 1, 10}, 502, :your_data_server)

In this setup, external Modbus clients can read/write values through your TCP/RTU server, backed by the data server mappings.

API map

GoalAPI
Send a request and wait for resultModbuzz.request/2, Modbuzz.request/3, Modbuzz.request/4
Send a request and receive by messageModbuzz.request_async/2, Modbuzz.request_async/3, Modbuzz.request_async/4, Modbuzz.request_async/5
Start/stop data serverModbuzz.start_data_server/1, Modbuzz.stop_data_server/1
Manage data server contentModbuzz.create_unit/1, Modbuzz.create_unit/2, Modbuzz.upsert/3, Modbuzz.upsert/4, Modbuzz.delete/2, Modbuzz.delete/3, Modbuzz.dump/1, Modbuzz.dump/2
Start/stop TCP clientModbuzz.start_tcp_client/3, Modbuzz.stop_tcp_client/1
Start/stop RTU clientModbuzz.start_rtu_client/3, Modbuzz.stop_rtu_client/1
Start/stop TCP serverModbuzz.start_tcp_server/4, Modbuzz.stop_tcp_server/1
Start/stop RTU serverModbuzz.start_rtu_server/4, Modbuzz.stop_rtu_server/1

For detailed external API behavior, see External API Guide.

Synchronous vs asynchronous request

Modbuzz.request/2, /3, and /4 are synchronous and return result directly.

Modbuzz.request_async/2, /3, /4, and /5 return :ok immediately and send result as a message:

{:modbuzz, name, unit_id, request, {:ok, response}}
{:modbuzz, name, unit_id, request, {:error, error_response_or_error_reason}}

Use synchronous request for simple command flow. Use asynchronous request when you need non-blocking behavior or concurrent request orchestration.

Lifecycle (start and stop)

Start functions return:

  • :ok
  • {:error, :already_started}

Stop functions return:

  • :ok
  • {:error, :not_started}

It is safe to call stop even when the process may already be down, as long as you handle {:error, :not_started}.

Server setup

data_source for Modbuzz.start_tcp_server/4 and Modbuzz.start_rtu_server/4 can be:

  • Data server name
  • TCP client name
  • RTU client name

TCP server example:

:ok = Modbuzz.start_tcp_server(:your_tcp_server, {192, 168, 1, 10}, 502, :your_data_source)

RTU server example:

:ok = Modbuzz.start_rtu_server(:your_rtu_server, "ttyUSB1", [speed: 19200], :your_data_source)

Gateway recipes

Use these recipes after you confirm Quick Start works.

TCP/RTU gateway

TCP server receives a request and passes it through to RTU client.

:ok = Modbuzz.start_rtu_client(:your_rtu_client, "ttyUSB0", [speed: 9600])
:ok = Modbuzz.start_tcp_server(:your_tcp_server, {192, 168, 1, 10}, 502, :your_rtu_client)

RTU/TCP gateway

RTU server receives a request and passes it through to TCP client.

:ok = Modbuzz.start_tcp_client(:your_tcp_client, {192, 168, 0, 10}, 502)
:ok = Modbuzz.start_rtu_server(:your_rtu_server, "ttyUSB1", [speed: 19200], :your_tcp_client)

TCP/TCP gateway

TCP server receives a request and passes it through to TCP client.

:ok = Modbuzz.start_tcp_client(:your_tcp_client, {192, 168, 0, 10}, 502)
:ok = Modbuzz.start_tcp_server(:your_tcp_server, {192, 168, 1, 10}, 502, :your_tcp_client)

RTU/RTU gateway

RTU server receives a request and passes it through to RTU client.

:ok = Modbuzz.start_rtu_client(:your_rtu_client, "ttyUSB0", [speed: 9600])
:ok = Modbuzz.start_rtu_server(:your_rtu_server, "ttyUSB1", [speed: 19200], :your_rtu_client)

Troubleshooting

  • Timeout on request: Check that the client/server is started, the device is reachable, and the request matches the expected unit/address.
  • No async result message: request_async returns :ok even if the target process is not running. Verify the client/server startup first.
  • Wrong or missing unit data: If you are using a data server as backend source, ensure create_unit/2 was called with the same unit_id used by the request.

Installation

If available in Hex, the package can be installed by adding modbuzz to your list of dependencies in mix.exs:

def deps do
  [
    {:modbuzz, "~> 0.3.0"}
  ]
end

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/modbuzz.

License

This project is licensed under the Apache-2.0 license.

And this project follows the REUSE compliance. For more details, see the REUSE SOFTWARE.

MODBUS References