Skip to main content

WriteHandler interface

The WriteHandler interface is how the server passes client writes to your code. The server answers reads itself:

RequestHandled byWhat your code does
Read coils, discrete inputs, holding registers or input registersThe server, from the DatabaseKeeps the values current with update_database
Write a single coil or registerwrite_single_coil or write_single_register, with the index and valueChecks them, applies the write, updates the Database it receives, and returns the result
Write multiple coils or registerswrite_multiple_coils or write_multiple_registers, once per request, with the start index and an iterator over the valuesThe same, for every value

Callbacks run on the runtime's threads, so don't block in them. Update the database through the Database the callback receives: the server holds the database's lock during the callback, so calling update_database from one can deadlock.

note

In Rust, there is no WriteHandler or Database: the RequestHandler trait has a method for each read and write. A read calls its method once per address, and a method you don't implement answers with an Illegal Function exception.

Return value​

The return value of each callback determines how the server answers. The most common cases are:

  • The index and value are valid: update the database and return success.
  • The index doesn't exist: return an ILLEGAL_DATA_ADDRESS exception.
  • The value isn't allowed for the point: return an ILLEGAL_DATA_VALUE exception.

The library answers malformed requests itself, such as one with an invalid start and count, without calling your code.

Use the WriteResult value to return exceptions. It has three static methods: success_init creates a success result, exception_init creates a standard Modbus exception, and raw_exception_init creates a non-standard Modbus exception.

Example​

struct SimpleHandler {
coils: Vec<bool>,
discrete_inputs: Vec<bool>,
holding_registers: Vec<u16>,
input_registers: Vec<u16>,
}

impl SimpleHandler {
fn new(count: usize) -> Self {
Self {
coils: vec![false; count],
discrete_inputs: vec![false; count],
holding_registers: vec![0; count],
input_registers: vec![0; count],
}
}
}

impl RequestHandler for SimpleHandler {
fn read_coil(&self, address: u16) -> Result<bool, ExceptionCode> {
self.coils.get(address as usize).to_result()
}

fn read_discrete_input(&self, address: u16) -> Result<bool, ExceptionCode> {
self.discrete_inputs.get(address as usize).to_result()
}

fn read_holding_register(&self, address: u16) -> Result<u16, ExceptionCode> {
self.holding_registers.get(address as usize).to_result()
}

fn read_input_register(&self, address: u16) -> Result<u16, ExceptionCode> {
self.input_registers.get(address as usize).to_result()
}

fn write_single_coil(&mut self, value: Indexed<bool>) -> Result<(), ExceptionCode> {
let coil = self
.coils
.get_mut(value.index as usize)
.ok_or(ExceptionCode::IllegalDataAddress)?;
*coil = value.value;
tracing::info!("write coil {}: {}", value.index, value.value);
Ok(())
}

fn write_single_register(&mut self, value: Indexed<u16>) -> Result<(), ExceptionCode> {
let register = self
.holding_registers
.get_mut(value.index as usize)
.ok_or(ExceptionCode::IllegalDataAddress)?;
*register = value.value;
tracing::info!("write holding register {}: {}", value.index, value.value);
Ok(())
}

fn write_multiple_coils(&mut self, values: WriteCoils) -> Result<(), ExceptionCode> {
// check the whole range exists before changing any coil
let coils = self
.coils
.get_mut(values.range.to_std_range())
.ok_or(ExceptionCode::IllegalDataAddress)?;
for (coil, value) in coils.iter_mut().zip(values.iterator) {
*coil = value.value;
tracing::info!("write coil {}: {}", value.index, value.value);
}
Ok(())
}

fn write_multiple_registers(&mut self, values: WriteRegisters) -> Result<(), ExceptionCode> {
// check the whole range exists before changing any register
let registers = self
.holding_registers
.get_mut(values.range.to_std_range())
.ok_or(ExceptionCode::IllegalDataAddress)?;
for (register, value) in registers.iter_mut().zip(values.iterator) {
*register = value.value;
tracing::info!("write holding register {}: {}", value.index, value.value);
}
Ok(())
}
}