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?
| // Transaction failed in suspicious conditions, wait for timeout to recover from the error. | ||
| case ModbusQueueState::RECOVERING: | ||
| { | ||
| if (timePassedSince(it->_startTime) > it->_timeout) { |
There was a problem hiding this comment.
This feels like it should be a member function of ModbusTransaction as it uses all parameters from that object.
There was a problem hiding this comment.
It processes indeed the transaction. But it also needs the context like which serial link is involved. In this specific part it needs to know the result of the previous Transaction.
As written above I wanted the Transaction to be a complete passive intermediator between device and link. The link is the active element going over the queue and picking the Transaction to be put on the wire. If we shift the responsibility to handle part of the transaction handling to this class we also have to make it aware of some context and when to do certain evaluations (coupling it to the 10-per-second handling).
If this remark is about evaluating if the timeout has expired: Yes we can delegate this to the Transaction. A new function it->timeoutPassed() can be introduced. Is used only in the Modbus_link. I expect 2 or 3 times. But it does not make it more efficient not removes the need for Modbus_link to know many details stored in Transaction.
|
You can add some enum and all other related functions like for turning it into a flash string. Just some random example of how it can be used: ESPEasy/src/src/Helpers/PeriodicalActions.cpp Lines 80 to 82 in 2776ae9 |
|
I refactored the link processing in Modbus_link. It should better fit the discussions above where we discovered some issues due to the sequence Modbus_devices may change a transaction from NOT_QUEUED or QUEUED. Now an _activeTransaction is kept with the Modbus_link. This transaction must be completed before a new transaction can be picked up from the queue. If no active transaction is on the wire the queue is not only checked for the next transaction, but it is also purged from discarded transactions. Note that devices may discard transactions on their own. Typically done when the Modbus_device is destructed. I will pick up comments from Ton a later moment. Missed them before committing. |
| void ModbusMGR_struct::show_modbus_interfaces() | ||
| { | ||
| String options_baudrate[MODBUS_MAX_BAUDRATE_SEL]; // Array to hold the baudrate options for the selector | ||
| String options_port[static_cast<size_t>(ESPEasySerialPort::MAX_SERIAL_TYPE)]; // Port otions for the selector, only valid ports will be |
There was a problem hiding this comment.
This array will be too small to hold all available ports, as MAX_SERIAL_TYPE isn't based on the highest value for a serial port definition.
We'll probably have to add a selector that doesn't store the GPIO pins in the standard Pin Settings fields, like serialHelper_webformLoad() currently does. (I'll see what I can do to solve this)
In that function you can also see how the current selector is filled with all available serial ports for the chip at hand (I've been testing on an ESP32-P4 that has 5 available serial ports, and was missing a couple 😅)
As a workaround I edited lib/ESPEasySerial/ESPEasySerialPort.h to move the serial3..serial5 definitions between usb_cdc_0 and MAX_SERIAL_TYPE.
There was a problem hiding this comment.
As a workaround I edited
lib/ESPEasySerial/ESPEasySerialPort.hto move theserial3..serial5definitions betweenusb_cdc_0andMAX_SERIAL_TYPE.
Make sure the enum values themselves (apart from MAX_SERIAL_TYPE ) are not changed as those are stored in the settings.
There was a problem hiding this comment.
I am struggling with the Serial concepts in ESPEasy. For the on-chip hardware ports we have an entry for each one in this ENUM. This is also causing the issues when a new chip introduces new ports or port types. For the software serial connections we can instantiate multiple serial links of this same type. I assume the same holds for the I2C serial. It makes sense to select specific ports if they have hard-code properties like the TX and RX pins. It also makes sense to allow multiple instances of the same type like software serial and I2C serial. But for those we need other means to separate the instances... What is the design behind the serial configuration?
This section of the Modbus link configuration needs some refactoring anyway. What I need is a way to select what type (or instance) of serial link to be used. And according to the selection different parameters may be needed.
For now I just want to make a drop-down selection menu containing the available serial port options in the ENUM. For this I construct a lookup table from dorp-down index to ESPEasySerialPort value. As such the total number of values in the enum should be sufficient. Problem is to filter the non-existing values for the specific platform. Next challenge is to filter the options that are useless for Modus. A challenge touched earlier in this pull-request and not solved yet.
Once we can select the right set of available serial types the parameters to setup that link shall be adapted to the selected serial type. I did not give it much attention as for now my focus was on the functional design and implementation. Getting the link working and improving it with help of TD-er Focus should shift to the configuration of the Modbus links (and documentation). I need a lot of help here as I have no experience with ESPEasy serial communication and the way it is/can be configured.
First step is to determine how to create a setup form that allows configuring multiple serial ports. First field should be the type from a drop-down list showing the available options for that specific platform/chip. As I see it this should be a reusable function provided by the serial subsystem (most likely Plugin_helper_serial or ESPEasySerial). What do we have available or should be added?
There was a problem hiding this comment.
First step is to determine how to create a setup form that allows configuring multiple serial ports. First field should be the type from a drop-down list showing the available options for that specific platform/chip. As I see it this should be a reusable function provided by the serial subsystem (most likely Plugin_helper_serial or ESPEasySerial). What do we have available or should be added?
I have started working on this, based on the comments I made above.
It is mostly some refactoring of the code that's currently hiding in serialHelper_webformLoad().
There was a problem hiding this comment.
Thanks @tonhuisman I will wait for results and/or recommendations.
I assume serialHelper_webformLoad() and serialHelper_webformSave() are pretty close. Needs to add an index to allow multiple serial ports on a single page. Unfortunately I don't know anything about setting up web pages with scripts. Maybe also allow a selection of which types of ports to exclude (hardware; software; USB; I2C)
| // Allow multiple ports being configured on a single web page, by using a linkId to identify the port. | ||
| /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
There was a problem hiding this comment.
Hmm, as said earlier, I've already started work on handling this situation. This will make merging stuff much harder 🤔
There was a problem hiding this comment.
We can leave out my part, as long as I can get the serial type and ports returned... I just hacked the original code such that each port has its own script by adding a sequence number.
|
I tried to generalize the plugin_helper_serial library. Two functions added that allows configuring multiple serial ports on a web form. It mimics the set used to configure the port for a plugin. Note that I have no experience on web forms or scripts. This needs a good review. Once accepted we can look in code de-duplication with the original functions. I also saw that the flag allowSoftwareSerial is not used in the code. But I don't see an easy way to remove an entry from a static array definition. |
Another idea can be to add an optional array of bool (or other type for attributes) like we have on some similar other functions to add a selector. |
That is taken care of in my refactored code 😉 |
Well, you'll have to revert your changes to I expect my PR to be merged soon, so you should be able to update with latest |
|
|
I am not very experienced with github. I try to sync with the mega branch, but that results in a warning to drop all my commits to get in sync again. How can I do a manual merge? I would like to drop all changes in the serial library and manually fix other issues. Where can I find the changes made by tonhuisman? Are these already in mega? |
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. |

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.