GithubHelp home page GithubHelp logo

modbus_client's Introduction

Introduction

This is a set of three packages implementing Modbus Client sending requests to a remote device (i.e. Modbus Server).

  • Modbus Client is the base implementation for the TCP and Serial packages.
  • Modbus Client TCP implements the TCP protocol to send requests via ethernet networks.
  • Modbus Client Serial implements the ASCII and RTU protocols to send requests via Serial Port

The split of the packages is done to minimize dependencies on your project.

Notes for serial implementation

  • For Flutter projects: you only need to add flutter_libserialport package to your own project (i.e. run flutter pub add flutter_libserialport)
  • For Dart projects: you need to build the serial library libserialport build instructions. As a workaround you can create a dummy Flutter project, add the flutter_libserialport package, build it and copy the auto-generated serialport library from the build folder into your project's root folder or wherever your binary code will run.

Features

  • Auto connection mode: specify how the send command behaves by auto connecting and auto disconnecting from the client by setting the ModbusConnectionMode
  • Unit id: both the Modbus Client and the Request can specify the target unit id. This can be useful when using serial clients where more units/devices can be attached to one serial client.
  • Response timeout: A timeout waiting the response can be set in the Modbus Client instance or in the Request itself.
  • Server discovery (TCP only): discovers the modbus server from a starting IP address Modbus Client TCP.
  • Connection timeout (TCP only): specify a connection timeout for the Modbus Client TCP.
  • Delay after connect (TCP only): you can apply an optional delay after server connection. In some cases (e.g. Huawei SUN2000 inverter) the server will not respond if requests are sent right after the connection.
  • Element types: this package offers a variety of element types: ModbusNumRegister (int16, uint16, int32, uint32), ModbusBitElement, ModbusEnumRegister, ModbusStatusRegister, ModbusBitMaskRegister, ModbusEpochRegister, ModbusBytesRegister.
  • File records: support File Records function code 0x14 and 0x15 of different types of numeric records (int16, uint16, int32, uint32, float and double).
  • Endianness: Define how bytes are arranged in the modbus register.
    • ABCD(swapWord: false, swapByte: false)
    • CDAB(swapWord: true, swapByte: false)
    • BADC(swapWord: false, swapByte: true)
    • DCBA(swapWord: true, swapByte: true)
  • Group of elements: in order to optimize request you can create group of elements.
  • Custom requests implementation: you can easily implement custom request by overriding the Request class, assigning the request PDU (i.e. the protocolDataUnit) and override the internalSetFromPduResponse method or you override the Element Request class by overriding the internalSetElementData method.
  • Logging: Modbus Client libraries have logging enabled. You can activate logging with the root logger or by creating a ModbusAppLogger instance in your app.

Usage

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.

Read Request

import 'package:modbus_client/modbus_client.dart';
import 'package:modbus_client_tcp/modbus_client_tcp.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));

  // Discover the Modbus server
  var serverIp = await ModbusClientTcp.discover("192.168.0.0");
  if (serverIp == null) {
    ModbusAppLogger.shout("No modbus server found!");
    return;
  }
  
  // Create the modbus client.
  var modbusClient = ModbusClientTcp(serverIp, unitId: 1);

  // Send a read request from the element
  await modbusClient.send(batteryTemperature.getReadRequest());

  // Ending here
  modbusClient.disconnect();
}

Write Request

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;

  @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));

  // Discover the Modbus server
  var serverIp = await ModbusClientTcp.discover("192.168.0.0");
  if (serverIp == null) {
    ModbusAppLogger.shout("No modbus server found!");
    return;
  }
  
  // Create the modbus client.
  var modbusClient = ModbusClientTcp(serverIp, unitId: 1);

  var req = batteryStatus.getWriteRequest(BatteryStatus.running);
  var res = await modbusClient.send(req);
  print(res.name);

  modbusClient.disconnect();
}

Modbus Elements

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.

Bit and Numeric Elements

Typical elements are simple bit and numeric values:

  • ModbusDiscreteInput
  • ModbusCoil
  • ModbusInt16Register
  • ModbusUint16Register
  • ModbusInt32Register
  • ModbusUint32Register
  • ModbusFloatRegister
  • ModbusDoubleRegister

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.

Enum Element

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();
}

Status Element

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"),
    ]),

Bit Mask Element

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]"),
    ]),

Epoch/DateTime Element

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);

Epoch/DateTime Element

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);

Bytes Array Element

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:

  • Read & Write have different limits
  • Modbus RTU and TCP have different limits
  • Device dependent limits To get the right limit please refer to Modbus specs and your device manual.
var bytesRegister = ModbusBytesRegister(
    name: "BytesArray",
    address: 4,
    byteCount: 10,
    onUpdate: (self) => print(self));

// Create the modbus client.
var modbusClient = ModbusClientTcp("127.0.0.1", 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();

Element Group

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();
}

Modbus File Records

This library supports function codes 0x14 and 0x15 to read and write different types of numeric records.

File Record Types

  • ModbusFileInt16Record
  • ModbusFileUint16Record
  • ModbusFileInt32Record
  • ModbusFileUint32Record
  • ModbusFileFloatRecord
  • ModbusFileDoubleRecord
  • ModbusFileMultipleRecord
import 'dart:typed_data';

import 'package:logging/logging.dart';
import 'package:modbus_client/modbus_client.dart';
import 'package:modbus_client_tcp/modbus_client_tcp.dart';

void main() async {
  // Simple modbus logging
  ModbusAppLogger(Level.FINE);

  // Create the modbus client.
  var modbusClient = ModbusClientTcp("127.0.0.1", 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();
}

modbus_client's People

Contributors

cabbi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

modbus_client's Issues

Pass byteCount in constructor

Hello, first of all thank you for your great work and this awesome library.

I am playing around and try to read address which is as follows:
Screenshot 2024-02-07 at 10 08 26
Turning the first part to decimal should be 154 for the address:

    ModbusUint16Register(
        name: "ConsumEnergyTodayInput",
        type: ModbusElementType.inputRegister,
        address: 154,
        onUpdate: (self) => print(self)),

but I am getting incorrect output. It seems that the problem lays in length of the address as it lays over 2 bytes. I tried to pass byteCount as a parameter but getting this error:
Screenshot 2024-02-07 at 9 35 48

Could you please advise how to read such address?

Thank you very much in advance.

[feature] number format support

In modbus, the data maybe have 4 formats:

big-endian: AB CD
little-endian: DC BA
big-endian byte swap: BA CD
little-endian byte swap: CD AB

It seems should have an option to support.

modbus_client depends on collection ^1.17.2

First of all thanks for making this package available!

When I try to install it on a fresh Flutter app (Counter app), I get the following error message:

because every version of flutter from sdk depends on collection 1.17.1 and every version of modbus_client depends on collection ^1.17.2, flutter from sdk is incompatible with modbus_client.
So, because nfcapp_flutter depends on both flutter from sdk and modbus_client any, version solving failed.

Any ideas how to resolve this?

Thx!
Matthias

Wrong Function Code Issue

Hello,
As we are testing with Modbus Slave Tool, reading holding register with the packets below returns
ModbusResponseCode.requestRxWrongFunctionCode

Slave ID 0x14 and Function Code is 0x03 is correct but couldn't figure it out why it returns wrong function code.
Tx: 14 03 00 06 00 01 66 CE
Rx: 14 03 02 0C 80 B1 27

Thanks.

03 (0x03) Read Holding Registers & 16 (0x10) Write Multiple Registers

Hello,
As far as I see, reading and writing operations are limited with 16 and 32-bit datatypes.
Modbus function codes 0x03 and 0x06 support writing and reading multiple bytes. I tried to add an implementation as below, but I failed. What I am trying to achieve is modifying the sendWriteRequest and readRequest methods as they accept byte arrays with lengths of more than 4 bytes.
I set the class with the byteCount required variable, and if byteCount > 4, it uses this write method. After this call, it says Modbus is not initiated.


ModbusWriteRequest _getWriteRequestWithCustomByteLength(dynamic value, {bool rawValue = false, int? unitId, Duration? responseTimeout}) {
  if (type.writeMultipleFunction == null) {
    throw ModbusException(
      context: "ModbusElement",
      msg: "$type element does not support custom byte length write request!");
  }
  Uint8List uint8List = Uint8List.fromList(value);

  var pdu = Uint8List(7 + byteCount); // Adjust the PDU length according to custom byte length
  var byteData = ByteData.view(pdu.buffer);
  byteData.setUint8(0, type.writeMultipleFunction!.code);
  byteData.setUint16(1, address);
  byteData.setUint16(3, byteCount ~/ 2); // Register count
  byteData.setUint8(5, byteCount); // Byte count

  // Set value in the PDU
  for (int i = 0; i < byteCount - 2; i++) {
    // byteData.setUint8(6 + i, uint8List[i]);
    byteData.setUint8(6 + i, uint8List[i]);

    if (kDebugMode) {
      print(uint8List[i]);
    }
  }

  return ModbusWriteRequest(this, pdu, type.writeMultipleFunction!, unitId: unitId, responseTimeout: responseTimeout);
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.