-
Notifications
You must be signed in to change notification settings - Fork 122
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
Support for custom functions in RTU #123
Comments
I would also appreciate this. I have some devices that don't provide register/coil addresses, they only specify a series of bytes to send (according to the Modbus RTU spec). Currently I'm using Here's the spec for a device that doesn't provide registers/coils, as an example. Maybe I'm misunderstanding the issue and this is actually possible with |
Hey just leaving a note that support for custom messages would be awesome. I also need to communicate with a device that just implements custom functions 🙈 |
Okay so I played around with this just to come to the conclusion that some vendors are just breaking the modbus spec shamelessly and are still calling it modbus. Anyway I do not see a way how this can be resolved nicely. So I will just have to stick with a vendor specific fork for my current project. Sucks but what ever 🤷♂️ … |
While I don't need actually custom functions, I wanted to use a function that is in the spec but As already mentioned, the initial problem is that the crate needs to know the length of the message to decode it, but this highly depends on the function being called. So, what if the user could provide us with this information? Enums don't really fit this use-case well, but this can be done with traits. We start by defining a trait for functions: trait Function {
const CODE: u8;
type Request;
type Response;
} This trait itself will be implemented for a dummy struct, but it will tell us what the function looks like. Here's an example: struct WriteSingleRegister;
struct WriteSingleRegisterRequest(Address, Word);
struct WriteSingleRegisterResponse(Address, Word);
impl Function for WriteSingleRegister {
const CODE: u8 = 0x06;
type Request = WriteSingleRegisterRequest;
type Response = WriteSingleRegisterResponse;
} Next, we use this definition in #[async_trait]
trait Client {
async fn call<F>(&mut self, request: F::Request) -> Result<F::Response>
where
F: Function,
F::Request: Encode,
F::Response: Decode;
} Here we additionally require the request type implement Also note that with this definition, we rule out the possibility of calling one function but receiving a response for another one. Although Encoding should be straightforward, but decoding is more interesting: trait Decode {
fn len(buf: Bytes) -> Result<Option<usize>>;
fn decode(buf: Bytes) -> Result<Option<Self>>;
} Some request/response types can return a constant length, but they can inspect the message if a dynamically-sized message is expected. A type need only concert with decoding itself, while the slave ID and the checksum should be possible to handle generically (once the message length is obtained). With such a setup, any function can be implemented outside of Another advantage of this approach is even further type-safety. For example, the function struct ReadHoldingRegisters<const N: usize>;
struct ReadHoldingRegistersRequest<const N: usize>(Address);
struct ReadHoldingRegistersResponse<const N: usize>([Word; N]);
impl<const N: usize> Function for ReadHoldingRegisters<N> {
const CODE: u8 = 0x03;
type Request = ReadHoldingRegistersRequest<N>;
type Response = ReadHoldingRegistersResponse<N>;
} A downside is that implementing this approach would require a massive overhaul of the crate. I might also miss some quirks that will arise during implementation (though I expect that this trait-based approach can be adjusted without losing the general idea). Moreover, since I'm mostly interested in the client-side of this crate, I didn't think about the server-side much, but I'm pretty sure the |
I have notices that custom functions are supported and there is even an example for TCP.
But I was testing in RTU and I found that the custom command is sent, but the response is never processed.
This is in part functions are not supported in:
fn get_response_pdu_len(adu_buf: &BytesMut) -> Result<Option<usize>>
Additionally in this funcion some quick fix can be done for supporting exception code responses for all functions, changing the match arm from
0x81..=0xAB
to0x81..=0xFF
. As all the exceptions share the same format regardless of the function code.I guess the initial problem of not supporting this is how to determine the start and end of a frame.
I have tested some changes in the decode functions and I kind of found a way to deal with it. It's not the best option, but it's something.
Do you think it can be a good solution?
The text was updated successfully, but these errors were encountered: