WriteHandler interface
The WriteHandler interface is how the server passes client writes to your code. The server answers reads itself:
| Request | Handled by | What your code does |
|---|---|---|
| Read coils, discrete inputs, holding registers or input registers | The server, from the Database | Keeps the values current with update_database |
| Write a single coil or register | write_single_coil or write_single_register, with the index and value | Checks them, applies the write, updates the Database it receives, and returns the result |
| Write multiple coils or registers | write_multiple_coils or write_multiple_registers, once per request, with the start index and an iterator over the values | The 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.
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_ADDRESSexception. - The value isn't allowed for the point: return an
ILLEGAL_DATA_VALUEexception.
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
- Rust
- C
- C++
- Java
- C#
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(())
}
}
// the most values one Modbus request can write: the library rejects longer requests,
// so the multiple write buffers never fill
#define MAX_WRITE_COILS 1968
#define MAX_WRITE_REGISTERS 123
rodbus_write_result_t on_write_single_coil(uint16_t index, bool value, rodbus_database_t *db, void *ctx)
{
if (!rodbus_database_update_coil(db, index, value)) {
return rodbus_write_result_exception_init(RODBUS_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
}
printf("write coil %u: %s\n", index, value ? "true" : "false");
return rodbus_write_result_success_init();
}
rodbus_write_result_t on_write_single_register(uint16_t index, uint16_t value, rodbus_database_t *db, void *ctx)
{
if (!rodbus_database_update_holding_register(db, index, value)) {
return rodbus_write_result_exception_init(RODBUS_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
}
printf("write holding register %u: %u\n", index, value);
return rodbus_write_result_success_init();
}
rodbus_write_result_t on_write_multiple_coils(uint16_t start, rodbus_bit_value_iterator_t *it, rodbus_database_t *db, void *ctx)
{
// buffer the values, checking every coil exists, before changing any
rodbus_bit_value_t values[MAX_WRITE_COILS];
size_t count = 0;
rodbus_bit_value_t *bit = NULL;
while ((bit = rodbus_bit_value_iterator_next(it))) {
bool current = false;
if (count == MAX_WRITE_COILS || rodbus_database_get_coil(db, bit->index, ¤t) != RODBUS_PARAM_ERROR_OK) {
return rodbus_write_result_exception_init(RODBUS_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
}
values[count++] = *bit;
}
for (size_t i = 0; i < count; ++i) {
rodbus_database_update_coil(db, values[i].index, values[i].value);
printf("write coil %u: %s\n", values[i].index, values[i].value ? "true" : "false");
}
return rodbus_write_result_success_init();
}
rodbus_write_result_t on_write_multiple_registers(uint16_t start, rodbus_register_value_iterator_t *it, rodbus_database_t *db, void *ctx)
{
// buffer the values, checking every register exists, before changing any
rodbus_register_value_t values[MAX_WRITE_REGISTERS];
size_t count = 0;
rodbus_register_value_t *reg = NULL;
while ((reg = rodbus_register_value_iterator_next(it))) {
uint16_t current = 0;
if (count == MAX_WRITE_REGISTERS || rodbus_database_get_holding_register(db, reg->index, ¤t) != RODBUS_PARAM_ERROR_OK) {
return rodbus_write_result_exception_init(RODBUS_MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
}
values[count++] = *reg;
}
for (size_t i = 0; i < count; ++i) {
rodbus_database_update_holding_register(db, values[i].index, values[i].value);
printf("write holding register %u: %u\n", values[i].index, values[i].value);
}
return rodbus_write_result_success_init();
}
rodbus_write_handler_t get_write_handler(void)
{
return (rodbus_write_handler_t){
.write_single_coil = &on_write_single_coil,
.write_single_register = &on_write_single_register,
.write_multiple_coils = &on_write_multiple_coils,
.write_multiple_registers = &on_write_multiple_registers,
.on_destroy = NULL,
.ctx = NULL,
};
}
class WriteHandler : public rodbus::WriteHandler {
rodbus::WriteResult write_single_coil(uint16_t index, bool value, rodbus::Database &db) override
{
if (!db.update_coil(index, value)) {
return rodbus::WriteResult::exception_init(rodbus::ModbusException::illegal_data_address);
}
std::cout << "write coil " + std::to_string(index) + ": " + (value ? "true" : "false") + "\n";
return rodbus::WriteResult::success_init();
}
rodbus::WriteResult write_single_register(uint16_t index, uint16_t value, rodbus::Database &db) override
{
if (!db.update_holding_register(index, value)) {
return rodbus::WriteResult::exception_init(rodbus::ModbusException::illegal_data_address);
}
std::cout << "write holding register " + std::to_string(index) + ": " + std::to_string(value) + "\n";
return rodbus::WriteResult::success_init();
}
rodbus::WriteResult write_multiple_coils(uint16_t start, rodbus::BitValueIterator &it, rodbus::Database &db) override
{
// buffer the values, checking every coil exists, before changing any
std::vector<rodbus::BitValue> values;
try {
while (it.next()) {
const auto value = it.get();
db.get_coil(value.index); // throws if the coil doesn't exist
values.push_back(value);
}
}
catch (const std::exception &) { // a missing point, or no memory for the buffer
return rodbus::WriteResult::exception_init(rodbus::ModbusException::illegal_data_address);
}
for (const auto &value : values) {
db.update_coil(value.index, value.value);
std::cout << "write coil " + std::to_string(value.index) + ": " + (value.value ? "true" : "false") + "\n";
}
return rodbus::WriteResult::success_init();
}
rodbus::WriteResult write_multiple_registers(uint16_t start, rodbus::RegisterValueIterator &it, rodbus::Database &db) override
{
// buffer the values, checking every register exists, before changing any
std::vector<rodbus::RegisterValue> values;
try {
while (it.next()) {
const auto value = it.get();
db.get_holding_register(value.index); // throws if the register doesn't exist
values.push_back(value);
}
}
catch (const std::exception &) { // a missing point, or no memory for the buffer
return rodbus::WriteResult::exception_init(rodbus::ModbusException::illegal_data_address);
}
for (const auto &value : values) {
db.update_holding_register(value.index, value.value);
std::cout << "write holding register " + std::to_string(value.index) + ": " + std::to_string(value.value) + "\n";
}
return rodbus::WriteResult::success_init();
}
};
std::unique_ptr<rodbus::WriteHandler> create_write_handler()
{
return std::make_unique<WriteHandler>();
}
private static class ExampleWriteHandler implements WriteHandler {
@Override
public WriteResult writeSingleCoil(UShort index, boolean value, Database database) {
if (!database.updateCoil(index, value)) {
return WriteResult.exceptionInit(ModbusException.ILLEGAL_DATA_ADDRESS);
}
System.out.println("write coil " + index.intValue() + ": " + value);
return WriteResult.successInit();
}
@Override
public WriteResult writeSingleRegister(UShort index, UShort value, Database database) {
if (!database.updateHoldingRegister(index, value)) {
return WriteResult.exceptionInit(ModbusException.ILLEGAL_DATA_ADDRESS);
}
System.out.println("write holding register " + index.intValue() + ": " + value.intValue());
return WriteResult.successInit();
}
@Override
public WriteResult writeMultipleCoils(UShort start, List<BitValue> values, Database database) {
// check every coil exists before changing any
for (BitValue value : values) {
if (!hasCoil(database, value.index)) {
return WriteResult.exceptionInit(ModbusException.ILLEGAL_DATA_ADDRESS);
}
}
for (BitValue value : values) {
database.updateCoil(value.index, value.value);
System.out.println("write coil " + value.index.intValue() + ": " + value.value);
}
return WriteResult.successInit();
}
@Override
public WriteResult writeMultipleRegisters(UShort start, List<RegisterValue> values, Database database) {
// check every register exists before changing any
for (RegisterValue value : values) {
if (!hasHoldingRegister(database, value.index)) {
return WriteResult.exceptionInit(ModbusException.ILLEGAL_DATA_ADDRESS);
}
}
for (RegisterValue value : values) {
database.updateHoldingRegister(value.index, value.value);
System.out.println("write holding register " + value.index.intValue() + ": " + value.value.intValue());
}
return WriteResult.successInit();
}
private static boolean hasCoil(Database database, UShort index) {
try {
database.getCoil(index);
return true;
} catch (ParamException ex) {
return false;
}
}
private static boolean hasHoldingRegister(Database database, UShort index) {
try {
database.getHoldingRegister(index);
return true;
} catch (ParamException ex) {
return false;
}
}
}
class WriteHandler : IWriteHandler
{
public WriteResult WriteSingleCoil(ushort index, bool value, Database database)
{
if (!database.UpdateCoil(index, value))
{
return WriteResult.ExceptionInit(ModbusException.IllegalDataAddress);
}
Console.WriteLine($"write coil {index}: {(value ? "true" : "false")}");
return WriteResult.SuccessInit();
}
public WriteResult WriteSingleRegister(ushort index, ushort value, Database database)
{
if (!database.UpdateHoldingRegister(index, value))
{
return WriteResult.ExceptionInit(ModbusException.IllegalDataAddress);
}
Console.WriteLine($"write holding register {index}: {value}");
return WriteResult.SuccessInit();
}
public WriteResult WriteMultipleCoils(ushort start, ICollection<BitValue> values, Database database)
{
// check every coil exists before changing any
foreach (var value in values)
{
if (!HasCoil(database, value.Index))
{
return WriteResult.ExceptionInit(ModbusException.IllegalDataAddress);
}
}
foreach (var value in values)
{
database.UpdateCoil(value.Index, value.Value);
Console.WriteLine($"write coil {value.Index}: {(value.Value ? "true" : "false")}");
}
return WriteResult.SuccessInit();
}
public WriteResult WriteMultipleRegisters(ushort start, ICollection<RegisterValue> values, Database database)
{
// check every register exists before changing any
foreach (var value in values)
{
if (!HasHoldingRegister(database, value.Index))
{
return WriteResult.ExceptionInit(ModbusException.IllegalDataAddress);
}
}
foreach (var value in values)
{
database.UpdateHoldingRegister(value.Index, value.Value);
Console.WriteLine($"write holding register {value.Index}: {value.Value}");
}
return WriteResult.SuccessInit();
}
private static bool HasCoil(Database database, ushort index)
{
try
{
database.GetCoil(index);
return true;
}
catch (ParamException)
{
return false;
}
}
private static bool HasHoldingRegister(Database database, ushort index)
{
try
{
database.GetHoldingRegister(index);
return true;
}
catch (ParamException)
{
return false;
}
}
}