This is a set of packages implementing Modbus Client sending requests to a remote device (i.e. Modbus Server).
The split of the packages is done to minimize dependencies on your project.
Notes for serial implementation
Using modbus client is simple. You define your elements, create a read or write request out of them and use the client to send the request.
import 'package:modbus_client/modbus_client.dart';
import 'package:modbus_client_serial/modbus_client_serial.dart';
void main() async {
// Create a modbus int16 register element
var batteryTemperature = ModbusInt16Register(
name: "BatteryTemperature",
type: ModbusElementType.inputRegister,
address: 22,
uom: "°C",
multiplier: 0.1,
onUpdate: (self) => print(self));
// Create the modbus client.
var modbusClient = ModbusClientSerialRtu(portName: "COM1", unitId: 1);
// Send a read request from the element
await modbusClient.send(batteryTemperature.getReadRequest());
// Ending here
modbusClient.disconnect();
}
import 'package:modbus_client/modbus_client.dart';
import 'package:modbus_client_serial/modbus_client_serial.dart';
enum BatteryStatus implements ModbusIntEnum {
offline(0),
standby(1),
running(2),
fault(3),
sleepMode(4);
const BatteryStatus(this.intValue);
@override
final int intValue;
@override
String toString() {
return name;
}
}
void main() async {
var batteryStatus = ModbusEnumRegister(
name: "BatteryStatus",
address: 11,
type: ModbusElementType.holdingRegister,
enumValues: BatteryStatus.values,
onUpdate: (self) => print(self));
var modbusClient = ModbusClientSerialRtu(portName: "COM1", unitId: 1);
var req = batteryStatus.getWriteRequest(BatteryStatus.running);
var res = await modbusClient.send(req);
print(res.name);
modbusClient.disconnect();
}
This library has a wide range of defined modbus elements having a name, a description, a modbus address and an update callback you can use in case the element value has been updated.
Typical elements are simple bit and numeric values:
Numeric elements have an uom (i.e. unit of measure), a multiplier and offset to make conversion from raw to engineering values (i.e. value = read_value*multiplier + offset), and viewDecimalPlaces to print out only needed decimals.
To read and write an enum as an element you can use a ModbusEnumRegister
/// Implement [ModbusIntEnum] to use it with a [ModbusEnumRegister]
enum BatteryStatus implements ModbusIntEnum {
offline(0),
standby(1),
running(2),
fault(3),
sleepMode(4);
const BatteryStatus(this.intValue);
@override
final int intValue;
@override
String toString() {
return name;
}
}
void main() async {
ModbusAppLogger(Level.FINEST);
var batteryStatus = ModbusEnumRegister(
name: "BatteryStatus",
address: 11,
type: ModbusElementType.holdingRegister,
enumValues: BatteryStatus.values);
var modbusClient = ModbusClientTcp("127.0.0.1", unitId: 1);
await modbusClient.send(batteryStatus.getReadRequest());
modbusClient.disconnect();
}
Similar to enum is a status element. Within a ModbusStatusRegister you can define all the possible statues (i.e. numeric <-> string pairs) for the element.
ModbusStatusRegister(
name: "Device status",
type: ModbusElementType.holdingRegister,
address: 32089,
statusValues: [
ModbusStatus(0x0000, "Standby: initializing"),
ModbusStatus(0x0001, "Standby: detecting insulation resistance"),
ModbusStatus(0x0002, "Standby: detecting irradiation"),
ModbusStatus(0x0003, "Standby: drid detecting"),
ModbusStatus(0x0100, "Starting"),
ModbusStatus(0x0200, "On-grid (Off-grid mode: running)"),
ModbusStatus(0x0201,
"Grid connection: power limited (Off-grid mode: running: power limited)"),
ModbusStatus(0x0202,
"Grid connection: selfderating (Off-grid mode: running: selfderating)"),
ModbusStatus(0x0203, "Off-grid Running"),
ModbusStatus(0x0300, "Shutdown: fault"),
ModbusStatus(0x0301, "Shutdown: command"),
ModbusStatus(0x0302, "Shutdown: OVGR"),
ModbusStatus(0x0303, "Shutdown: communication disconnected"),
ModbusStatus(0x0304, "Shutdown: power limited"),
ModbusStatus(0x0305, "Shutdown: manual startup required"),
ModbusStatus(0x0306, "Shutdown: DC switches disconnected"),
ModbusStatus(0x0307, "Shutdown: rapid cutoff"),
ModbusStatus(0x0308, "Shutdown: input underpower"),
ModbusStatus(0x0401, "Grid scheduling: cosφ-P curve"),
ModbusStatus(0x0402, "Grid scheduling: Q-U curve"),
ModbusStatus(0x0403, "Grid scheduling: PF-U curve"),
ModbusStatus(0x0404, "Grid scheduling: dry contact"),
ModbusStatus(0x0405, "Grid scheduling: Q-P curve"),
ModbusStatus(0x0500, "Spotcheck ready"),
ModbusStatus(0x0501, "Spotchecking"),
ModbusStatus(0x0600, "Inspecting"),
ModbusStatus(0X0700, "AFCI self check"),
ModbusStatus(0X0800, "I-V scanning"),
ModbusStatus(0X0900, "DC input detection"),
ModbusStatus(0X0A00, "Running: off-grid charging"),
ModbusStatus(0xA000, "Standby: no irradiation"),
]),
Use ModbusBitMaskRegister if your device has registers where each bit value has a special meaning. You can define both an active and inactive value for each ModbusBitMask object.
ModbusBitMaskRegister(
name: "Alarm 1",
type: ModbusElementType.holdingRegister,
address: 32008,
bitMasks: [
ModbusBitMask(0, "High String Input Voltage [2001 Major]"),
ModbusBitMask(1, "DC Arc Fault [2002 Major]"),
ModbusBitMask(2, "String Reverse Connection [2011 Major]"),
ModbusBitMask(3, "String Current Backfeed [2012 Warning]"),
ModbusBitMask(4, "Abnormal String Power [2013 Warning]"),
ModbusBitMask(5, "AFCI Self-Check Fail. [2021 Major]"),
ModbusBitMask(6, "Phase Wire Short-Circuited to PE [2031 Major]"),
ModbusBitMask(7, "Grid Loss [2032 Major]"),
ModbusBitMask(8, "Grid Undervoltage [2033 Major]"),
ModbusBitMask(9, "Grid Overvoltage [2034 Major]"),
ModbusBitMask(10, "Grid Volt. Imbalance [2035 Major]"),
ModbusBitMask(11, "Grid Overfrequency [2036 Major]"),
ModbusBitMask(12, "Grid Underfrequency [2037 Major]"),
ModbusBitMask(13, "Unstable Grid Frequency [2038 Major]"),
ModbusBitMask(14, "Output Overcurrent [2039 Major]"),
ModbusBitMask(15, "Output DC Component Overhigh [2040 Major]"),
]),
Use ModbusEpochRegister if your device holds timestamp values as Epoch/Unix time in seconds.
ModbusEpochRegister(
name: "Startup time",
type: ModbusElementType.holdingRegister,
address: 32091,
isUtc: false);
Use ModbusEpochRegister if your device holds timestamp values as Epoch/Unix time in seconds.
ModbusEpochRegister(
name: "Startup time",
type: ModbusElementType.holdingRegister,
address: 32091,
isUtc: false);
Use ModbusBytesRegister if you want to read and write multiple bytes/registers at a time. Note that byteCount cannot exceed 250 bytes which is the multiple read bytes limit for Modbus/RTU. Note that the protocol limit depends on multiple factors:
var bytesRegister = ModbusBytesRegister(
name: "BytesArray",
address: 4,
byteCount: 10,
onUpdate: (self) => print(self));
// Create the modbus client.
var modbusClient = ModbusClientSerialRtu(portName: "COM1", unitId: 1);
var req1 = bytesRegister.getWriteRequest(Uint8List.fromList(
[0x01, 0x02, 0x03, 0x04, 0x05, 0x66, 0x07, 0x08, 0x09, 0x0A]));
var res = await modbusClient.send(req1);
print(res);
var req2 = bytesRegister.getReadRequest();
res = await modbusClient.send(req2);
print(bytesRegister.value);
modbusClient.disconnect();
You can define a ModbusElementsGroup to optimize the elements reading. The most the element addresses are contiguous the most performant is the request. The address range limit for bits is 2000 and 125 for registers. You can use the ModbusElementsGroup object as a kind of list of element.
import 'package:modbus_client/modbus_client.dart';
import 'package:modbus_client_tcp/modbus_client_tcp.dart';
enum BatteryStatus implements ModbusIntEnum {
offline(0),
standby(1),
running(2),
fault(3),
sleepMode(4);
const BatteryStatus(this.intValue);
@override
final int intValue;
}
void main() async {
// Create a modbus elements group
var batteryRegs = ModbusElementsGroup([
ModbusEnumRegister(
name: "BatteryStatus",
type: ModbusElementType.holdingRegister,
address: 37000,
enumValues: BatteryStatus.values),
ModbusInt32Register(
name: "BatteryChargingPower",
type: ModbusElementType.holdingRegister,
address: 37001,
uom: "W",
description: "> 0: charging - < 0: discharging"),
ModbusUint16Register(
name: "BatteryCharge",
type: ModbusElementType.holdingRegister,
address: 37004,
uom: "%",
multiplier: 0.1),
ModbusUint16Register(
name: "BatteryTemperature",
type: ModbusElementType.holdingRegister,
address: 37022,
uom: "°C",
multiplier: 0.1),
]);
// Create the modbus client.
var modbusClient = ModbusClientTcp("127.0.0.1", unitId: 1);
// Send a read request from the group
await modbusClient.send(batteryRegs.getReadRequest());
print(batteryRegs[0]);
print(batteryRegs[1]);
print(batteryRegs[2]);
print(batteryRegs[3]);
// Ending here
modbusClient.disconnect();
}
This library supports function codes 0x14 and 0x15 to read and write different types of numeric records.
import 'dart:typed_data';
import 'package:logging/logging.dart';
import 'package:modbus_client/modbus_client.dart';
import 'package:modbus_client_serial/modbus_client_serial.dart';
void main() async {
// Simple modbus logging
ModbusAppLogger(Level.FINE);
// Create the modbus client.
var modbusClient = ModbusClientSerialRtu(portName: "COM1", unitId: 1);
// Write two file records
var r1 = ModbusFileUint16Record(
fileNumber: 4,
recordNumber: 1,
recordData: Uint16List.fromList([12573, 56312]));
var r2 = ModbusFileDoubleRecord(
fileNumber: 3,
recordNumber: 9,
recordData: Float64List.fromList([123.5634, 125756782.8492]));
await modbusClient.send(ModbusFileRecordsWriteRequest([r1, r2]));
// Read two file records
r1 = ModbusFileUint16Record.empty(
fileNumber: 4, recordNumber: 1, recordDataCount: 2);
r2 = ModbusFileDoubleRecord.empty(
fileNumber: 3, recordNumber: 9, recordDataCount: 2);
await modbusClient.send(ModbusFileRecordsReadRequest([r1, r2]));
// Write multiple records
var multipleRecords =
ModbusFileMultipleRecord(fileNumber: 4, recordNumber: 1);
multipleRecords.addNext(ModbusRecordType.int16, -123);
multipleRecords.addNext(ModbusRecordType.uint16, 5000);
multipleRecords.addNext(ModbusRecordType.int32, -1234567890);
multipleRecords.addNext(ModbusRecordType.uint32, 1234567890);
multipleRecords.addNext(ModbusRecordType.float, 123.45);
multipleRecords.addNext(ModbusRecordType.double, 12345.6789);
await modbusClient.send(multipleRecords.getWriteRequest());
multipleRecords = ModbusFileMultipleRecord.empty(
fileNumber: 4, recordNumber: 1, recordDataByteLength: 24);
await modbusClient.send(multipleRecords.getReadRequest());
multipleRecords.start();
print(multipleRecords.getNext(ModbusRecordType.int16));
print(multipleRecords.getNext(ModbusRecordType.uint16));
print(multipleRecords.getNext(ModbusRecordType.int32));
print(multipleRecords.getNext(ModbusRecordType.uint32));
print(multipleRecords.getNext(ModbusRecordType.float));
print(multipleRecords.getNext(ModbusRecordType.double));
// Ending here
modbusClient.disconnect();
}