bertmelis / esp32ModbusRTU

modbus RTU client for ESP32
MIT License
70 stars 44 forks source link

Multiple consumers in one sketch #21

Closed Miq1 closed 3 years ago

Miq1 commented 4 years ago

Hi Bert,

thanks again for your help with my initial struggles with my RS485 wiring ;). Now I am facing another challenge and would like to get your thoughts on it.

My sketch is running on a device that can be used locally with buttons and display, but on the other hand will serve up to 8 TCP clients doing MODBUS requests. So in theory I will have up to 9 consumers for the esp32modbusRTU object. It will be a TCP-RTU bridge sitting in my fuse box to connect power meters etc. into the TCP network.

The problem is to keep track of the respective requests and resulting responses. One could compare the function codes, slave IDs etc., but that would only give some probability to match the right request with its response.

I am considering different solutions, all of them having some issues.

1) add two more calling arguments to the request functions, that give addresses of separate onData and onError handlers for the very request. Internally, the esp32modbusRTU instance can check if these handler addresses are different from NULL and use these to return the data and errors to the caller. The function signature will be like

bool readHoldingRegisters(uint8_t slaveAddress, uint16_t address, uint16_t numberRegisters, esp32Modbus::MBRTUOnData handler=NULL, esp32Modbus::MBRTUOnError handler=NULL);

This way the interface will not be broken for existing users of the library.

But this is not sufficient for my application - I would be able to separate local from TCP requests, but the TCP tasks are all sharing the same code, so they would remain undistinguishable.

2) add a new calling argument uint32_t requesterToken to the request functions that the requesting consumer can use to put in a unique identifier. Having a signature as above, defaulting the token to zero, the interface again would not be broken for the request functions, but the onData and onError handlers would have to be modified to bring back the token - breaking the interface.

3) derive a new class from esp32modbusRTU, that has both the new features described above. This is not comfortably done, unfortunately, as your original class has a couple of data and functions declared private, making it impossible to envelope those I would need to. If only these were declared protected...

So what would you suggest for me to do?

bertmelis commented 4 years ago

The option to add the callback to individual requests are a valid options, so is making private functions protected.

Now I don't see how you can't separate the local from the TCP requests.

Another points: TCP requests can come in faster then the RTU is capable of handling. And there wil lbe concurrent requests. You'll need some sort of queue to "funnel" them into RTU.

Miq1 commented 4 years ago

Yes, I will have TCP request managing on the ESP32. I even thought about caching read requests (0x03 etc.) done most frequently and inquire the RTU slaves only periodically. The more pressing is the need to quickly recognize the request/response pairs.

Another thing I am currently poking at is to handle the concurrent TCP connections. FreeRTOS is in principle capable of maintaining a separate task for each, but the ESP32's processing power is rather limiting. I may be forced to reduce the number of concurrent TCP connections to keep it bearable. Different topic, though.

I of course can fork your code and modify it, but I wanted to add something maybe valuable for others rather than just ripping it :D

bertmelis commented 4 years ago

Just to let you know, I'm creating a modbus TCP slave, based on asynctcp. It's in another repo. I'm on phone right now so a bit hard to link.

Miq1 commented 4 years ago

Sounds like something I could need! Take your time - whenever you are ready.

bertmelis commented 4 years ago

Thinking about it, it will not help you like it's designed now. You'd have to respond in the callback but in your case you have to push to RTU first and wait for an answer. By that time, the watchdog of the async task will start barking.

Miq1 commented 4 years ago

My idea was to keep the sockets open until I got the response or some seconds-long timeout strikes. But if I may I would like to learn more about your async slave - perhaps I can pick some better ideas from it.

bertmelis commented 4 years ago

https://github.com/bertmelis/espModbus

In the callback, a "Connection" is given that you can use to get the request's properties. This object also has a respond method. In the current implementation, the object destroyed after the callback. I'd let it live until the response is sent. But then I leave the user the responsibility not to lose the pointer to the object (and leaking memory).

But I also have to merge the different repo's. It's a mess with namespaces and class names now. I didn't think this through... (Just a free-time, self-taught programmer)

Miq1 commented 4 years ago

He he heh, sounds familiar... :D Half my life I have spent refactoring my code again and again. Not for the worst, but it wastes the time imagined for new ideas.

But seriously: if you think you could share your code, please do so.

bertmelis commented 4 years ago

Modbus slave using AsyncTCP is in the link: https://github.com/bertmelis/espModbus

Miq1 commented 4 years ago

Yeah, saw that, but Github is giving me 500s errors currently. Will try tomorrow.

Miq1 commented 4 years ago

Howdy! I had a look at the code of espModbus now. I definitely now understand what you meant regarding namespace etc. :-))

My application is a bit different from a real MODBUS slave, so part of the code providing slave functionality is currently not necessary for me. I will simply take the TCP input, reformat it slightly to suit the RTU requirements and forward it. If the address, function code or values are malformed, I will return the error response to the TCP requester without interfering. I have plans, though, to provide a slave functionalitry later to read out parameters of the bridge device itself, but in the first row I will have to solve the bridging task. Long sermon, short conclusion: I will try with the vanilla EthernetServer class from the Ethernet library first. It looks like the lib is able to cope with the 8 concurrent connections the W5500 chip can provide out of the box, so I may be lucky to have a solution already. I of course will have to track the client communications in the bridge and maintain message numbers etc., but that may be it already. I will take your esp32modbusRTU as a base and modify it to support the token and adhoc handlers as described above.

Thank you for your support - I will definitely reconsider your async code if the simple approach does not work!

Miq1 commented 4 years ago

Hi Bert,

me again. I made some progress in the meantime. I modified your code to

I am managing the clients' requests and responses via the token and that is working quite well.

The problem I am facing now: the requests coming in via TCP will be well-formed in most of the cases, but may use other function codes different from those (01, 02, 03, 04, 16) that are explicitly coded in your library. The slaves on the RS485 bus will understand those function codes, though. So for the sake of speed and lazyness ;) I would like to add some

bool rawRequest(uint8_t slaveAddress, uint8_t function, uint8_t *data, uint8_t dataLength, uint32_t token=0); 

The problem is the size the internal ModbusMessage constructor of the ModbusResponse will need - I do not know it in advance for these raw requests. Several functions (like isComplete()) are expecting a size being known from the request already.

How would you solve this?

Miq1 commented 4 years ago

Self comment...

I will have the rawRequest() set a flag and a pro forma size of the response and catch the length field in the response proper to extend it (if necessary) if the flag signals it results from a rawRequest().

bertmelis commented 4 years ago

Nice hack. Sorry for the lack of support. Am a bit busy here...

Miq1 commented 4 years ago

No worries. I will get along :) Once I will have the device finished I am working on: would you be interested in the modified code?

Miq1 commented 4 years ago

I got the raw request stuff running today. Now I will connect it to the TCP MODBUS world - will be interesting to learn what the requests are. BTW: I fixed the rtsPin=-1 thing - it did not work before without a valid GPIO in the constructor.

Miq1 commented 3 years ago

Discussion got lengthy and trailed off ;)