Skip to content

P183 modbus RTU#5390

Open
flashmark wants to merge 55 commits into
letscontrolit:megafrom
flashmark:P183_Modbus_registers
Open

P183 modbus RTU#5390
flashmark wants to merge 55 commits into
letscontrolit:megafrom
flashmark:P183_Modbus_registers

Conversation

@flashmark
Copy link
Copy Markdown
Contributor

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.

Comment thread src/_P183_modbus.ino
@TD-er
Copy link
Copy Markdown
Member

TD-er commented Aug 31, 2025

Can you look at the timing stats for this plugin you added?
I suspect the reading like this will take 100's of msec per register read.

Please take a look at what I did for the Eastron plugin, where I set a queue of registers to read.
This way the waiting time per call is nihil and thus not blocking the rest of the ESPeasy functionality.

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 did not check if you did this, so please make sure to use the same way as in the most recent plugins using a serial port.

@flashmark
Copy link
Copy Markdown
Contributor Author

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.

@flashmark
Copy link
Copy Markdown
Contributor Author

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:

  • Multiple plugin instances of different plugin types can share the same physical Modbus. (Use the same link)
  • Multiple links should be possible in parallel
  • Modbus access shall not hold the CPU during message exchange

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: UserVar.setFloat(it->taskIndex, it->taskVarIndex, value);
It is a fast way to handle the data, but it makes it impossible to retrieve other data from the Modbus device.

My proposal would be to split the code into the following classes:

  • Plugin & optional data_struct to handle the user interaction as standard in ESPEasy
  • A Modbus class that handles one link. This includes a queue and Modbus packet coding/decoding.
  • A singleton Modbus "broker" that manages a list of Modbus links. The broker handles init/terminate requests from the plugins and tries to combine requests from multiple plugins in a single Modbus link.
  • Existing ESPEasy serial link class responsible for the data transport over the selected serial link

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:
Do we want to have the serial link specified through the plugin or add Modbus links as dedicated resources to the hardware page? My preference would be to keep it as is. Will make the broker a bit more complex, but we can manage that.

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Sep 6, 2025

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.
This does add a "Network" tab, just like the current "Devices" and "Controllers" tab we currently have.
And when I say "just like", it really is very similar.
So the first table is showing all network adapters and a short overview of its current state/config.

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).
This way you can define interfaces like Modbus, SPI, I2C, 1-wire, etc. (maybe also extended GPIO pins)

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.
And also as you rightfully mentioned, some main (singleton) handler to handle all interfaces of the same type.
For example, when requesting a read or write on a modbus device, you must make sure you have completed this before something else is requested.

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.
This does work fine for Eastron devices, as it is all the same. You call for a register and interpret the result, then store the value somewhere.
However this already has some limitation as it only assumes float values. There are however some registers which do not return a float.

So to "fix" this, I imagine there might be some new PLUGIN_MODBUS_READ_RESULT call, which then should be implemented in those plugins which support Modbus communications.
Then in the event (ESPEasy EventStruct), there should be the taskindex set and probably some other values too and a pointer to the read data.
Those plugins then should request a read to the Modbus handler from the PLUGIN_READ call.
This way the read is already way more generic.

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.
For example the SenseAir S8 may take 200+ msec to reply and currently does actively wait.
The same for the ACU28x (or how those power meters are called...) and probably also for those PZEM meters.

@flashmark
Copy link
Copy Markdown
Contributor Author

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?

@TD-er
Copy link
Copy Markdown
Member

TD-er commented Sep 6, 2025

Not likely that this will remain the (file) structure.

For the WiFi/network rewrite, I'm introducing namespaces like ESPEasy/net/wifi

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.
Like there are certain devices which can be used via various different bus types. For example the same sensor on I2C or SPI and/or UART.
But that's something to worry about later.

Comment thread src/_P183_modbus.ino Outdated
Comment thread src/src/Helpers/Modbus_mgr.cpp Outdated
Comment thread src/src/Helpers/Modbus_link.h Outdated
Comment thread src/src/Helpers/Modbus_link.cpp Outdated
Comment thread src/src/Helpers/Modbus_link.cpp Outdated
Comment thread src/src/Helpers/Modbus_link.cpp Outdated
Comment thread src/src/Helpers/Modbus_link.cpp Outdated
@flashmark
Copy link
Copy Markdown
Contributor Author

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 design separation into several classes:

  • Plugin: Unique for a device and implementing the interaction with ESPEasy.
  • Modbus_device: Represents one modbus device and is coupled to a plugin. Does the modbus message (de)coding.
  • Modbus_link: Represents a physical modbus link (serial link). One link can serve multiple modbus_devices.
  • Modbus_mgr: Connects the devices and the links. This singleton object knows the devices and the links. Main task is to create a link when the first device wants to connect and connect a device to a link on request.
  • Queue: Each link has a queue of pending modbus transactions.

Main issues to resolve:

  • Serial port configuration is connected to a modbus_link. But I can only configure a plugin. Workaround: last device connecting to a link determines the properties. (If the ESPEasySerialPort type is different then it is a new link, still to think how to handle multiple "software serial")
  • Transaction takes too much time to wait/poll. Instead we use a queue to prepare transactions (one message exchange). As a result we need some callback mechanism to return the results.
  • For the device to link relation the is a real callback is implemented as a class function and the link knows the modbus_device that queued the transaction.
  • For the plugin to device relation I don't know how to properly implement a mechanism that can feed back the uint16 registers from the modbus reply. Converting them to plugin output values blurs the responsibilities of the plugin and the modbus_device. This will make reuse much more difficult (like the P078 plugin).
  • To keep the link busy it needs to poll the serial link for a complete reply. For now this is done by a ten_per_second trigger from each plugin. This is a bit heavy on CPU load. I am looking for a direct triggering of the link objects. The code should be prepared for that.

Is there a way to store design documentation with a plugin?

Comment thread src/_P183_modbus.ino Outdated
Comment thread src/_P183_modbus.ino Outdated
Comment thread src/src/Helpers/Modbus_device.cpp Outdated
if (_modbus_link != nullptr) {
_modbus_link->freeTransactions(this);
ModbusMGR_singleton.disconnect(_deviceID);
_modbus_link = nullptr;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use some (weak) std::shared_ptr like structure for this?

Comment thread src/src/Helpers/Modbus_device.cpp Outdated
Comment thread src/src/Helpers/Modbus_device.cpp Outdated
Comment thread src/src/Helpers/Modbus_device.cpp Outdated
@flashmark
Copy link
Copy Markdown
Contributor Author

Can you also post the graphical rendering of the UML diagram?

Sure, Is it possible to generate some documentation with UML diagrams? I don't know much about document generator used by ESPEasy. And it should not be part of the user manual... With the callbacks the code is quite complex and if other developers want to build plugins on the Modbus facility they should be aware of the caveats.

@TD-er
Copy link
Copy Markdown
Member

TD-er commented May 3, 2026

Can you also post the graphical rendering of the UML diagram?

Sure, Is it possible to generate some documentation with UML diagrams? I don't know much about document generator used by ESPEasy. And it should not be part of the user manual... With the callbacks the code is quite complex and if other developers want to build plugins on the Modbus facility they should be aware of the caveats.

Well it is at least much easier to 'read' when it is rendered into some diagram.
And since you were using quite a specific notation syntax, I thought maybe you also would use some tool yourself to convert it into an image. Maybe something like dot (or using dot).

@flashmark
Copy link
Copy Markdown
Contributor Author

Can you also post the graphical rendering of the UML diagram?

Sure, Is it possible to generate some documentation with UML diagrams? I don't know much about document generator used by ESPEasy. And it should not be part of the user manual... With the callbacks the code is quite complex and if other developers want to build plugins on the Modbus facility they should be aware of the caveats.

Well it is at least much easier to 'read' when it is rendered into some diagram. And since you were using quite a specific notation syntax, I thought maybe you also would use some tool yourself to convert it into an image. Maybe something like dot (or using dot).

I am using PlantUML to specify UML diagrams. There is a plugin for VisualCode. I will upload SVG drawings generated by PlantUML. I will try to create some additional documentation in text.

@flashmark
Copy link
Copy Markdown
Contributor Author

Question: how can I show the connected Modbus link on the device overview page? For I2C the port column shows "I2C

"
image

@tonhuisman
Copy link
Copy Markdown
Contributor

how can I show the connected Modbus link on the device overview page?

There is a function PLUGIN_WEBFORM_SHOW_CONFIG that can be handled in your plugin, see P152 for a usable example, just insert <BR/> where you need a newline if you have multiple lines to show. Many other plugins use this function to show the serial configuration.
The I2C and SPI configuration is handled by the DevicesPage code, as they have a matching Device.Type attribute, but the Serial types aren't specific enough to include all info for specific setups, that's why most of the Serial type plugins have a custom implementation (though most are very similar).
For Modbus we don't (yet) have a Device type defined, we can think about introducing that though.

@flashmark
Copy link
Copy Markdown
Contributor Author

I tried to compile for ESP8266 and it failed on existence of ESPEasy_key_value_store. Having the Modbus link configuration stored using KVS and most likely ESP8266 has limited memory I propose to disable the plugin and the Modbus facilities for this CPU.

@TD-er
Copy link
Copy Markdown
Member

TD-er commented May 5, 2026

You deleted close to 2000 lines in docs/source/Plugin/_plugin_sets_overview.repl
I don't think that's intended?

@TD-er
Copy link
Copy Markdown
Member

TD-er commented May 5, 2026

I tried to compile for ESP8266 and it failed on existence of ESPEasy_key_value_store. Having the Modbus link configuration stored using KVS and most likely ESP8266 has limited memory I propose to disable the plugin and the Modbus facilities for this CPU.

Yes please do (for now)
I will think about how to proceed on ESP8266 when we try to use this for existing Modbus plugins.

@tonhuisman
Copy link
Copy Markdown
Contributor

tonhuisman commented May 5, 2026

I tried to compile for ESP8266 and it failed on existence of ESPEasy_key_value_store.

Pulled this local and compiled the current state of affairs (custom_274_ESP8266_4M1M and custom_312_ESP8266_4M1M).
The compiler found a couple of issues:

  • Failing on ESPEasy_key_value_store because in define_plugin_sets.h at line 4519 it needs to have added
    || FEATURE_MODBUS_FAC to get the KVS feature enabled.
  • Then compilation failed because the definition for DAT_MODBUS_INTERFACE_OFFSET is in an #ifdef ESP32 section in StorageLayout.h, so I moved that a bit lower, outside that section, and wrapped it in #if FEATURE_MODBUS_FAC. You'll have to check if it might need a different offset for ESP8266, I'm not sure how you calculated that offset, so it might need a check for ESP8266/ESP32 here. There's an open change-request with the correct definition (here), so it will have to be moved out of the #ifdef ESP32 section anyway.
  • Then next compilation failed because there's a conflicting definition of MODBUS_READ_HOLDING_REGISTERS and friends, in Modbus_RTU.h they are #define MODBUS_READ_HOLDING_REGISTERS 0x03 (etc.) and in Modbus_device.cpp they are defined as global variables: const uint8_t MODBUS_READ_HOLDING_REGISTERS = 0x03; (etc.). Simplest fix is to replace that last set with #define MODBUS_READ_HOLDING_REGISTERS ((uint8_t)0x03) (etc.)
  • The remaining warnings can be ignored for now (a couple are being worked on already)

You'll have to do the functional checks on ESP8266, as I don't have any Modbus hardware available 👼

@tonhuisman
Copy link
Copy Markdown
Contributor

You deleted close to 2000 lines in docs/source/Plugin/_plugin_sets_overview.repl I don't think that's intended?

That file will be re-generated when the PR is merged, and the documentation uploaded to RTD. Git still doesn't understand that we excluded that file via .gitignore, so ppl keep committing that, unfortunately.

@flashmark
Copy link
Copy Markdown
Contributor Author

You deleted close to 2000 lines in docs/source/Plugin/_plugin_sets_overview.repl I don't think that's intended?

That file will be re-generated when the PR is merged, and the documentation uploaded to RTD. Git still doesn't understand that we excluded that file via .gitignore, so ppl keep committing that, unfortunately.

Sorry I am not sure If I have to revert or not. Unfortunately I need some help if reverting is required. I am not a git expert and I don't want to ruin this project...

@tonhuisman
Copy link
Copy Markdown
Contributor

Don't worry about that file, we'll generate & commit a correct one later

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Provide a new transaction structure that can be used to build a Modbus request and queue it at this link
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Modbus_RequestQueueElement * ModbusLINK_struct::newTransaction(struct ModbusDEVICE_struct *device)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really tricky as you easily create a memory leak.
Better let this return an std::unique_ptr<Modbus_RequestQueueElement> and when storing into the queue you need to do a takeover.
This way as soon as the unique pointer gets out of scope, it will be destructed.
Another option is to create some queue or list of the objects and then use a constructor of this class which will for sure set the state to something that will not be processed from the queue.
This way you can add a new entry using emplace_back or something depending on the queue type.
After emplacing a new item, you can use something like auto it = _queue.back(); and when this isn't the same to _queue.end(), you can set the values.

This queue can be of type Modbus_RequestQueueElement or an unique_ptr to it, so you can also deal with allocation failures.
See how controller queue is doing this as this is exactly how it is done there.


case ModbusQueueState::ERROR_OCCURRED:
{
it++;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a potential memory leak.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is to inform the initiator of the transaction (the Modbus_device) uses this or the RESPONSE_RECEIVED state to interact with the result and then set the state to READY_FOR_DESTROY. I am not experienced in queue handling patterns in C++, maybe there is a more robust way to manage ownership and destruction. Coming from high performance embedded C I also don't want to spill too much overhead in copying data between the various objects. I will study the controller queue.

Comment thread src/src/Helpers/Modbus_device.h
Comment thread src/src/Helpers/Modbus_device.h
Comment thread src/src/PluginStructs/P183_data_struct.cpp
Comment thread src/src/PluginStructs/P183_data_struct.cpp
Comment thread src/_P183_modbus.ino
@flashmark
Copy link
Copy Markdown
Contributor Author

I am still in doubt if the setup is right for a simple embedded processor. The main idea is to have a queue owned by the Modbus_link The queue is evaluated on a timer base (10 per second). The Modbus_device knows the protocol, encoding/decoding the messages and queuing the transaction to the Modbus_link. Once a transaction is done a callback to the Modbus_device is done. Note that in theory the device could also poll/check the result when it does not fill in the owning device in the transaction. I don't see a valid reason to do so with the latest design. Also note that, as mentioned by TDer in the review above ownership and lifecycle is a bit tricky. The Modbus_device takes the initiative to create a transaction, then it is on the Modbus_links queue, and finally the Modbus_device is responsible to mark it as READY_FOR_DESTROY and the Modbus_link will destroy it.

In the Modbus_device the write transactions (and any unrecognized transactions) are just destroyed once the response is received (or timeout occurred). For read transactions I now have 3 options (evolved during the development).

  1. use pointers to update the value and state (busy/success/error). This was the original implementation and still used when the caller needs to wait for the result. For the rest I abandoned this option.
    2a) Send a PLUGIN_TASKTIMER_IN event to the plugin with a single value as parameter
    2b) Send a PLUGIN_TASKTIMER_IN event to the plugin with a pointer to a registerSet, an array of values in case the read contains multiple registers.

To keep the administration in the Modbus_device and plugin simple all context is associated with the transaction on the queue. So Modbus_device and plugin need to recognize what they have asked from data returned in the callback or in the PLUGIN_TASKTIMER_IN event. This prevents the need for queues in the Modbus_device and plugin and the overhead of matching pending requests to received answers.

An issue is that this requires the transaction to be kept until it is fully handled by the Modbus_device. As initially I did not have a callback from the Modbus_link processing I decided to wait for destruction until the Modbus_device marks it as such. If we decide to make the callback subscription mandatory the modbus_link can destroy the transaction immediately after the callback. This decision will block asynchronous handling by the Modbus_device in future. But I doubt if that would be useful. device and link are very closely coupled and I don't see a reason to postpone the processing by the Modbus_device.

In the current implementation the Modbus_device owns the transaction for a short while such that it can fill the request. As suggested this could be solved by keeping it in full ownership of the Modbus_link by adding a CREATED state in which the queue will not process it. To be discussed if we want to add logic to assure the sequence is adjusted once it is queued or just keep the sequence according to the creation sequence. Mind that multiple devices might use the same link.

I was not sure if the sendEvent() function was a synchronous call to the plugin or queuing it to an event queue. I learned that it is a synchronous call which makes the event just a callback. This might optimize the current code a bit. It does imply that we must handle a plugin call that requires immediate result with care. Using a PLUGIN_TASKTIMER_IN event will result in a recursive call to the plugin. Most likely this will work, but requires a deep stack. Due to the history of the design currently blocking Modbus reads are handled by a separate function on the Modbus_device and the plugin shall call the process() function to assure the reception of the response is handled. I see 3 options here:

  1. Keep it as is, direct response is deprecated and requires special handling
  2. Don't allow direct response. This requires that all needed registers are cached somehow.
  3. Allow a recursive PLUGIN_TASKTIMER_IN event to handle the response

@TD-er
Copy link
Copy Markdown
Member

TD-er commented May 18, 2026

In my experience, it is always much easier to think of code if things are guaranteed to be done.
That's why I suggested to use some unique pointer type to keep the elements.
This way you don't need to think about cleaning up, as the unique pointer will call the destructor of the object it holds when the pointer gets out of scope.
So a list of unique pointers is quite easy to manage. Whenever you pop the element, it will be destructed properly.

The unique pointer does have its drawback for some specific use cases, as it can only be a single owner of the element it points to and if you would like to hand it over and it needs to remain in existence, you need to do a take-over of the pointer.
There are other smart pointer types, like auto pointer (and others) which essentially keep a counter of the number of links to the object. Whenever this counter reaches 0, the object gets destructed.
And of course there are variations on that concept, where they add strong and weak pointers, etc.

Anyway, the gist of this is, move the responsibility of proper destruction of the object to the destructor of the object and then wrap it in either some kind of unique/smart pointer type. Or just have a list of elements of these objects.

Taking this simplicity a bit further, then we can have a very basic data structure like this:

struct MB_handler {
  // Checking all queues to see what can be done
  bool loop()
  {
    bool res{};
    for (auto it = _queues.begin(); it != _queues.end(); ++it) {
      if (it->second.loop()) res = true;
    }
    return res;
  }

  // Get the right queue given a task index
  MB_Queue* getQueue(TaskIndex taskIndex);

private:
  std::map<TaskIndex, MB_Queue> _queues;
};

struct MB_Queue {
  // acting on the _command_list.front() element
  // When done
  bool loop()
  {
    bool res{};
    for (auto it = _command_list.begin(); it != _command_list.end(); ++it) {
      if (it->loop() == MB_state::Finished) {
        res = true;
        it = _command_list.erase(it);
      } else {
         return res;
      }
    }
    return res;
  }

  void add(MB_command& cmd);

  MB_port_definition _port;
  std::list<MB_command> _command_list;
};

struct MB_command {
  MB_state _state = MB_state::Uninitialized;
  CallBackInfo _cbInfo;
  
  // Do whatever is needed to process the state machine (incl callback stuff) and return current state
  MB_state loop(); 

};

This is just some way too short illustration on how it could be processed.
I don't think this is too much for a microcontroller to handle, as that's exactly how a lot of other code in ESPEasy is done.

@flashmark
Copy link
Copy Markdown
Contributor Author

I think the Modbus_link was close to the example, but I obfuscated it due to legacy in the development. The newTransaction() should be replaced by a proper constructor of the queue element to make clear the transaction is initially owned by the Modbus_device. Once queued it is owned by the Modbus_link (placed on the queue) where it stays until destroyed. In the callback to the device the transaction pointer is only passed to retrieve the result. Then I can destroy the transaction after the callback. I only need the READY_FOR_DESTROY state if the Modbus_device cancels all pending transactions, most likely because it will be destroyed itself. This invalidates the callback references pending on the queue!

I did not destroy the transactions directly in the freeTransactions() because it is unclear for me if the process trigger (currently ten per second timer) is synchronized with the plugin event handling. Marking it for destruction and handling all destruction in the same process handling safer. If ESPEasy architecture assures only cooperative multi threading this can be dropped.

Not sure I have to use the smart pointer here to transfer the transaction once from Modbus_device to Modbus_link when queuing it. I will upload a cleaned version soon.

@TD-er
Copy link
Copy Markdown
Member

TD-er commented May 20, 2026

I did not destroy the transactions directly in the freeTransactions() because it is unclear for me if the process trigger (currently ten per second timer) is synchronized with the plugin event handling. Marking it for destruction and handling all destruction in the same process handling safer. If ESPEasy architecture assures only cooperative multi threading this can be dropped.

Well I guess keeping the entire modbus command object is a bit tricky and probably also larger than what is needed for ESPEasy to process the received reply.
So you can just schedule a PLUGIN_xxx call (one of those which also get an EventStruct along with them) and copy the data into it which is needed by the task/plugin to process the return value.
Then you can safely remove (and destruct) the Modbus command object from the queue.
No need to keep things in sync, no need to wait for other things to complete, etc.
Just append the PLUGIN_xxx callback to the scheduler queue and that will be handled in due time.

Like I said, programming is much simpler if you can safely assume certain things will be dealt with at the appropriate place.

The Modbus handler is just about managing the queues.
The Modbus queue handler is about handling 1 queue.
The Modbus command thingy is about handling 1 command + collecting the reply and handing it over to where it will be processed further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants