P183 modbus RTU - #5390
Conversation
|
Can you look at the timing stats for this plugin you added? Please take a look at what I did for the Eastron plugin, where I set a queue of registers to read. My intention is to make all ESPEasy plugins using modbus use this approach to share access to the same Modbus bus. Also the ESPEasySerial for (nearly) all plugins is using the same set of PCONFIG() for the serial config. |
|
I have been busy for some time. Try to pickup the comments soon. Indeed the exchange takes some time and I will look into creating a queue. Eastron was my inspiration, but I did not look in most of the details as it was mainly device specific handling. Serial settings were copied from Eastron. To share the modbus I need some more insights how the sharing is intended. I have too little experience with some details in ESPEasy. |
|
A small introduction of myself. I started as embedded software developer using mainly C, but I am for quite some time software architect and not doing professional coding anymore. I am definitely not a C++ expert. I am working for a large company building complex machines. I like to pickup smaller embedded software projects and domotica. And I really like the way ESPEasy is set up. I see some issues with the current P078 implementation. The modbus and Eastron device specific features are mixed over the various "layers". There is the "plugin" that takes care of the configuration and external data (variable, config, web representation). Then the "data_struct" to put the behavior of the plugin in a C++ friendly environment (away from .ino). And there is a "Eastron library" in SDM.cpp. If understood you well the requirements are:
The P078 implementation uses a queue that can manage multiple plugins in theory. For me it is unclear how it can differentiate between multiple links. The plugin defines the serial link, if there are multiple plugins connected it seems the last plugin that initializes defines the link properties. Is this desired behavior? The queue knows it is handling SDM messages and delivers the received holding register directly into the uservar: My proposal would be to split the code into the following classes:
Broker and link should be outside any plugin as they can be shared by multiple plugin classes. As they are both Modbus specific they can be in a single file sharing a header file. I still need to think about the details how to exchange data and fit the queue neatly in the design. What has to be in and how does it return results. The Modbus link should be able to handle both the RTU binary and ASCII format. It should be able to handle other Modbus message types. Both option for future only :-) One thing to consider: |
|
Well hats off to you sir, as you seem to have a very good idea of the layers we have right now :) Right now, I am working on another pull-request to do a complete rewrite for ESPEasy's WiFi/network implementation. When clicking "Edit" on such an adapter, you get the specific settings, very similar to how controllers and tasks are being dealt with. My next idea for a follow-up on this is to add a tab for "Interfaces" (or buses, name is not yet clear). Especially some of those interfaces which share a bus for multiple devices, need a handler to deal with all devices and pending transactions on the bus. This idea is already implemented (in a bit too specific way) by keeping track of a list of registers to read and where to put the result. So to "fix" this, I imagine there might be some new Then adding a main handler would probably be something for a next pull-request so we can think of a more generic way to manage modbus handlers. The main advantage with this is that there is no longer a collision when accessing modbus devices on the same bus and there is no active blocking wait for a reply from the device, which may take quite some time. |
|
Looks good. By the way, I am not in any hurry to push my branch. Please continue the framework and let me know where I can contribute for something modbus specific. A suggesting is to remove the word MODBUS in the event and keep is a bit more abstract like BUS_READ_RESULT. This can then be any pending bus transaction. I think that I2C could also benefit. If we add this callback trough an event and a central bus or link administration the singleton management layer will be very simple. The plugin know the bus type and index and the manager returns the associated bus object. Maybe do some admin to check how many active plugins are coupled to the bus; and do initialization termination when the first joins or the last leaves. By the way, I saw a Modbus_RTU file. Is this where the new bus management stuff should grow? |
|
Not likely that this will remain the (file) structure. For the WiFi/network rewrite, I'm introducing namespaces like The idea of having a generic bus callback/event also seems OK, as long as the bus manager/handler does keep in mind which task may be expecting specific bus responses. |
|
Sorry it is still work in progress I wanted to set aside. I had some other duties and am just picking up this project. Will update it soon with a more crisp design. Keep in mind: I am not a C++ coder, any corrections and suggestions are welcomed.
Main issues to resolve:
Is there a way to store design documentation with a plugin? |
| if (_modbus_link != nullptr) { | ||
| _modbus_link->freeTransactions(this); | ||
| ModbusMGR_singleton.disconnect(_deviceID); | ||
| _modbus_link = nullptr; |
There was a problem hiding this comment.
Why not use some (weak) std::shared_ptr like structure for this?
My changes have been merged into |
|
Simple fix if you know how to get to the conflicts :-) |
|
Here's a .diff file (zipped so GH will allow the upload). The extracted file should be placed in the root of your local repository, then from a terminal (best to open/use that from inside VSCode, shortcut: That should go without any issues. |
It states "error: No valid patches in input (allow with "--allow-empty")". And when double clicking the diff it states "Already up to date". But I don't see modifications in mudbus_mgr.cpp showing how to use the new library. I am missing a clue on how I can show multiple, separate, serial links on the same web page as I do for the Modbus interface(s). I would like to see your modified version. |
|
Hmm, not sure what went wrong, here's an updated diff: |
Thanks a lot, working on it. |
|
I got it working with a minor duplicate variable definition fixed. Thanks again for the help. I like the selection of the serial port types using a selection variable. For the user the "disabled" it is a bit unclear. It is shown, but cannot be selected. Would be nice it it was greyed out or even skipped. But I am not the HTML expert.... There is something strange in how the I2C settings are handled. RX & TX pins are drawn separately with addFormPinSelect() from the calling webform setup function like show_modbus_interfaces(). The I2C selection is drawn from within serialHelper_webformLoad(). This makes the interface between the Plugin_Helper_serial library and the using form setup code quite complex. Especially as there are several naming conventions involved for the pins and other values. Some documentation would be welcome to prevent undesired usage. |
|
I have a question about the I2C serial. Does it support automatic direction changing? I copied the DE/RE switching pin from other RS485 based plugins and the serial interface has some support for it. The UART seems to have some hardware support for this. But is this also supported for I2C and software serial? For now the modbus setup page does support selection of the DE/RE pin for all serial port types, but it does not guarantee it works. I am using a serial to RS485 converter that does an automatic switching between TX and RX. I cannot test the pin-controlled switching. I updated the pull request with the update to use the new serial library functions from tonhuisman. What is next to conclude this exercise. It grew a bit bigger that expected when I started to play with a simple Modbus device... Also note that conversion of existing plugins will not be easy as the existing plugins include the setup of the serial link. That is now delegated to the Modbus link and the interfaces page. What needs to be reviewed carefully:
|
AFAICS, there's a TODO in the ESPEasySerial driver for the SC16IS752 to investigate if this can be enabled. The chip does seem to support that, so it should be possible to implement. OTOH, a similar TODO is in the code for SW-Serial, so there it also isn't implemented (yet). You could, for now, remove the
Most parts are already in place from line 3160 in |
| serialHelper_webformLoad(_modbus_links[link].port, | ||
| _modbus_links[link].serial_rx, | ||
| _modbus_links[link].serial_tx, | ||
| INCLUDE_HW_SERIAL + INCLUDE_SW_SERIAL + INCLUDE_I2C_SERIAL, |
There was a problem hiding this comment.
The INCLUDE_SW_SERIAL and INCLUDE_I2C_SERIAL options don't support DE/RE or automatic RX/TX switching (yet), and I2C serial has a fixed buffer size of 64 bytes that can easily cause overflows on high-volume Modbus buses, so these ports should better not be used for Modbus, and thus not included here for now.
Also, to avoid weird behavior, they shouldn't be combined by adding (+) but or-ed (|), so a duplicate/alias value doesn't mess things up unexpectedly.
There was a problem hiding this comment.
Also SW-serial is really horrible on some ESP32's like the ESP32-C3.
SW Serial should be used as absolute last resort, so best to leave it out for this ModbusRTU feature.
There was a problem hiding this comment.
Thanks, the answers I was looking for. Will disable them.
|
|
||
| "," | ||
| Dumps the holding registers in the address range ``<start>`` until ``<end>``. | ||
| Dumps the holding registers in the address range ``<start>`` until ``<end>``. The output is un the logging. |
There was a problem hiding this comment.
Indeed. It is mend for debugging. I don't know how I can provide a table for debugging purposes in another way. Or do you only intend to attend to the spelling mistake.
By the way: I also don't know how I can best provide a holding register from the cache as result of a command.
| const String cmd = parseString(string, 1); | ||
|
|
||
| if (equals(cmd, F("register"))) { | ||
| uint16_t address = parseString(string, 2).toInt(); |
There was a problem hiding this comment.
The convention for PLUGIN_GET_CONFIG_VALUE is to use a period (.) as the separator, not a comma (though some plugins support the comma as a fall-back).
const String cmd = parseString(string, 1, '.');
And using the standard unsigned int parser (that handles 0x hex and 0b binary notation):
uint32_t address{};
if (validUIntFromString(parseString(string, 2, '.'), address)) {
uint16_t value = 0;
... the rest of your code up to and including success = true;
}
|
Thanks, Difficult to spot on my screen. I am not sure where to put this P183 plugin. It definitely does not fit on the ESP8266 builds. It is running fine for some time on my ESP32-C3. But I cannot guarantee is is production proof yet. What is the release strategy here? |
|
Still an issue with the documentation generation. Seems in my environment the "✓" is not recognized by codecs.charmap_encode(input,self.errors,encoding_table)[0]. This results in missing the overviews per collection. I did not touch the code so I expect this to be an issue in by build environment. Other question is in which collection to put the P183 Modbus registers plugin. With all new Modbus facilities code behind it I would release it as "experimental", but we can assign it to a collection on ESP32 boards. |
|
You can of course put it in the MAX builds and since quite a lot of power meters and industrial sensors are using Modbus RTU, I guess Energy and Climate? |
|
About the checkmarks, you can compare it with the checkmarks used here: https://espeasy.readthedocs.io/en/latest/ESPEasy/ESPchips.html |
Strange it is in a file not touched by me: \ESPEasy\docs\builds_overview.py The mentioned Supported ESP Chips page only shows the red cross symbol, not the checkmark "✓". My generated code shows the same page with the red cross symbol. I also copied the symbol in plain documentation code (.repl). Works fine. I suspect something with language/character sets is wrong on my Windows machine or in the Python library. It is the Python interpreter that issues an error when calling function output.write(). For now I don't see it blocking for the Modbus delivery. Will put it in MAX for now. |


Initial version of a simple Modbus RTU plugin. This plugin handles a Modbus device as a set of holding registers. Up to 4 holding registers can be read into the 4 values of the plugin. The plugin number is 183 as agreed elsewhere.