-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add modbus_reply_callback() and modbus_set_reply_callback() #408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Not sure why travis is failing, it works locally. |
RTU-slave. In its implementation the user has to check whether the slave-id, _slave_, which was | ||
decoded from the request, should be answered to or not and returning TRUE if so, otherwise FALSE. | ||
Returning FALSE will make *modbus_reply_callback()* exit immedialty and return 0. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is accept_rtu_slave only required for RTU? The address field is also available in TCP requests. I don't see why this should be handled differently?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But is it used in TCP? To address different devices via one connection? I don't see it being explained in the spec (while flying over it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remember correctly, it is used e.g. in SMA inverters to have different register mappings available. And think about a TCP to RTU modbus proxy where you want to have one address for the gateway itself and the others mapped to the UART...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, the slave-field in TCP is useful.
The reason for not needing an accept_rtu_slave
in a TCP-connection is, that you probably don't want to ignore a request based on the slave-ID when implementing a TCP-server. A TCP-connection is point-to-point whereas a RTU-bus is broadcast, a slave on a RS482-bus sees all the requests coming by, even the ones it doesn't want to handle, so you need to filter - not answering those you don't care for - someone else will do. In TCP no one else will handle the request.
Is that logic correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely correct. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(but you can still handle it optionally if you want yes? For people making rtu drop in replacements, and that sort of thing...)
MODBUS_WRITE-function-requests are passed to the write-callback. When encountering the | ||
MODBUS_FC_WRITE_AND_READ_REGISTERS-function modbus_reply_callback is calling the write and the | ||
read-callback. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess a dedicated write_read callback is required here... reason below.
MODBUS_FC_WRITE_AND_READ_REGISTERS-function modbus_reply_callback is calling the write and the | ||
read-callback. | ||
|
||
These functions are designed for implementing a Modbus TCP server and RTU slaves. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wording nitpick: better write "...implementing Modbus TCP and RTU slaves." ?
buf[1] = value1 & 0xff; | ||
buf[2] = (value2 >> 8) & 0xff; | ||
buf[3] = value2 & 0xff; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just use the helper macros here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they are part of the API, yes. I personally prefer to tell the dirty truth and leave it to the user.
src/modbus.c
Outdated
} | ||
} | ||
|
||
// TODO broadcast responses should use the slave-id, probably |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broadcasts simply do not have a response by definition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for helping me with this question.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
however, it is a quirk that many devices use, and has been porposed as one of the "quirks_modes" that libmodbus should support using.... (sorry for the necro)
int req_length); | ||
|
||
/** | ||
* UTILS FUNCTIONS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some general notes:
-
this PR is very... big 😄 ... I'm not sure that I already understood all parts
-
When implementing a Modbus server, you usually want to do something with the data, i.e. the Modbus server is only one part of the whole application. The servers which I have written were multi-threaded, so you usually need some locking when accessing shared data. I think this can already prepared by adding lock/unlock callbacks.
-
I'm wondering whether extending the modbus context makes sense. As already stated in Added messagetype 0x14 and 0x15 for file-access. #407, I could image a second "layer" on top of the existing "ground or base layer" functions which cares about such higher-level functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
Yes it's big, I think to make it easier to review I will split out the reply code in a separate file. I think it is not possible to further split the code in several commits. It's complex.
-
I agree with the fact that you need locking around the data, however I disagree that libmodbus has to take care of this within its API. If you need locking, just lock the
modbus_reply_callback()
-call in your code and access to the data is atomic. -
Is the current reply-based-on-mapping-code used in anything else than examples and tests? When thinking about servers and slaves I was very quickly limited by the mapping-implementation, which made me develop this code. I my eyes, the reply-callback-code fits the library's style, no need to create extra layers.
I'm going to write/import examples in the test-dirs which will show how to write a multi-slave-server, that could help to see how to use it.
Thanks a lot for your review! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be master slave opperate in modbus rtu in PIC18fXXX
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If libmodbus compiles and works for this platform, this callback-based server/slave should work as well.
d113379
to
029ddca
Compare
Based on @frodete's proposal (stephane#319) this implementation adds a a callback-based reply-functionality. The old mapping-implementation is now based on this version. As a nice side-effect some DRY code optimizations could have been done. Also adds a MODBUS_SLAVE_ACCEPT_ALL "address", which can be used with modbus_set_slave(). This makes the modbus_receive()-function accept all requests, independently of the slave-address found in the decoded-request. Useful for multi-slave-implementation using libmodbus.
029ddca
to
fdd398e
Compare
Endorsement: using this PR in production code; I have read some of the code, it looks good to me. Maybe the interface is a bit too low-level, e.g. I don't like that I need to handle both WRITE_SINGLE_REGISTER and WRITE_MULTIPLE_REGISTERS when the callback signature actually covers both cases, but maybe this flexibility can be useful for some cases. I like the fact that it comes with docu, which seems complete and accurate, although it could profit from being translated into "simple English". I have started doing that, and will finish it if there is interest. |
Happy to hear that. Same here. Used happily in two different production environments. One of it really uses it all, especially the key-feature: multiple RTU-slaves. Regarding Thanks for your documentation efforts. Describing all functionalities in a man-pages took me an enormous amount of time if you can simplify it, would be highly appreciated. Happy to pull that in. In my fork at least. |
+1 for this PR as well, would be a good point adding support for custom command codes |
Why wasn't the pull request merged? Can i help it somehow? |
Has there been any more progress on this PR? |
Based on @frodete's proposal (#319) this implementation adds a a callback-based reply-functionality.
The old mapping-implementation is now based on this version. As a nice side-effect some DRY code optimizations could have been done.
Unit tests are OK (locally, not on travis though). And my RTU-multi-slave server works.
EDIT:
My motivation for needing a callback-based reply is that I need to implement a modbus-tcp-server and a modbus-rtu-slave. Both will handle the data within a cache. The rtu-slave implementation has to be able to handle multiple slaves. On one RS485-port multiple slave-id will be handled.
More details to what I have done: I took the
modbus_reply
-function and changed its name tomodbus_reply_callback
. Then, each time where something was checked against thestart_bits/start_registers
-adresses andtab_*
-counts I replaced it with a call to the callbackverify
. And each time the payload-part of the request or response-buffer was accesses I replaced it with a call to the callbackread
orwrite
respectively.Then I implemented the previous
modbus_reply
-function in a way that it uses themodbus_reply_callback
function by providing callbacks forverify
,read
andwrite
. In these callback-functions I put the code which previously was in charge to fill in the buffer or to verify the boundaries. By doing so, some code-duplicates have been removed, especially accesses to the buffers in the mapping and its verification.Also adds a MODBUS_SLAVE_ACCEPT_ALL "address", which can be used with modbus_set_slave(). This makes the modbus_receive()-function accept all requests, independently of the slave-address found in the decoded-request. Useful for multi-slave-implementation using libmodbus.
I updated the man-pages and added one for modbus_reply_callback(), please review and comment and merge