Asking for feedback: new ModbusWorker implementation

For the last weeks and months I worked on a rewrite of the ModbusWorker implementation (triggered by Zeitlicher Ablauf der Modbus-Tasks)

See this Pull-Request:

I know there are more things to be improved in the Modbus implementation (e.g. enabling usage via delegation and now only via inheritance), but in its core this is only a rewrite of the ‘ModbusWorker’, that schedules the execution of modbus Tasks. Mainly it should perform much better in reading registers ‘as late as possible’ and writing ‘as early as possible’ while handling defective components (e.g. meters that are not available) much better without blocking the entire bus.

As this is a quite critical component for many users of OpenEMS, I’d be glad to receive input from the Community before merging this into the next Release, e.g. as comments here (with trace-logs enabled) or as review on Github. Currently my target for merging this is 2023.9.0.

Thank you in advance!

See readme below for details on the new version:

Modbus

Modbus is a widely used standard for fieldbus communications. It is used by all kinds of hardware devices like photovoltaics inverters, electric meters, and so on.

Modbus/TCP

[https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java[Bridge](https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusTcpImpl.java[Bridge) Modbus/RTU Serial] for fieldbus communication via TCP/IP network.

Modbus/RTU

[https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java[Bridge](https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.bridge.modbus/src/io/openems/edge/bridge/modbus/BridgeModbusSerialImpl.java[Bridge) Modbus/TCP] for fieldbus communication via RS485 serial bus.

Implementation details

OpenEMS Components that use Modbus communication, must implement the ModbusComponent interface and provide a ModbusProtocol. A protocol uses the notion of a Task to define an individual Modbus Read or Write request that can cover multiple Modbus Registers or Coils depending on the Modbus function code. It is possible to add or remove tasks to/from a protocol at runtime or to change the execution Priority. The Modbus Bridge (Bridge Modbus/RTU Serial or Bridge Modbus/TCP) collects all protocols and manages the execution of Tasks.

Execution of Modbus Tasks

Execution of Modbus Tasks is managed by the ModbusWorker. It…

  • executes Write-Tasks as early as possible (directly after the EXECUTE_WRITE event)
  • executes Read-Tasks as late as possible to have values available exactly when they are needed (i.e. just before the BEFORE_PROCESS_IMAGE event). To achieve this, the ModbusWorker evaluates all execution times and ‘learns’ an ideal delay time, that is applied on every Cycle - the ‘CycleDelay’
  • handles defective ModbusComponents (i.e. ones where tasks have repeatedly failed) and delays reading from/writing to those components in order to avoid having defective components block the entire communication bus. Maximum delay is 5 minutes for read from defective components. ModbusComponents can trigger a retry from a defective Component by calling the retryModbusCommunication() method.

Priority

Read-Tasks can have two different priorities, that are defined in the ModbusProtocol definition:

  • HIGH: the task is executed once every Cycle
  • LOW: only one task of all defined LOW priority tasks of all components registered on the same bridge is executed per Cycle
    Write-Tasks always have HIGH priority, i.e. a set-point is always executed as-soon-as-possible - as long as the Component is not marked as defective

Channels

Each Modbus Bridge provides Channels for more detailed information:

  • CycleTimeIsTooShort: the configured global Cycle-Time is too short to execute all planned tasks in one Cycle
  • CycleDelay: see ‘CycleDelay’ in the ‘ModbusWorker’ description above

Logging

Often it is useful to print detailed logging information on the Console for debugging purposes. Logging can be enabled on Task level in the definition of the ModbusProtocol by adding .debug() or globally per Modbus Bridge via the LogVerbosity configuration parameter:

  • NONE: Show no logs
  • DEBUG_LOG: Shows basic logging information via the Controller.Debug.Log
  • READS_AND_WRITES: Show logs for all read and write requests
  • READS_AND_WRITES_VERBOSE: Show logs for all read and write requests, including actual hex or binary values of request and response
  • READS_AND_WRITES_DURATION: Show logs for all read and write requests, including actual duration time per request
  • READS_AND_WRITES_DURATION_TRACE_EVENTS: Show logs for all read and write requests, including actual duration time per request & trace the internal Event-based State-Machine

The log level via configuration parameter may be changed at any time during runtime without side-effects on the communication.

Regards,
Stefan

Hi Stefan,

Thanks for the detailed update on the ModbusWorker improvements. Your suggested improvements are pretty much in line with what I have seen on similar solutions.

I think having the option to set individual (target) channel sample periods are a nice solution to have, since it gives you the flexibility to monitor one channel at a higher frequency than another. In the case where both channels are lower priority and not critical for control. I do understand that this would add a lot of complexity with setup and could cause more problems in other areas than what the benefit would be for this functionality. Just something to think about, but it is very handy when you have a large amount of channels that needs to be monitored and you want to minimise data and storage usage.

Anyhow, that’s my 2 cents on it, keep up the good work!

Cheers

Thanks for the feedback! Those sampling periods might be a good improvement to have in future. They could be implemented via the new TasksSupplier, similarly to the Priority concept. I’d still leave this out for now, because I did not want to introduce completely new concepts with this PR, but rather focus on not-breaking things. :slight_smile:

Regards,
Stefan