nanoMODBUS/nanomodbus.c
Valerio De Benedetto 1d0321b285 Project rename
2022-01-25 00:37:54 +01:00

1245 lines
34 KiB
C

#include "nanomodbus.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#ifdef NMBS_DEBUG
#include <stdio.h>
#define DEBUG(...) printf(__VA_ARGS__)
#else
#define DEBUG(...) (void) (0)
#endif
#if !defined(NMBS_BIG_ENDIAN) && !defined(NMBS_LITTLE_ENDIAN)
#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || \
defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__)
#define NMBS_BIG_ENDIAN
#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \
defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__)
#define NMBS_LITTLE_ENDIAN
#else
#error "Failed to automatically detect platform endianness. Please define either NMBS_BIG_ENDIAN or NMBS_LITTLE_ENDIAN."
#endif
#endif
#define get_1(m) \
(m)->msg.buf[(m)->msg.buf_idx]; \
(m)->msg.buf_idx++
#define put_1(m, b) \
(m)->msg.buf[(m)->msg.buf_idx] = (b); \
(m)->msg.buf_idx++
#define discard_1(m) (m)->msg.buf_idx++
#ifdef NMBS_BIG_ENDIAN
#define get_2(m) \
(*(uint16_t*) ((m)->msg.buf + (m)->msg.buf_idx)); \
(m)->msg.buf_idx += 2
#define put_2(m, w) \
(*(uint16_t*) ((m)->msg.buf + (m)->msg.buf_idx)) = (w); \
(m)->msg.buf_idx += 2
#else
#define get_2(m) \
((uint16_t) ((m)->msg.buf[(m)->msg.buf_idx + 1])) | (((uint16_t) (m)->msg.buf[(m)->msg.buf_idx] << 8)); \
(m)->msg.buf_idx += 2
#define put_2(m, w) \
(m)->msg.buf[(m)->msg.buf_idx] = ((uint8_t) ((((uint16_t) (w)) & 0xFF00) >> 8)); \
(m)->msg.buf[(m)->msg.buf_idx + 1] = ((uint8_t) (((uint16_t) (w)) & 0x00FF)); \
(m)->msg.buf_idx += 2
#endif
static void msg_buf_reset(nmbs_t* nmbs) {
nmbs->msg.buf_idx = 0;
}
static void msg_state_reset(nmbs_t* nmbs) {
msg_buf_reset(nmbs);
nmbs->msg.unit_id = 0;
nmbs->msg.fc = 0;
nmbs->msg.transaction_id = 0;
nmbs->msg.broadcast = false;
nmbs->msg.ignored = 0;
}
static void msg_state_req(nmbs_t* nmbs, uint8_t fc) {
if (nmbs->current_tid == UINT16_MAX)
nmbs->current_tid = 1;
else
nmbs->current_tid++;
msg_state_reset(nmbs);
nmbs->msg.unit_id = nmbs->dest_address_rtu;
nmbs->msg.fc = fc;
nmbs->msg.transaction_id = nmbs->current_tid;
if (nmbs->msg.unit_id == 0 && nmbs->platform.transport == NMBS_TRANSPORT_RTU)
nmbs->msg.broadcast = true;
}
int nmbs_create(nmbs_t* nmbs, const nmbs_platform_conf* platform_conf) {
if (!nmbs)
return NMBS_ERROR_INVALID_ARGUMENT;
memset(nmbs, 0, sizeof(nmbs_t));
nmbs->byte_timeout_ms = -1;
nmbs->read_timeout_ms = -1;
nmbs->byte_spacing_ms = 0;
if (!platform_conf)
return NMBS_ERROR_INVALID_ARGUMENT;
if (platform_conf->transport != NMBS_TRANSPORT_RTU && platform_conf->transport != NMBS_TRANSPORT_TCP)
return NMBS_ERROR_INVALID_ARGUMENT;
if (!platform_conf->read_byte || !platform_conf->write_byte || !platform_conf->sleep)
return NMBS_ERROR_INVALID_ARGUMENT;
nmbs->platform = *platform_conf;
return NMBS_ERROR_NONE;
}
nmbs_error nmbs_client_create(nmbs_t* nmbs, const nmbs_platform_conf* platform_conf) {
return nmbs_create(nmbs, platform_conf);
}
nmbs_error nmbs_server_create(nmbs_t* nmbs, uint8_t address_rtu, const nmbs_platform_conf* platform_conf,
const nmbs_callbacks* callbacks) {
if (platform_conf->transport == NMBS_TRANSPORT_RTU && address_rtu == 0)
return NMBS_ERROR_INVALID_ARGUMENT;
nmbs_error ret = nmbs_create(nmbs, platform_conf);
if (ret != NMBS_ERROR_NONE)
return ret;
nmbs->address_rtu = address_rtu;
nmbs->callbacks = *callbacks;
return NMBS_ERROR_NONE;
}
void nmbs_set_read_timeout(nmbs_t* nmbs, int32_t timeout_ms) {
nmbs->read_timeout_ms = timeout_ms;
}
void nmbs_set_byte_timeout(nmbs_t* nmbs, int32_t timeout_ms) {
nmbs->byte_timeout_ms = timeout_ms;
}
void nmbs_set_byte_spacing(nmbs_t* nmbs, uint32_t spacing_ms) {
nmbs->byte_spacing_ms = spacing_ms;
}
void nmbs_set_destination_rtu_address(nmbs_t* nmbs, uint8_t address) {
nmbs->dest_address_rtu = address;
}
void nmbs_set_platform_arg(nmbs_t* nmbs, void* arg) {
nmbs->platform.arg = arg;
}
static uint16_t crc_calc(const uint8_t* data, uint32_t length) {
uint16_t crc = 0xFFFF;
for (uint32_t i = 0; i < length; i++) {
crc ^= (uint16_t) data[i];
for (int j = 8; j != 0; j--) {
if ((crc & 0x0001) != 0) {
crc >>= 1;
crc ^= 0xA001;
}
else
crc >>= 1;
}
}
return crc;
}
static nmbs_error recv(nmbs_t* nmbs, uint32_t count) {
uint32_t r = 0;
while (r != count) {
int ret = nmbs->platform.read_byte(nmbs->msg.buf + nmbs->msg.buf_idx + r, nmbs->byte_timeout_ms,
nmbs->platform.arg);
if (ret == 0) {
return NMBS_ERROR_TIMEOUT;
}
else if (ret != 1) {
return NMBS_ERROR_TRANSPORT;
}
r++;
}
return NMBS_ERROR_NONE;
}
static nmbs_error send(nmbs_t* nmbs) {
uint32_t spacing_ms = 0;
if (nmbs->platform.transport == NMBS_TRANSPORT_RTU)
spacing_ms = nmbs->byte_spacing_ms;
for (int i = 0; i < nmbs->msg.buf_idx; i++) {
if (spacing_ms != 0)
nmbs->platform.sleep(spacing_ms, nmbs->platform.arg);
int ret = nmbs->platform.write_byte(nmbs->msg.buf[i], nmbs->read_timeout_ms, nmbs->platform.arg);
if (ret == 0) {
return NMBS_ERROR_TIMEOUT;
}
else if (ret != 1) {
return NMBS_ERROR_TRANSPORT;
}
}
return NMBS_ERROR_NONE;
}
static nmbs_error recv_msg_footer(nmbs_t* nmbs) {
if (nmbs->platform.transport == NMBS_TRANSPORT_RTU) {
uint16_t crc = crc_calc(nmbs->msg.buf, nmbs->msg.buf_idx);
nmbs_error err = recv(nmbs, 2);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t recv_crc = get_2(nmbs);
if (recv_crc != crc)
return NMBS_ERROR_TRANSPORT;
}
DEBUG("\n");
return NMBS_ERROR_NONE;
}
static nmbs_error recv_msg_header(nmbs_t* nmbs, bool* first_byte_received) {
// We wait for the read timeout here, just for the first message byte
int32_t old_byte_timeout = nmbs->byte_timeout_ms;
nmbs->byte_timeout_ms = nmbs->read_timeout_ms;
msg_state_reset(nmbs);
if (first_byte_received)
*first_byte_received = false;
if (nmbs->platform.transport == NMBS_TRANSPORT_RTU) {
nmbs_error err = recv(nmbs, 1);
nmbs->byte_timeout_ms = old_byte_timeout;
if (err != NMBS_ERROR_NONE)
return err;
if (first_byte_received)
*first_byte_received = true;
nmbs->msg.unit_id = get_1(nmbs);
err = recv(nmbs, 1);
if (err != NMBS_ERROR_NONE)
return err;
nmbs->msg.fc = get_1(nmbs);
}
else if (nmbs->platform.transport == NMBS_TRANSPORT_TCP) {
nmbs_error err = recv(nmbs, 1);
nmbs->byte_timeout_ms = old_byte_timeout;
if (err != NMBS_ERROR_NONE)
return err;
if (first_byte_received)
*first_byte_received = true;
// Advance buf_idx
discard_1(nmbs);
err = recv(nmbs, 7);
if (err != NMBS_ERROR_NONE)
return err;
// Starting over
msg_buf_reset(nmbs);
nmbs->msg.transaction_id = get_2(nmbs);
uint16_t protocol_id = get_2(nmbs);
uint16_t length = get_2(nmbs); // We should actually check the length of the request against this value
nmbs->msg.unit_id = get_1(nmbs);
nmbs->msg.fc = get_1(nmbs);
if (protocol_id != 0)
return NMBS_ERROR_TRANSPORT;
if (length > 255)
return NMBS_ERROR_TRANSPORT;
}
return NMBS_ERROR_NONE;
}
static nmbs_error recv_req_header(nmbs_t* nmbs, bool* first_byte_received) {
nmbs_error err = recv_msg_header(nmbs, first_byte_received);
if (err != NMBS_ERROR_NONE)
return err;
if (nmbs->platform.transport == NMBS_TRANSPORT_RTU) {
// Check if request is for us
if (nmbs->msg.unit_id == NMBS_BROADCAST_ADDRESS)
nmbs->msg.broadcast = true;
else if (nmbs->msg.unit_id != nmbs->address_rtu)
nmbs->msg.ignored = true;
else
nmbs->msg.ignored = false;
}
return NMBS_ERROR_NONE;
}
static nmbs_error recv_res_header(nmbs_t* nmbs) {
uint16_t req_transaction_id = nmbs->msg.transaction_id;
uint8_t req_fc = nmbs->msg.fc;
nmbs_error err = recv_msg_header(nmbs, NULL);
if (err != NMBS_ERROR_NONE)
return err;
if (nmbs->platform.transport == NMBS_TRANSPORT_TCP) {
if (nmbs->msg.transaction_id != req_transaction_id)
return NMBS_ERROR_INVALID_RESPONSE;
}
if (nmbs->msg.ignored)
return NMBS_ERROR_INVALID_RESPONSE;
if (nmbs->msg.fc != req_fc) {
if (nmbs->msg.fc - 0x80 == req_fc) {
err = recv(nmbs, 1);
if (err != NMBS_ERROR_NONE)
return err;
uint8_t exception = get_1(nmbs);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (exception < 1 || exception > 4)
return NMBS_ERROR_INVALID_RESPONSE;
else {
DEBUG("exception %d\n", exception);
return exception;
}
}
else {
return NMBS_ERROR_INVALID_RESPONSE;
}
}
DEBUG("NMBS res <- fc %d\t", nmbs->msg.fc);
return NMBS_ERROR_NONE;
}
static void send_msg_header(nmbs_t* nmbs, uint16_t data_length) {
msg_buf_reset(nmbs);
if (nmbs->platform.transport == NMBS_TRANSPORT_RTU) {
put_1(nmbs, nmbs->msg.unit_id);
}
else if (nmbs->platform.transport == NMBS_TRANSPORT_TCP) {
put_2(nmbs, nmbs->msg.transaction_id);
put_2(nmbs, 0);
put_2(nmbs, (uint16_t) (1 + 1 + data_length));
put_1(nmbs, nmbs->msg.unit_id);
}
put_1(nmbs, nmbs->msg.fc);
}
static nmbs_error send_msg_footer(nmbs_t* nmbs) {
if (nmbs->platform.transport == NMBS_TRANSPORT_RTU) {
uint16_t crc = crc_calc(nmbs->msg.buf, nmbs->msg.buf_idx);
put_2(nmbs, crc);
}
nmbs_error err = send(nmbs);
DEBUG("\n");
return err;
}
static void send_req_header(nmbs_t* nmbs, uint16_t data_length) {
send_msg_header(nmbs, data_length);
DEBUG("NMBS req -> fc %d\t", nmbs->msg.fc);
}
static void send_res_header(nmbs_t* nmbs, uint16_t data_length) {
send_msg_header(nmbs, data_length);
DEBUG("NMBS res -> fc %d\t", nmbs->msg.fc);
}
static nmbs_error handle_exception(nmbs_t* nmbs, uint8_t exception) {
nmbs->msg.fc += 0x80;
send_msg_header(nmbs, 1);
put_1(nmbs, exception);
DEBUG("NMBS res -> exception %d\n", exception);
return send_msg_footer(nmbs);
}
static nmbs_error handle_read_discrete(nmbs_t* nmbs, nmbs_error (*callback)(uint16_t, uint16_t, nmbs_bitfield)) {
nmbs_error err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address = get_2(nmbs);
uint16_t quantity = get_2(nmbs);
DEBUG("a %d\tq %d", address, quantity);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.ignored) {
if (quantity < 1 || quantity > 2000)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
if (callback) {
nmbs_bitfield bf = {0};
err = callback(address, quantity, bf);
if (err != NMBS_ERROR_NONE) {
if (nmbs_error_is_exception(err))
return handle_exception(nmbs, err);
else
return handle_exception(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
}
if (!nmbs->msg.broadcast) {
uint8_t discrete_bytes = (quantity / 8) + 1;
send_res_header(nmbs, discrete_bytes);
put_1(nmbs, discrete_bytes);
DEBUG("b %d\t", discrete_bytes);
DEBUG("coils ");
for (int i = 0; i < discrete_bytes; i++) {
put_1(nmbs, bf[i]);
DEBUG("%d", bf[i]);
}
err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
}
}
else {
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
}
}
return NMBS_ERROR_NONE;
}
static nmbs_error handle_read_registers(nmbs_t* nmbs, nmbs_error (*callback)(uint16_t, uint16_t, uint16_t*)) {
nmbs_error err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address = get_2(nmbs);
uint16_t quantity = get_2(nmbs);
DEBUG("a %d\tq %d", address, quantity);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.ignored) {
if (quantity < 1 || quantity > 125)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
if (callback) {
uint16_t regs[125] = {0};
err = callback(address, quantity, regs);
if (err != NMBS_ERROR_NONE) {
if (nmbs_error_is_exception(err))
return handle_exception(nmbs, err);
else
return handle_exception(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
}
if (!nmbs->msg.broadcast) {
uint8_t regs_bytes = quantity * 2;
send_res_header(nmbs, regs_bytes);
put_1(nmbs, regs_bytes);
DEBUG("b %d\t", regs_bytes);
DEBUG("regs ");
for (int i = 0; i < quantity; i++) {
put_2(nmbs, regs[i]);
DEBUG("%d", regs[i]);
}
err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
}
}
else {
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
}
}
return NMBS_ERROR_NONE;
}
static nmbs_error handle_read_coils(nmbs_t* nmbs) {
return handle_read_discrete(nmbs, nmbs->callbacks.read_coils);
}
static nmbs_error handle_read_discrete_inputs(nmbs_t* nmbs) {
return handle_read_discrete(nmbs, nmbs->callbacks.read_discrete_inputs);
}
static nmbs_error handle_read_holding_registers(nmbs_t* nmbs) {
return handle_read_registers(nmbs, nmbs->callbacks.read_holding_registers);
}
static nmbs_error handle_read_input_registers(nmbs_t* nmbs) {
return handle_read_registers(nmbs, nmbs->callbacks.read_input_registers);
}
static nmbs_error handle_write_single_coil(nmbs_t* nmbs) {
nmbs_error err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address = get_2(nmbs);
uint16_t value = get_2(nmbs);
DEBUG("a %d\tvalue %d", address, value);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.ignored) {
if (nmbs->callbacks.write_single_coil) {
if (value != 0 && value != 0xFF00)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
err = nmbs->callbacks.write_single_coil(address, value == 0 ? false : true);
if (err != NMBS_ERROR_NONE) {
if (nmbs_error_is_exception(err))
return handle_exception(nmbs, err);
else
return handle_exception(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
}
if (!nmbs->msg.broadcast) {
send_res_header(nmbs, 4);
put_2(nmbs, address);
put_2(nmbs, value);
DEBUG("a %d\tvalue %d", address, value);
err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
}
}
else {
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
}
}
return NMBS_ERROR_NONE;
}
static nmbs_error handle_write_single_register(nmbs_t* nmbs) {
nmbs_error err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address = get_2(nmbs);
uint16_t value = get_2(nmbs);
DEBUG("a %d\tvalue %d", address, value);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.ignored) {
if (nmbs->callbacks.write_single_register) {
err = nmbs->callbacks.write_single_register(address, value);
if (err != NMBS_ERROR_NONE) {
if (nmbs_error_is_exception(err))
return handle_exception(nmbs, err);
else
return handle_exception(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
}
if (!nmbs->msg.broadcast) {
send_res_header(nmbs, 4);
put_2(nmbs, address);
put_2(nmbs, value);
DEBUG("a %d\tvalue %d", address, value);
err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
}
}
else {
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
}
}
return NMBS_ERROR_NONE;
}
static nmbs_error handle_write_multiple_coils(nmbs_t* nmbs) {
nmbs_error err = recv(nmbs, 5);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address = get_2(nmbs);
uint16_t quantity = get_2(nmbs);
uint8_t coils_bytes = get_1(nmbs);
DEBUG("a %d\tq %d\tb %d\tcoils ", address, quantity, coils_bytes);
err = recv(nmbs, coils_bytes);
if (err != NMBS_ERROR_NONE)
return err;
nmbs_bitfield coils;
for (int i = 0; i < coils_bytes; i++) {
coils[i] = get_1(nmbs);
DEBUG("%d ", coils[i]);
}
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.ignored) {
if (quantity < 1 || quantity > 0x07B0)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
if (coils_bytes == 0)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if ((quantity / 8) + 1 != coils_bytes)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if (nmbs->callbacks.write_multiple_coils) {
err = nmbs->callbacks.write_multiple_coils(address, quantity, coils);
if (err != NMBS_ERROR_NONE) {
if (nmbs_error_is_exception(err))
return handle_exception(nmbs, err);
else
return handle_exception(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
}
if (!nmbs->msg.broadcast) {
send_res_header(nmbs, 4);
put_2(nmbs, address);
put_2(nmbs, quantity);
DEBUG("a %d\tq %d", address, quantity);
err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
}
}
else {
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
}
}
return NMBS_ERROR_NONE;
}
static nmbs_error handle_write_multiple_registers(nmbs_t* nmbs) {
nmbs_error err = recv(nmbs, 5);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address = get_2(nmbs);
uint16_t quantity = get_2(nmbs);
uint8_t registers_bytes = get_1(nmbs);
DEBUG("a %d\tq %d\tb %d\tregs ", address, quantity, registers_bytes);
err = recv(nmbs, registers_bytes);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t registers[0x007B];
for (int i = 0; i < registers_bytes / 2; i++) {
registers[i] = get_2(nmbs);
DEBUG("%d ", registers[i]);
}
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.ignored) {
if (quantity < 1 || quantity > 0x007B)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
if (registers_bytes == 0)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if (registers_bytes != quantity * 2)
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
if (nmbs->callbacks.write_multiple_registers) {
err = nmbs->callbacks.write_multiple_registers(address, quantity, registers);
if (err != NMBS_ERROR_NONE) {
if (nmbs_error_is_exception(err))
return handle_exception(nmbs, err);
else
return handle_exception(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
}
if (!nmbs->msg.broadcast) {
send_res_header(nmbs, 4);
put_2(nmbs, address);
put_2(nmbs, quantity);
DEBUG("a %d\tq %d", address, quantity);
err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
}
}
else {
return handle_exception(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
}
}
return NMBS_ERROR_NONE;
}
static nmbs_error handle_req_fc(nmbs_t* nmbs) {
DEBUG("fc %d\t", nmbs->msg.fc);
nmbs_error err;
switch (nmbs->msg.fc) {
case 1:
err = handle_read_coils(nmbs);
break;
case 2:
err = handle_read_discrete_inputs(nmbs);
break;
case 3:
err = handle_read_holding_registers(nmbs);
break;
case 4:
err = handle_read_input_registers(nmbs);
break;
case 5:
err = handle_write_single_coil(nmbs);
break;
case 6:
err = handle_write_single_register(nmbs);
break;
case 15:
err = handle_write_multiple_coils(nmbs);
break;
case 16:
err = handle_write_multiple_registers(nmbs);
break;
default:
err = NMBS_EXCEPTION_ILLEGAL_FUNCTION;
}
return err;
}
nmbs_error nmbs_server_poll(nmbs_t* nmbs) {
msg_state_reset(nmbs);
bool first_byte_received = false;
nmbs_error err = recv_req_header(nmbs, &first_byte_received);
if (err != NMBS_ERROR_NONE) {
if (!first_byte_received && err == NMBS_ERROR_TIMEOUT)
return NMBS_ERROR_NONE;
else
return err;
}
#ifdef NMBS_DEBUG
printf("NMBS req <- ");
if (nmbs->platform.transport == NMBS_TRANSPORT_RTU) {
if (nmbs->msg.broadcast)
printf("broadcast\t");
printf("client_id %d\t", nmbs->msg.unit_id);
}
#endif
err = handle_req_fc(nmbs);
if (err != NMBS_ERROR_NONE) {
if (!nmbs_error_is_exception(err))
return err;
}
return err;
}
static nmbs_error read_discrete(nmbs_t* nmbs, uint8_t fc, uint16_t address, uint16_t quantity, nmbs_bitfield values) {
if (quantity < 1 || quantity > 2000)
return NMBS_ERROR_INVALID_ARGUMENT;
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return NMBS_ERROR_INVALID_ARGUMENT;
msg_state_req(nmbs, fc);
send_req_header(nmbs, 4);
put_2(nmbs, address);
put_2(nmbs, quantity);
DEBUG("a %d\tq %d", address, quantity);
nmbs_error err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv_res_header(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv(nmbs, 1);
if (err != NMBS_ERROR_NONE)
return err;
uint8_t coils_bytes = get_1(nmbs);
DEBUG("b %d\t", coils_bytes);
err = recv(nmbs, coils_bytes);
if (err != NMBS_ERROR_NONE)
return err;
DEBUG("coils ");
for (int i = 0; i < coils_bytes; i++) {
values[i] = get_1(nmbs);
DEBUG("%d", values[i]);
}
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
return NMBS_ERROR_NONE;
}
nmbs_error nmbs_read_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield coils_out) {
return read_discrete(nmbs, 1, address, quantity, coils_out);
}
nmbs_error nmbs_read_discrete_inputs(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out) {
return read_discrete(nmbs, 2, address, quantity, inputs_out);
}
static nmbs_error read_registers(nmbs_t* nmbs, uint8_t fc, uint16_t address, uint16_t quantity, uint16_t* registers) {
if (quantity < 1 || quantity > 125)
return NMBS_ERROR_INVALID_ARGUMENT;
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return NMBS_ERROR_INVALID_ARGUMENT;
msg_state_req(nmbs, fc);
send_req_header(nmbs, 4);
put_2(nmbs, address);
put_2(nmbs, quantity);
DEBUG("a %d\tq %d ", address, quantity);
nmbs_error err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv_res_header(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv(nmbs, 1);
if (err != NMBS_ERROR_NONE)
return err;
uint8_t registers_bytes = get_1(nmbs);
DEBUG("b %d\t", registers_bytes);
err = recv(nmbs, registers_bytes);
if (err != NMBS_ERROR_NONE)
return err;
DEBUG("regs ");
for (int i = 0; i < registers_bytes / 2; i++) {
registers[i] = get_2(nmbs);
DEBUG("%d", registers[i]);
}
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (registers_bytes != quantity * 2)
return NMBS_ERROR_INVALID_RESPONSE;
return NMBS_ERROR_NONE;
}
nmbs_error nmbs_read_holding_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out) {
return read_registers(nmbs, 3, address, quantity, registers_out);
}
nmbs_error nmbs_read_input_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out) {
return read_registers(nmbs, 4, address, quantity, registers_out);
}
nmbs_error nmbs_write_single_coil(nmbs_t* nmbs, uint16_t address, bool value) {
msg_state_req(nmbs, 5);
send_req_header(nmbs, 4);
uint16_t value_req = value ? 0xFF00 : 0;
put_2(nmbs, address);
put_2(nmbs, value_req);
DEBUG("a %d\tvalue %d ", address, value_req);
nmbs_error err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.broadcast) {
err = recv_res_header(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address_res = get_2(nmbs);
uint16_t value_res = get_2(nmbs);
DEBUG("a %d\tvalue %d", address, value_res);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (address_res != address)
return NMBS_ERROR_INVALID_RESPONSE;
if (value_res != value_req)
return NMBS_ERROR_INVALID_RESPONSE;
}
return NMBS_ERROR_NONE;
}
nmbs_error nmbs_write_single_register(nmbs_t* nmbs, uint16_t address, uint16_t value) {
msg_state_req(nmbs, 6);
send_req_header(nmbs, 4);
put_2(nmbs, address);
put_2(nmbs, value);
DEBUG("a %d\tvalue %d", address, value);
nmbs_error err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.broadcast) {
err = recv_res_header(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address_res = get_2(nmbs);
uint16_t value_res = get_2(nmbs);
DEBUG("a %d\tvalue %d ", address, value_res);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (address_res != address)
return NMBS_ERROR_INVALID_RESPONSE;
if (value_res != value)
return NMBS_ERROR_INVALID_RESPONSE;
}
return NMBS_ERROR_NONE;
}
nmbs_error nmbs_write_multiple_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const nmbs_bitfield coils) {
if (quantity < 1 || quantity > 0x07B0)
return NMBS_ERROR_INVALID_ARGUMENT;
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return NMBS_ERROR_INVALID_ARGUMENT;
uint8_t coils_bytes = (quantity / 8) + 1;
msg_state_req(nmbs, 15);
send_req_header(nmbs, 5 + coils_bytes);
put_2(nmbs, address);
put_2(nmbs, quantity);
put_1(nmbs, coils_bytes);
DEBUG("a %d\tq %d\tb %d\t", address, quantity, coils_bytes);
DEBUG("coils ");
for (int i = 0; i < coils_bytes; i++) {
put_1(nmbs, coils[i]);
DEBUG("%d ", coils[i]);
}
nmbs_error err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.broadcast) {
err = recv_res_header(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address_res = get_2(nmbs);
uint16_t quantity_res = get_2(nmbs);
DEBUG("a %d\tq %d", address_res, quantity_res);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (address_res != address)
return NMBS_ERROR_INVALID_RESPONSE;
if (quantity_res != quantity)
return NMBS_ERROR_INVALID_RESPONSE;
}
return NMBS_ERROR_NONE;
}
nmbs_error nmbs_write_multiple_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const uint16_t* registers) {
if (quantity < 1 || quantity > 0x007B)
return NMBS_ERROR_INVALID_ARGUMENT;
if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1)
return NMBS_ERROR_INVALID_ARGUMENT;
uint8_t registers_bytes = quantity * 2;
msg_state_req(nmbs, 16);
send_req_header(nmbs, 5 + registers_bytes);
put_2(nmbs, address);
put_2(nmbs, quantity);
put_1(nmbs, registers_bytes);
DEBUG("a %d\tq %d\tb %d\t", address, quantity, registers_bytes);
DEBUG("regs ");
for (int i = 0; i < quantity; i++) {
put_2(nmbs, registers[i]);
DEBUG("%d ", registers[i]);
}
nmbs_error err = send_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (!nmbs->msg.broadcast) {
err = recv_res_header(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv(nmbs, 4);
if (err != NMBS_ERROR_NONE)
return err;
uint16_t address_res = get_2(nmbs);
uint16_t quantity_res = get_2(nmbs);
DEBUG("a %d\tq %d", address_res, quantity_res);
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
if (address_res != address)
return NMBS_ERROR_INVALID_RESPONSE;
if (quantity_res != quantity)
return NMBS_ERROR_INVALID_RESPONSE;
}
return NMBS_ERROR_NONE;
}
nmbs_error nmbs_send_raw_pdu(nmbs_t* nmbs, uint8_t fc, const void* data, uint32_t data_len) {
msg_state_req(nmbs, fc);
send_msg_header(nmbs, data_len);
DEBUG("raw ");
for (uint32_t i = 0; i < data_len; i++) {
put_1(nmbs, ((uint8_t*) (data))[i]);
DEBUG("%d ", ((uint8_t*) (data))[i]);
}
return send_msg_footer(nmbs);
}
nmbs_error nmbs_receive_raw_pdu_response(nmbs_t* nmbs, void* data_out, uint32_t data_out_len) {
nmbs_error err = recv_res_header(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
err = recv(nmbs, data_out_len);
if (err != NMBS_ERROR_NONE)
return err;
for (uint32_t i = 0; i < data_out_len; i++) {
((uint8_t*) (data_out))[i] = get_1(nmbs);
}
err = recv_msg_footer(nmbs);
if (err != NMBS_ERROR_NONE)
return err;
return NMBS_ERROR_NONE;
}
#ifndef NMBS_STRERROR_DISABLED
const char* nmbs_strerror(nmbs_error error) {
switch (error) {
case NMBS_ERROR_TRANSPORT:
return "transport error";
case NMBS_ERROR_TIMEOUT:
return "timeout";
case NMBS_ERROR_INVALID_RESPONSE:
return "invalid response received";
case NMBS_ERROR_INVALID_ARGUMENT:
return "invalid argument provided";
case NMBS_ERROR_NONE:
return "no error";
case NMBS_EXCEPTION_ILLEGAL_FUNCTION:
return "modbus exception 1: illegal function";
case NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS:
return "modbus exception 2: illegal data address";
case NMBS_EXCEPTION_ILLEGAL_DATA_VALUE:
return "modbus exception 3: data value";
case NMBS_EXCEPTION_SERVER_DEVICE_FAILURE:
return "modbus exception 4: server device failure";
default:
return "unknown error";
}
}
#endif