Skip to content
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
38c20bc
P183 initial state
flashmark Aug 24, 2025
46f2a50
P183 2nd attempt
flashmark Aug 24, 2025
7d0e802
P183 Add initial documentation
flashmark Aug 27, 2025
cc31b10
P183 Add documentation (preliminary)
flashmark Aug 28, 2025
53ec930
P183 Minor rework
flashmark Aug 31, 2025
a715544
Baseline before refacoring
flashmark Sep 6, 2025
5f8868a
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Sep 15, 2025
aa34ac3
P183 Uncrustify
flashmark Sep 15, 2025
02f46b3
Merge branch 'P183_Modbus_registers' of https://github.com/flashmark/…
flashmark Sep 15, 2025
11cf754
Starting complete make over
flashmark Sep 30, 2025
e7aabda
P183 Rework phase 1
flashmark Oct 6, 2025
1ee2b35
Merge branch 'mega' into P183_Modbus_registers
flashmark Oct 6, 2025
18e8b86
P183 Small refactoring
flashmark Oct 6, 2025
bd7661e
P183 Snapshot refactoring
flashmark Oct 12, 2025
8c66411
P183 Start cleaning up
flashmark Oct 15, 2025
fc27c88
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Oct 15, 2025
8f5e5f9
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Oct 17, 2025
a4dfca1
P183 Pending rework, not final
flashmark Oct 17, 2025
7795cc1
P183 Several fixes
flashmark Oct 18, 2025
6e201fb
P183 Update logging
flashmark Oct 23, 2025
280e8bf
P183: Init link fix & minor updates
flashmark Oct 23, 2025
4515c8c
P183: initial UML design documentation
flashmark Oct 23, 2025
21b427e
P183: Minor fixes and start of design documentation
flashmark Oct 26, 2025
8d5ce4d
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Oct 27, 2025
5ba2f9f
Merge branch 'mega' into P183_Modbus_registers
flashmark Dec 11, 2025
b432092
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Dec 13, 2025
c182cea
P183 Added documentation
flashmark Dec 13, 2025
67134da
Merge branch 'mega' into P183_Modbus_registers
TD-er Feb 10, 2026
3495afe
Merge branch 'mega' into P183_Modbus_registers
TD-er Feb 10, 2026
abe1a67
Merge branch 'mega' into P183_Modbus_registers
TD-er Feb 12, 2026
e93b4c3
Merge branch 'mega' into P183_Modbus_registers
TD-er Feb 13, 2026
ef31d27
[P183] Make new generic Modbus RTU PR build again
TD-er Feb 13, 2026
c322798
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Feb 16, 2026
da1e31d
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Mar 5, 2026
0098e3c
Secure work in progress, don't merge to master
flashmark Mar 20, 2026
7e01a41
Merge branch 'mega' into P183_Modbus_registers
flashmark Mar 20, 2026
934b595
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Apr 7, 2026
610c3e9
P183 snapshot
flashmark Apr 7, 2026
33e4439
Merge branch 'P183_Modbus_registers' of https://github.com/flashmark/…
flashmark Apr 7, 2026
6b774aa
P183 modbus interfaces support
flashmark Apr 16, 2026
7efc42d
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Apr 16, 2026
68bba7f
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark Apr 23, 2026
2e2ab34
Save state some init problems left
flashmark Apr 23, 2026
bb28e71
Merge branch 'P183_Modbus_registers' of https://github.com/flashmark/…
flashmark Apr 23, 2026
470d3c2
securing state, not ready yet
flashmark Apr 27, 2026
884deb3
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark May 1, 2026
c66aee9
Functionality ready, to be debugged and cleaned up
flashmark May 2, 2026
79ffeb4
Merge branch 'P183_Modbus_registers' of https://github.com/flashmark/…
flashmark May 2, 2026
afa1b6a
Small review comment updates
flashmark May 3, 2026
0f9c1dd
Rework & fixed initialization
flashmark May 3, 2026
d254ae4
P183 documentation update
flashmark May 5, 2026
9111e0d
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark May 9, 2026
30d2b12
P183 Modbus cleanup, not ready yet
flashmark May 9, 2026
115cb39
Merge branch 'letscontrolit:mega' into P183_Modbus_registers
flashmark May 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions docs/source/Plugin/P183.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
.. include:: ../Plugin/_plugin_substitutions_p18x.repl
.. _P183_page:

|P183_typename|
==================================================

|P183_shortinfo|

Plugin details
--------------

Type: |P183_type|

Port Type: |P183_porttype|

Name: |P183_name|

Status: |P183_status|

GitHub: |P183_github|_

Maintainer: |P183_maintainer|

Used libraries: |P183_usedlibraries|

Introduction
------------

Modbus is a serial communication protocol commonly used for connecting industrial electronic devices.
It is a master/slave (or client/server) protocol, which means that one device (the master) initiates communication and the other devices (the slaves) respond.
Modbus RTU (Remote Terminal Unit) is a variant of the Modbus protocol that uses binary representation of data and is typically used over serial communication lines such as RS-485 or RS-232.
This plugin supports Modbus RTU communication over a serial interface. To support RS-485 communication, an external RS-485 to TTL converter is required.

Modbus RTU uses a register-based addressing scheme, where each device has a unique address and data is stored in registers.
Registers can be of different types, such as holding registers, input registers, coils, and discrete inputs.
This plugin supports reading holding registers (function code 03) and writing to holding registers (function code 06).

The plugin can be configured to read up to 4 holding registers from a Modbus slave device and store the values in user variables.
It can also write values to a specified holding register on the Modbus slave device.

The plugin does not support any hardware specific features. Instead it is a generic plugin that can be used with any Modbus RTU compatible device. You have to lookup the correct register addresses and data formats in the documentation of the Modbus device you want to communicate with.

Supported hardware
------------------

|P183_usedby|

Configuration
-------------

* **Name**: Required by ESPEasy, must be unique among the list of available devices/tasks.

* **Enabled**: The device can be disabled or enabled. When not enabled the device should not use any resources.

Sensor
^^^^^^

.. image:: P183_sensor_config.png

The available Serial protocol settings here depend on the build used.

* **Serial Port**: The serial port to use for Modbus communication.

* **Baudrate**: The baudrate for the serial communication. Common values are 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200.

* **Modbus Device Address**: The Modbus slave device ID to communicate with (1..247).

* **Enable Collision Detection**: Enable collision detection to avoid data corruption when multiple devices are connected to the same serial bus. This requires a GPIO pin to be configured as input and connected to the bus transceiver's DE/RE pin.

* **ESP RX GPIO ← TX (RO)**: The GPIO pin to use as RX input for the serial communication. This pin should be connected to the TX output of the RS-485 to TTL converter.

* **ESP TX GPIO → RX (DI)**: The GPIO pin to use as TX output for the serial communication. This pin should be connected to the RX input of the RS-485 to TTL converter.

* **GPIO → RE/DE(optional)**: The GPIO pin to use for collision detection. This pin should be connected to the DE/RE pin of the RS-485 to TTL converter.

Output Configuration
^^^^^^^^^^^^^^^^^^^^

.. image:: P183_output_config.png

* **Number of registers to read**: The number of holding registers the plugin will read (1..4).

* **Holding register for valueX**: The Modbus holding register address to read for valueX (X=1..4).

The holding register address is a 16-bit value (0..65535). This is the register address as specified with the Modbus device and is used directly in the Modbus read holding registers (function code 03). The plugin will read the number of values specified, starting with value1

Commands available
^^^^^^^^^^^^^^^^^^

.. include:: P183_commands.repl

.. Events
.. ~~~~~~

.. .. include:: P183_events.repl

Get Config Values
^^^^^^^^^^^^^^^^^

Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ``<taskname>`` by the **Name** of the task.

.. include:: P183_config_values.repl

Plugin state
------------

This plugin uses a new Modbus facility that allows multiple Modbus devices to share the same Modbus link.
The current implementataion of the Modbus facility requires that the plugin specifies the serial link parameters.
When a Modbus link is shared between multiple plugins, the last plugin that connects to the Modbus link will determine the serial link parameters.
This may lead to unexpected behavior if multiple plugins are using different serial link parameters on the same serial port. For a deterministic behavior, make sure that all plugins using the same serial port use the same serial link parameters.

In the future the Modbus facility may be extended to configure the Modbus links as separate entities. As a result the plugins would only need to specify which Modbus link to use, instead of the serial link parameters.
This change will be backward incompatible with the current implementation. Therefore the plugin status is set to 'Experimental'.

Change log
----------

.. versionchanged:: 2.0
...

|added|
Major overhaul for 2.0 release.

.. versionadded:: 1.0
...

|added|
Initial release version.





28 changes: 28 additions & 0 deletions docs/source/Plugin/P183_commands.repl
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.. csv-table::
:header: "Command", "Extra information"
:widths: 20, 30

"
``modbus,write,<address>,<value>``

","
Write value ``<value>`` into the holding register ``<address>``.
"
"
``modbus,read,<address>``

","
Read the value from the holding register ``<address>``. Note that the value is read from the Modbus device but it will not be returned as part of the command. The value is printed in the logging.
"
"
``modbus,scan``

","
Scans the modbus address range for responding units. It will do a read function for holding register 1. Note this will take some time. Ouput is in the logging.
"
"
``modbus,dump,<start>,<end>``

","
Dumps the holding registers in the address range ``<start>`` until ``<end>``.
"
10 changes: 10 additions & 0 deletions docs/source/Plugin/P183_config_values.repl
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. csv-table::
:header: "Config value", "Information"
:widths: 20, 30

"
| ``[<taskname>#register,<reg>]``
","
| Returns the value of holding register ``<reg>`` of the device. The value is directly read from the Modbus device. The holding register number ``<reg>`` can be any valid register in the device.
"

Binary file added docs/source/Plugin/P183_output_config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/source/Plugin/P183_sensor_config.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/source/Plugin/_Plugin.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ There are different released versions of ESP Easy:
":ref:`P177_page`","|P177_status|","|P177_status_lb|","P177"
":ref:`P178_page`","|P178_status|","|P178_status_lb|","P178"
":ref:`P180_page`","|P180_status|","|P180_status_lb|","P180"

":ref:`P183_page`","[P183_status]","[P183_status_lb]","P183"

.. include:: <isonum.txt>
.. include:: _plugin_sets_overview.repl
Expand Down
2 changes: 1 addition & 1 deletion docs/source/Plugin/_plugin_categories.repl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. |Plugin_Analog_input| replace:: :ref:`P002_page`, :ref:`P007_page`, :ref:`P025_page`, :ref:`P060_page`, :ref:`P097_page`
.. |Plugin_Acceleration| replace:: :ref:`P120_page`, :ref:`P125_page`
.. |Plugin_Color| replace:: :ref:`P112_page`
.. |Plugin_Communication| replace:: :ref:`P016_page`, :ref:`P020_page`, :ref:`P035_page`, :ref:`P044_page`, :ref:`P054_page`, :ref:`P071_page`, :ref:`P087_page`, :ref:`P089_page`, :ref:`P094_page`, :ref:`P101_page`, :ref:`P118_page`, :ref:`P176_page`
.. |Plugin_Communication| replace:: :ref:`P016_page`, :ref:`P020_page`, :ref:`P035_page`, :ref:`P044_page`, :ref:`P054_page`, :ref:`P071_page`, :ref:`P087_page`, :ref:`P089_page`, :ref:`P094_page`, :ref:`P101_page`, :ref:`P118_page`, :ref:`P176_page`, :ref:`P183_page`
.. |Plugin_Display| replace:: :ref:`P012_page`, :ref:`P023_page`, :ref:`P036_page`, :ref:`P057_page`, :ref:`P073_page`, :ref:`P075_page`, :ref:`P095_page`, :ref:`P104_page`, :ref:`P116_page`, :ref:`P131_page`, :ref:`P148_page`
.. |Plugin_Distance| replace:: :ref:`P013_page`, :ref:`P110_page`, :ref:`P113_page`, :ref:`P134_page`
.. |Plugin_Dust| replace:: :ref:`P018_page`, :ref:`P053_page`, :ref:`P056_page`, :ref:`P144_page`, :ref:`P175_page`
Expand Down
12 changes: 12 additions & 0 deletions docs/source/Plugin/_plugin_substitutions_p18x.repl
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,15 @@
.. |P180_compileinfo| replace:: `.`
.. |P180_usedlibraries| replace:: `.`

.. |P183_name| replace:: :cyan:`Modbus RTU`
.. |P183_type| replace:: :cyan:`Communication`
.. |P183_typename| replace:: :cyan:`Communication - Modbus RTU`
.. |P183_porttype| replace:: `Serial`
.. |P183_status| replace:: :yellow:`TESTING` :yellow:`TESTING`
.. |P183_github| replace:: P183_modbus.ino
.. _P183_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P183_modbus.ino
.. |P183_usedby| replace:: `Modbus RTU over RS485 devices`
.. |P183_shortinfo| replace:: `Generic Modbus RTU sensor`
.. |P183_maintainer| replace:: `flashmark`
.. |P183_compileinfo| replace:: `.`
.. |P183_usedlibraries| replace:: `ESPEasySerial`
42 changes: 42 additions & 0 deletions misc/modbusFacility/Modbus_class.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
@startuml

class plugin {

}

class plugin_struct {

}

class Modbus_mgr <<singleton>> {

}

class Modbus_link {
}

class Modbus_device {

}

class Serial_port {
}

class Queue {

}

class Transaction {

}

plugin *-- plugin_struct
plugin_struct *-- Modbus_device
Modbus_device "*" -right-> "1" Modbus_link
Modbus_link "1" -right-> "1" Serial_port
Modbus_mgr "1" *-- "*" Modbus_link
Modbus_mgr "1" -- "*" Modbus_device
Modbus_link *-- "1" Queue
Queue o-- "*" Transaction
Modbus_device "1" --> "*" Transaction
@enduml
11 changes: 11 additions & 0 deletions misc/modbusFacility/Modbus_link_state.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@startuml
[*] --> NOT_QUEUED
state active {
NOT_QUEUED --> QUEUED : queueTransaction()
QUEUED --> MESSAGE_SENT : link inactive
MESSAGE_SENT --> RESPONSE_RECEIVED : received valid response
MESSAGE_SENT --> ERROR_OCCURRED : response timeout or invalid response
}
active -down-> READY_FOR_DESTROY : freeTransaction()
READY_FOR_DESTROY --> [*]
@enduml
40 changes: 40 additions & 0 deletions misc/modbusFacility/Modbus_notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
The MODBUS_FAC facilities intend to provide a framework that allows multiple Modbus RTU devices to connect to a single physical Modbus link. It supports multiple physical Modbus links in parallel.
This is realized by introducing 3 classes:
- Modbus_device: Represents a single device. The Modbus_device is responsibel for coding/decoding the Modus protocol.
- Modbus_link: Represents a single Modbus conenction. Each Modbus_link object is connected to a separate serial link.
- Modbus_mgr: Singleton manager that manages all links and keeps track of which devices are connected to which link.

The main design requirement is to allow multiple plugins to use the same Modbus link. This is achieved by separating the link control from the device control.
Modbus connunication consists of a request-reply message exchange over a serial link. After a request is sent the bus is occupied until the reply is received or a timeout occurred.
A matching request-reply message pair is called a transaction in MODBUS_FAC.

The main design constraint for the MODBUS_FAC is to reduce the time wasted in waiting for a response message. For this a transaction queue is used.
The queue is owned by the Modbus_link. A Modbus_device puts a request message on the queue and does not wait for the reply.
Once the matching reply is received by the Modbus_link the Modbus_device is notified by a callback function.

The Modbus_device uses transactions to communicate to the associated hardware module. Messages are encoded in Modbus RTU protocol format by the Modbus_device.
Once the request message is created it is transferred to the Modbus_link where it is added the the transaction queue.
When the transaction is done, either a response message is received or a timeout occurred, the Modbus_device callback function is called and the response message is decoded and evaluated.

The Modbus_link uses a queue to handle transactions sequentially. Each transaction has a state to track if it is queued, being processed, or completed. At maximum one transaction is being processed at a time.
The modbus_link uses an ESPEasy serial link for the actual transmit and receive of the messages.

To match a Modbus_device with a Modbus_link a singleton Modus_mgr is used. This Modbus_mgr owns all Modbus_links and determines to which link a Modbus_device is assigned.
In case no matching link is found the Modbus_mgr creates a corresponding Modbus_link. Currently ESPEasy has no means to configure a Modbus_link object with the serial link properties. Instead the serial link is configured in the plugin.
The plugin passes the configuration data to the device. When the Modbus_device requests the Modbus_mgr to connect to a Modbus_link it passes the serial parameters. The Modbus_mgr uses the serial port type to distinguish the links.
Whenever a new device is connected to a Modbus_link the link will be reprogrammed with the new serial parameters. Note that all devices using the same serial link shall provide the same serial link parameters.
In case of different serial configuration parameters there is no guarantee which set will be used for the Modbus_link.

In future a separate configuration screen should be implemented to configure each Modus_link. Once this is available the Modbus_device can mention the link identification without the need to pass all configuration parameters.

The current implementataion of the Modbus_mgr uses the serial port (ESPEasySerialPort enum) to identify the Modbus_link. This implies that only one software serial link can be used.
It should be possible to extend this by checking the assigned RX and TX pins when a software serial port is selected.

Drawback of the queue mechanism is the need for the Modbus_device (and the associated plugin) to wait for the response message without blocking the CPU. For this the Modbus_device needs to track "state".
Either the plugin can queue only a single transaction or it must be able to track for which transaction in the queue a response is received.
To keep this state tracking generic the transaction contains a pointer to a userState and userData. The Modbus_device can use these values depending on the transaction type to return state information to the plugin.

The owning plugin can only return a value once it is received. For repetitive read events the previous received value can be returned.
For specific read commands either a polling mechanism must be implemented or the plugin blocks until the response is received.
The most efficient mechanism would be a trigger event that can be sent by the Modbus_device once the data is received. Such trigger event for plugins can reduce the CPU load by removing the need for regular polling (e.g. ten_per_second) or blocking on a transaction.
For this the plugin event must be extended with the possibility to send "updates" to the plugin. Such an extension shall be generic and not Modbus specific. It is beyond the Modbus facilities development to add such new generic plugin features.
56 changes: 56 additions & 0 deletions misc/modbusFacility/Modbus_seq1.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@startuml
actor System as system
participant Plugin as plugin
participant Plugin_struct as plugin_struct
participant Modbus_device as modbus_device
participant Modbus_mgr as modbus_mgr
participant Modbus_link as modbus_link
queue Queue as queue

system -> plugin ++ : init
create plugin_struct
plugin -> plugin_struct ++ : new()
create modbus_device
plugin_struct -> modbus_device ++ : new()
modbus_device -> modbus_mgr ++ : connect()
alt link not exists
create modbus_link
modbus_mgr -> modbus_link : new()
end
modbus_mgr -> modbus_link : init()
modbus_device <-- modbus_mgr -- : link
plugin_struct <-- modbus_device --
plugin <-- plugin_struct --
system <-- plugin --

system -> plugin ++ : read
plugin -> plugin_struct ++ : read()
plugin_struct -> modbus_device ++ : readHoldingRegister()
modbus_device -> modbus_link ++: queueTransaction()
modbus_link -> queue : add
modbus_device <-- modbus_link --
plugin_struct <-- modbus_device --
plugin_struct -> plugin_struct : retrieve latest value
plugin <-- plugin_struct --
system <-- plugin --

system -> plugin ++ : ten_per_second
plugin -> plugin_struct ++ : ten_per_second()
plugin_struct -> modbus_device ++ : processCommand()
modbus_device -> modbus_link ++: processCommand()
alt response available
modbus_link -> modbus_device ++ : linkCallback()
modbus_device -> plugin_struct : update latest value
modbus_link <-- modbus_device --
end
alt no transaction
modbus_link -> queue : get next transaction
alt transaction available
modbus_link -> modbus_link : send request
end
end
modbus_device <-- modbus_link --
plugin_struct <-- modbus_device --
plugin <-- plugin_struct --
system <-- plugin --
@enduml
Loading