Skip to content

Incorrect CRC Calculation for Custom Requests in RTU Mode #323

Open
@andrewliu1345

Description

@andrewliu1345

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions