Modbus Typed Client

Documentation

A type-safe Modbus client for easily creating Modbus API calls

This package is an abstraction on top of the modbus Dart package that provides an easy way to map Modbus register data into Dart classes and vice versa. It relies on the modbus package to handle the underlying implementation of Modbus protocol calls.

This package was created to simplify reading and writing to modbus registers (which can only hold booleans or ints under the hood). The type conversion code can be located in one place, while the code to actually read and write to the device no longer has to deal with the conversion details.

This package also abstracts the way Modbus calls are made. It can automatically poll the Modbus device. It can also queue modbus calls to avoid conflicting calls being made at the same time, which is not allowed otherwise.

Features

  • Simplify the reading and writing of Dart data classes to Modbus devices
  • Automatically poll modbus devices and return the data as a Dart stream
  • Easily create API classes that abstract away Modbus spec details such as the register type or address

Usage

First, a typed client must be created. Supported types should be passed into the client when constructed.

final ModbusClient sourceClient = createTcpClient('192.168.0.190', port: 502);

final client = ModbusTypedClient(
  sourceClient: sourceClient,
  typeConverters: [
    // Built-in type converter for binary values
    const EitherTypeConverter<Direction>(Direction.left, Direction.right),
    // Create custom type converters via your own subclass
    const EquipmentInfoTypeConverter(),
    // Create custom type converters using callbacks
    RegisterTypeConverter<String>.from(
      readCallback: readString,
      writeCallback: writeString,
    ),
  ],
);

The typed client can then read and write the configured types to the modbus device.

// Read values from the modbus device (as a stream)
//
// The modbus device will be constantly polled and any new values will be 
// emitted. The behavior and poll interval can be configured as well.
final Stream<String> nameStream = 
  client.read<String>(Register.inputRegister, 42);

// Write values to the modbus device
await client.write<Direction>(64, Direction.left);

Unlike raw ModbusClient calls, there is no issue with multiple concurrent calls to the modbus device. The typed client automatically handles the queueing of calls to avoid conflicts.

While straightforward read and write calls can be made with the modbus client, it is also possible to quickly define an API class using the typed client. The API can abstract away the register type and address, allowing the ultimate consumer to focus on the actual values being read / written.

// Define an API class that encodes the type, register and address information
// for each piece of data.
class SmartPanelModbusApi {
  SmartPanelModbusApi(this.sourceClient);

  final ModbusClient sourceClient;
  late final ModbusTypedClient _client = ModbusTypedClient(
    sourceClient: sourceClient,
    typeConverters: [ ... ],
  );

  late final readName = 
    _client.createReadCall<String>(Register.inputRegister, 42);
  late final writeName = _client.createWriteCall<String>(42);

  ...
}

// Consumers can use the API class without worrying about type, register and 
// address implementation details
final api = SmartPanelModbusApi(sourceClient);

final Stream<String> nameStream = api.readName();

await api.writeName('Marvin');

Additional information

  • modbus package - The underlying package used to make Modbus protocol calls

Libraries

modbus_typed_client