Description
Problem Description
When generating custom Modbus RTU requests using tokio-modbus, the CRC calculation for the request frame appears to be incorrect. Specifically, when creating a custom request with function code 0x07 and data [0x00, 0x10, 0x37], the generated frame has an invalid CRC.
Generated frame (incorrect):
02 07 00 10 37 10 a2
Expected frame (correct):
02 07 00 10 37 ac 03 // Using standard Modbus CRC16 algorithm
Code Snippet
let request = Request::Custom(0x07, Cow::Borrowed(&binding));
ctx.call(request).await.map(|result| {
result
.map_err(|e| ModbusError::Task(e.to_string()))
.map(|response| match response {
Response::Custom(addr, words) => {
info!("addr: {:?}", addr);
words
}
_ => unreachable!("call() should reject mismatching responses"),
})
})
Expected Behavior
For custom requests (Request::Custom), the library should:
Properly format the RTU frame: [address][function_code][data][crc_low][crc_high]
Calculate CRC using standard Modbus CRC16 algorithm (polynomial 0x8005)
Place CRC in little-endian order (low byte first)
For the specific input:
Address: 0x02
Function code: 0x07
Data: [0x00, 0x10, 0x37]
Expected CRC: 0x03AC → bytes [0xAC, 0x03]
Actual Behavior
The generated frame ends with 10 a2 instead of the correct ac 03. This causes communication failures with devices expecting standard Modbus RTU frames.
Steps to Reproduce
Create a custom request with function code 0x07 and data [0x00, 0x10, 0x37]
Send through RTU client
Capture output frame
Observe incorrect CRC 10 a2 instead of correct ac 03
Environment
tokio-modbus version: [e.g., 0.7.0]
OS: [e.g., Linux x86_64]
Hardware: [e.g., Serial port, USB-RS485 adapter]
Suggested Fix
The CRC calculation should follow the standard Modbus RTU specification:
# Python reference implementation
def modbus_crc(data: bytes) -> int:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
return crc
# For [0x02, 0x07, 0x00, 0x10, 0x37]:
# crc = modbus_crc(b'\x02\x07\x00\x10\x37') = 0x03AC
# bytes should be [0xAC, 0x03]
Additional Context
This issue specifically affects custom requests (Request::Custom). Standard function codes (like Read Holding Registers) appear to calculate CRC correctly. The problem likely resides in the frame construction logic for custom requests in the RTU client implementation.