Add File Records functions
Add both File Records functions (0x14 Read and 0x15 Write) for Client and Server roles. Also, add a little tests and example. Resolves: [#1] and [#24]
This commit is contained in:
parent
cbd4fa69fb
commit
e5b51cc4e7
@ -24,6 +24,8 @@ Its main features are:
|
|||||||
- 06 (0x06) Write Single Register
|
- 06 (0x06) Write Single Register
|
||||||
- 15 (0x0F) Write Multiple Coils
|
- 15 (0x0F) Write Multiple Coils
|
||||||
- 16 (0x10) Write Multiple registers
|
- 16 (0x10) Write Multiple registers
|
||||||
|
- 20 (0x14) Read File Record
|
||||||
|
- 21 (0x15) Write File Record
|
||||||
- Platform-agnostic
|
- Platform-agnostic
|
||||||
- Requires only C99 and its standard library
|
- Requires only C99 and its standard library
|
||||||
- Data transport read/write function are implemented by the user
|
- Data transport read/write function are implemented by the user
|
||||||
@ -151,5 +153,7 @@ Please refer to `examples/arduino/README.md` for more info about building and ru
|
|||||||
- `NMBS_SERVER_WRITE_SINGLE_REGISTER_DISABLED`
|
- `NMBS_SERVER_WRITE_SINGLE_REGISTER_DISABLED`
|
||||||
- `NMBS_SERVER_WRITE_MULTIPLE_COILS_DISABLED`
|
- `NMBS_SERVER_WRITE_MULTIPLE_COILS_DISABLED`
|
||||||
- `NMBS_SERVER_WRITE_MULTIPLE_REGISTERS_DISABLED`
|
- `NMBS_SERVER_WRITE_MULTIPLE_REGISTERS_DISABLED`
|
||||||
|
- `NMBS_SERVER_READ_FILE_RECORD_DISABLED`
|
||||||
|
- `NMBS_SERVER_WRITE_FILE_RECORD_DISABLED`
|
||||||
- `NMBS_STRERROR_DISABLED` to disable the code that converts `nmbs_error`s to strings
|
- `NMBS_STRERROR_DISABLED` to disable the code that converts `nmbs_error`s to strings
|
||||||
- Debug prints about received and sent messages can be enabled by defining `NMBS_DEBUG`
|
- Debug prints about received and sent messages can be enabled by defining `NMBS_DEBUG`
|
||||||
|
|||||||
@ -12,7 +12,6 @@
|
|||||||
#include "nanomodbus.h"
|
#include "nanomodbus.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
fprintf(stderr, "Usage: client-tcp [address] [port]\n");
|
fprintf(stderr, "Usage: client-tcp [address] [port]\n");
|
||||||
@ -91,6 +90,30 @@ int main(int argc, char* argv[]) {
|
|||||||
printf("Register at address 27: %d\n", r_regs[1]);
|
printf("Register at address 27: %d\n", r_regs[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write file
|
||||||
|
uint16_t file[4] = {0x0000, 0x00AA, 0x5500, 0xFFFF};
|
||||||
|
err = nmbs_write_file_record(&nmbs, 1, 0, file, 4);
|
||||||
|
if (err != NMBS_ERROR_NONE) {
|
||||||
|
fprintf(stderr, "Error writing file - %s", nmbs_strerror(err));
|
||||||
|
if (!nmbs_error_is_exception(err))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("Write file registers: 0x%04X 0x%04X 0x%04X 0x%04X\n", file[0], file[1], file[2], file[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file
|
||||||
|
memset(file, 0, sizeof(file));
|
||||||
|
err = nmbs_read_file_record(&nmbs, 1, 0, file, 4);
|
||||||
|
if (err != NMBS_ERROR_NONE) {
|
||||||
|
fprintf(stderr, "Error writing file - %s", nmbs_strerror(err));
|
||||||
|
if (!nmbs_error_is_exception(err))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("Read file registers: 0x%04X 0x%04X 0x%04X 0x%04X\n", file[0], file[1], file[2], file[3]);
|
||||||
|
}
|
||||||
|
|
||||||
// Close the TCP connection
|
// Close the TCP connection
|
||||||
disconnect(conn);
|
disconnect(conn);
|
||||||
|
|
||||||
|
|||||||
@ -22,13 +22,16 @@
|
|||||||
// The data model of this sever will support coils addresses 0 to 100 and registers addresses from 0 to 32
|
// The data model of this sever will support coils addresses 0 to 100 and registers addresses from 0 to 32
|
||||||
#define COILS_ADDR_MAX 100
|
#define COILS_ADDR_MAX 100
|
||||||
#define REGS_ADDR_MAX 32
|
#define REGS_ADDR_MAX 32
|
||||||
|
#define FILE_SIZE_MAX 32
|
||||||
|
|
||||||
// A single nmbs_bitfield variable can keep 2000 coils
|
// A single nmbs_bitfield variable can keep 2000 coils
|
||||||
nmbs_bitfield server_coils = {0};
|
nmbs_bitfield server_coils = {0};
|
||||||
uint16_t server_registers[REGS_ADDR_MAX] = {0};
|
uint16_t server_registers[REGS_ADDR_MAX] = {0};
|
||||||
|
uint16_t server_file[FILE_SIZE_MAX];
|
||||||
|
|
||||||
nmbs_error handle_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, void* arg) {
|
nmbs_error handle_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
|
||||||
UNUSED_PARAM(arg);
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
if (address + quantity > COILS_ADDR_MAX + 1)
|
if (address + quantity > COILS_ADDR_MAX + 1)
|
||||||
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
@ -43,8 +46,10 @@ nmbs_error handle_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
nmbs_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, void* arg) {
|
nmbs_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils, uint8_t unit_id,
|
||||||
|
void* arg) {
|
||||||
UNUSED_PARAM(arg);
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
if (address + quantity > COILS_ADDR_MAX + 1)
|
if (address + quantity > COILS_ADDR_MAX + 1)
|
||||||
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
@ -58,8 +63,10 @@ nmbs_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
nmbs_error handler_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, void* arg) {
|
nmbs_error handler_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out, uint8_t unit_id,
|
||||||
|
void* arg) {
|
||||||
UNUSED_PARAM(arg);
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
if (address + quantity > REGS_ADDR_MAX + 1)
|
if (address + quantity > REGS_ADDR_MAX + 1)
|
||||||
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
@ -72,8 +79,10 @@ nmbs_error handler_read_holding_registers(uint16_t address, uint16_t quantity, u
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
nmbs_error handle_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers, void* arg) {
|
nmbs_error handle_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers,
|
||||||
|
uint8_t unit_id, void* arg) {
|
||||||
UNUSED_PARAM(arg);
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
if (address + quantity > REGS_ADDR_MAX + 1)
|
if (address + quantity > REGS_ADDR_MAX + 1)
|
||||||
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
@ -86,6 +95,40 @@ nmbs_error handle_write_multiple_registers(uint16_t address, uint16_t quantity,
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error handle_read_file_record(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
|
||||||
|
uint8_t unit_id, void* arg) {
|
||||||
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
|
if (file_number != 1)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
|
|
||||||
|
if ((record_number + count) > FILE_SIZE_MAX)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
|
|
||||||
|
memcpy(registers, server_file + record_number, count * sizeof(uint16_t));
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error handle_write_file_record(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
|
||||||
|
uint16_t count, uint8_t unit_id, void* arg) {
|
||||||
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
|
if (file_number != 1)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
|
|
||||||
|
if ((record_number + count) > FILE_SIZE_MAX)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
|
|
||||||
|
memcpy(server_file + record_number, registers, count * sizeof(uint16_t));
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
fprintf(stderr, "Usage: server-tcp [address] [port]\n");
|
fprintf(stderr, "Usage: server-tcp [address] [port]\n");
|
||||||
@ -111,6 +154,8 @@ int main(int argc, char* argv[]) {
|
|||||||
callbacks.write_multiple_coils = handle_write_multiple_coils;
|
callbacks.write_multiple_coils = handle_write_multiple_coils;
|
||||||
callbacks.read_holding_registers = handler_read_holding_registers;
|
callbacks.read_holding_registers = handler_read_holding_registers;
|
||||||
callbacks.write_multiple_registers = handle_write_multiple_registers;
|
callbacks.write_multiple_registers = handle_write_multiple_registers;
|
||||||
|
callbacks.read_file_record = handle_read_file_record;
|
||||||
|
callbacks.write_file_record = handle_write_file_record;
|
||||||
|
|
||||||
// Create the modbus server. It's ok to set address_rtu to 0 since we are on TCP
|
// Create the modbus server. It's ok to set address_rtu to 0 since we are on TCP
|
||||||
nmbs_t nmbs;
|
nmbs_t nmbs;
|
||||||
|
|||||||
400
nanomodbus.c
400
nanomodbus.c
@ -55,6 +55,11 @@ static void discard_1(nmbs_t* nmbs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void discard_n(nmbs_t* nmbs, uint16_t n) {
|
||||||
|
nmbs->msg.buf_idx += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static uint16_t get_2(nmbs_t* nmbs) {
|
static uint16_t get_2(nmbs_t* nmbs) {
|
||||||
uint16_t result =
|
uint16_t result =
|
||||||
((uint16_t) nmbs->msg.buf[nmbs->msg.buf_idx]) << 8 | (uint16_t) nmbs->msg.buf[nmbs->msg.buf_idx + 1];
|
((uint16_t) nmbs->msg.buf[nmbs->msg.buf_idx]) << 8 | (uint16_t) nmbs->msg.buf[nmbs->msg.buf_idx + 1];
|
||||||
@ -70,6 +75,39 @@ static void put_2(nmbs_t* nmbs, uint16_t data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint8_t* get_n(nmbs_t* nmbs, uint16_t n) {
|
||||||
|
uint8_t* msg_buf_ptr = nmbs->msg.buf + nmbs->msg.buf_idx;
|
||||||
|
nmbs->msg.buf_idx += n;
|
||||||
|
return msg_buf_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static uint16_t* get_regs(nmbs_t* nmbs, uint16_t n) {
|
||||||
|
uint16_t* msg_buf_ptr = (uint16_t*) (nmbs->msg.buf + nmbs->msg.buf_idx);
|
||||||
|
nmbs->msg.buf_idx += n * 2;
|
||||||
|
while (n--) {
|
||||||
|
msg_buf_ptr[n] = (msg_buf_ptr[n] << 8) | ((msg_buf_ptr[n] >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
return msg_buf_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void put_regs(nmbs_t* nmbs, const uint16_t* data, uint16_t n) {
|
||||||
|
uint16_t* msg_buf_ptr = (uint16_t*) (nmbs->msg.buf + nmbs->msg.buf_idx);
|
||||||
|
nmbs->msg.buf_idx += n * 2;
|
||||||
|
while (n--) {
|
||||||
|
msg_buf_ptr[n] = (data[n] << 8) | ((data[n] >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void swap_regs(uint16_t* data, uint16_t n) {
|
||||||
|
while (n--) {
|
||||||
|
data[n] = (data[n] << 8) | ((data[n] >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void msg_buf_reset(nmbs_t* nmbs) {
|
static void msg_buf_reset(nmbs_t* nmbs) {
|
||||||
nmbs->msg.buf_idx = 0;
|
nmbs->msg.buf_idx = 0;
|
||||||
}
|
}
|
||||||
@ -593,6 +631,95 @@ nmbs_error recv_write_multiple_registers_res(nmbs_t* nmbs, uint16_t address, uin
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error recv_read_file_record_res(nmbs_t* nmbs, uint16_t* registers, uint16_t count) {
|
||||||
|
nmbs_error 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 respone_size = get_1(nmbs);
|
||||||
|
|
||||||
|
err = recv(nmbs, respone_size);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
uint8_t subreq_data_size = get_1(nmbs) - 1;
|
||||||
|
uint8_t subreq_reference_type = get_1(nmbs);
|
||||||
|
uint16_t* subreq_record_data = (uint16_t*) get_n(nmbs, subreq_data_size);
|
||||||
|
|
||||||
|
err = recv_msg_footer(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (registers) {
|
||||||
|
if (subreq_reference_type != 6)
|
||||||
|
return NMBS_ERROR_INVALID_RESPONSE;
|
||||||
|
|
||||||
|
if (count != (subreq_data_size / 2))
|
||||||
|
return NMBS_ERROR_INVALID_RESPONSE;
|
||||||
|
|
||||||
|
swap_regs(subreq_record_data, subreq_data_size / 2);
|
||||||
|
memcpy(registers, subreq_record_data, subreq_data_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error recv_write_file_record_res(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number,
|
||||||
|
const uint16_t* registers, uint16_t count) {
|
||||||
|
nmbs_error 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 respone_size = get_1(nmbs);
|
||||||
|
|
||||||
|
err = recv(nmbs, respone_size);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
uint8_t subreq_reference_type = get_1(nmbs);
|
||||||
|
uint16_t subreq_file_number = get_2(nmbs);
|
||||||
|
uint16_t subreq_record_number = get_2(nmbs);
|
||||||
|
uint16_t subreq_record_lenght = get_2(nmbs);
|
||||||
|
NMBS_DEBUG_PRINT("a %d\tr %d\tl %d\t fwrite ", subreq_file_number, subreq_record_number, subreq_record_lenght);
|
||||||
|
|
||||||
|
uint16_t subreq_data_size = subreq_record_lenght * 2;
|
||||||
|
uint16_t* subreq_record_data = (uint16_t*) get_n(nmbs, subreq_data_size);
|
||||||
|
|
||||||
|
err = recv_msg_footer(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (registers) {
|
||||||
|
if (subreq_reference_type != 6)
|
||||||
|
return NMBS_ERROR_INVALID_RESPONSE;
|
||||||
|
|
||||||
|
if (subreq_file_number != file_number)
|
||||||
|
return NMBS_ERROR_INVALID_RESPONSE;
|
||||||
|
|
||||||
|
if (subreq_record_number != record_number)
|
||||||
|
return NMBS_ERROR_INVALID_RESPONSE;
|
||||||
|
|
||||||
|
if (subreq_record_lenght != count)
|
||||||
|
return NMBS_ERROR_INVALID_RESPONSE;
|
||||||
|
|
||||||
|
swap_regs(subreq_record_data, subreq_record_lenght);
|
||||||
|
if (memcmp(registers, subreq_record_data, subreq_data_size))
|
||||||
|
return NMBS_ERROR_INVALID_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifndef NMBS_SERVER_DISABLED
|
#ifndef NMBS_SERVER_DISABLED
|
||||||
#if !defined(NMBS_SERVER_READ_COILS_DISABLED) || !defined(NMBS_SERVER_READ_DISCRETE_INPUTS_DISABLED)
|
#if !defined(NMBS_SERVER_READ_COILS_DISABLED) || !defined(NMBS_SERVER_READ_DISCRETE_INPUTS_DISABLED)
|
||||||
static nmbs_error handle_read_discrete(nmbs_t* nmbs,
|
static nmbs_error handle_read_discrete(nmbs_t* nmbs,
|
||||||
@ -1002,6 +1129,206 @@ static nmbs_error handle_write_multiple_registers(nmbs_t* nmbs) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef NMBS_SERVER_READ_FILE_RECORD_DISABLED
|
||||||
|
static nmbs_error handle_read_file_record(nmbs_t* nmbs) {
|
||||||
|
nmbs_error err = recv(nmbs, 1);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
uint8_t request_size = get_1(nmbs);
|
||||||
|
|
||||||
|
err = recv(nmbs, request_size);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
const uint8_t subreq_header_size = 7;
|
||||||
|
uint8_t subreq_count = request_size / subreq_header_size;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
uint8_t reference_type;
|
||||||
|
uint16_t file_number;
|
||||||
|
uint16_t record_number;
|
||||||
|
uint16_t record_lenght;
|
||||||
|
} subreq[subreq_count];
|
||||||
|
|
||||||
|
uint8_t respone_data_size = 0;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < subreq_count; i++) {
|
||||||
|
subreq[i].reference_type = get_1(nmbs);
|
||||||
|
subreq[i].file_number = get_2(nmbs);
|
||||||
|
subreq[i].record_number = get_2(nmbs);
|
||||||
|
subreq[i].record_lenght = get_2(nmbs);
|
||||||
|
|
||||||
|
respone_data_size += 2 + subreq[i].record_lenght * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
discard_n(nmbs, request_size % subreq_header_size);
|
||||||
|
|
||||||
|
err = recv_msg_footer(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (!nmbs->msg.ignored) {
|
||||||
|
if (request_size % subreq_header_size)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||||
|
|
||||||
|
if (request_size < 0x07 || request_size > 0xF5)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||||
|
|
||||||
|
put_res_header(nmbs, respone_data_size);
|
||||||
|
put_1(nmbs, respone_data_size);
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < subreq_count; i++) {
|
||||||
|
if (subreq[i].reference_type != 0x06)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
if (subreq[i].file_number == 0x0000)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
if (subreq[i].record_number > 0x270F)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
NMBS_DEBUG_PRINT("a %d\tr %d\tl %d\t fread ", subreq[i].file_number, subreq[i].record_number,
|
||||||
|
subreq[i].record_lenght);
|
||||||
|
|
||||||
|
uint16_t subreq_data_size = subreq[i].record_lenght * 2;
|
||||||
|
put_1(nmbs, subreq_data_size + 1);
|
||||||
|
put_1(nmbs, 0x06); // add Reference Type const
|
||||||
|
uint16_t* subreq_data = (uint16_t*) get_n(nmbs, subreq[i].record_lenght * 2);
|
||||||
|
|
||||||
|
if (nmbs->callbacks.read_file_record) {
|
||||||
|
err = nmbs->callbacks.read_file_record(subreq[i].file_number, subreq[i].record_number, subreq_data,
|
||||||
|
subreq[i].record_lenght, nmbs->msg.unit_id, nmbs->platform.arg);
|
||||||
|
if (err != NMBS_ERROR_NONE) {
|
||||||
|
if (nmbs_error_is_exception(err))
|
||||||
|
return send_exception_msg(nmbs, err);
|
||||||
|
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
swap_regs(subreq_data, subreq[i].record_lenght);
|
||||||
|
//get_regs(nmbs, subreq[i].record_lenght);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nmbs->msg.broadcast) {
|
||||||
|
err = send_msg(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return recv_read_file_record_res(nmbs, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NMBS_SERVER_WRITE_FILE_RECORD_DISABLED
|
||||||
|
static nmbs_error handle_write_file_record(nmbs_t* nmbs) {
|
||||||
|
nmbs_error err = recv(nmbs, 1);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
uint8_t request_size = get_1(nmbs);
|
||||||
|
|
||||||
|
err = recv(nmbs, request_size);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// We can save msg.buf index and use it later for context recovery.
|
||||||
|
uint16_t msg_buf_idx = nmbs->msg.buf_idx;
|
||||||
|
discard_n(nmbs, request_size);
|
||||||
|
|
||||||
|
err = recv_msg_footer(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (!nmbs->msg.ignored) {
|
||||||
|
const uint8_t subreq_header_size = 7;
|
||||||
|
uint16_t size = request_size;
|
||||||
|
nmbs->msg.buf_idx = msg_buf_idx; // restore context
|
||||||
|
|
||||||
|
if (request_size < 0x07 || request_size > 0xF5)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||||
|
|
||||||
|
do {
|
||||||
|
uint8_t subreq_reference_type = get_1(nmbs);
|
||||||
|
uint16_t subreq_file_number = get_2(nmbs);
|
||||||
|
uint16_t subreq_record_number = get_2(nmbs);
|
||||||
|
uint16_t subreq_record_lenght = get_2(nmbs);
|
||||||
|
discard_n(nmbs, subreq_record_lenght * 2);
|
||||||
|
|
||||||
|
if (subreq_reference_type != 0x06)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
if (subreq_file_number == 0x0000)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
if (subreq_record_number > 0x270F)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
NMBS_DEBUG_PRINT("a %d\tr %d\tl %d\t fwrite ", subreq_file_number, subreq_record_number,
|
||||||
|
subreq_record_lenght);
|
||||||
|
size -= (subreq_header_size + subreq_record_lenght * 2);
|
||||||
|
} while (size >= subreq_header_size);
|
||||||
|
|
||||||
|
if (size)
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||||
|
|
||||||
|
// checks completed
|
||||||
|
|
||||||
|
size = request_size;
|
||||||
|
nmbs->msg.buf_idx = msg_buf_idx; // restore context
|
||||||
|
|
||||||
|
do {
|
||||||
|
discard_1(nmbs);
|
||||||
|
uint16_t subreq_file_number = get_2(nmbs);
|
||||||
|
uint16_t subreq_record_number = get_2(nmbs);
|
||||||
|
uint16_t subreq_record_lenght = get_2(nmbs);
|
||||||
|
uint16_t* subreq_data = get_regs(nmbs, subreq_record_lenght);
|
||||||
|
|
||||||
|
if (nmbs->callbacks.write_file_record) {
|
||||||
|
err = nmbs->callbacks.write_file_record(subreq_file_number, subreq_record_number, subreq_data,
|
||||||
|
subreq_record_lenght, nmbs->msg.unit_id, nmbs->platform.arg);
|
||||||
|
if (err != NMBS_ERROR_NONE) {
|
||||||
|
if (nmbs_error_is_exception(err))
|
||||||
|
return send_exception_msg(nmbs, err);
|
||||||
|
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
swap_regs(subreq_data, subreq_record_lenght); // restore swapping
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
size -= (subreq_header_size + subreq_record_lenght * 2);
|
||||||
|
} while (size >= subreq_header_size);
|
||||||
|
|
||||||
|
if (!nmbs->msg.broadcast) {
|
||||||
|
// The normal response to 'Write File' is an echo of the request.
|
||||||
|
// We can restore buffer index and respone msg.
|
||||||
|
nmbs->msg.buf_idx = msg_buf_idx;
|
||||||
|
discard_n(nmbs, request_size);
|
||||||
|
|
||||||
|
err = send_msg(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return recv_write_file_record_res(nmbs, 0, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
static nmbs_error handle_req_fc(nmbs_t* nmbs) {
|
static nmbs_error handle_req_fc(nmbs_t* nmbs) {
|
||||||
NMBS_DEBUG_PRINT("fc %d\t", nmbs->msg.fc);
|
NMBS_DEBUG_PRINT("fc %d\t", nmbs->msg.fc);
|
||||||
@ -1056,6 +1383,18 @@ static nmbs_error handle_req_fc(nmbs_t* nmbs) {
|
|||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef NMBS_SERVER_READ_FILE_RECORD_DISABLED
|
||||||
|
case 20:
|
||||||
|
err = handle_read_file_record(nmbs);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NMBS_SERVER_WRITE_FILE_RECORD_DISABLED
|
||||||
|
case 21:
|
||||||
|
err = handle_write_file_record(nmbs);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err = NMBS_EXCEPTION_ILLEGAL_FUNCTION;
|
err = NMBS_EXCEPTION_ILLEGAL_FUNCTION;
|
||||||
}
|
}
|
||||||
@ -1299,6 +1638,67 @@ nmbs_error nmbs_write_multiple_registers(nmbs_t* nmbs, uint16_t address, uint16_
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error nmbs_read_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, uint16_t* registers,
|
||||||
|
uint16_t count) {
|
||||||
|
if (file_number == 0x0000)
|
||||||
|
return NMBS_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
|
if (record_number > 0x270F)
|
||||||
|
return NMBS_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
|
msg_state_req(nmbs, 20);
|
||||||
|
put_req_header(nmbs, 8);
|
||||||
|
|
||||||
|
put_1(nmbs, 7); // add Byte Count
|
||||||
|
put_1(nmbs, 6); // add Reference Type const
|
||||||
|
put_2(nmbs, file_number);
|
||||||
|
put_2(nmbs, record_number);
|
||||||
|
put_2(nmbs, count);
|
||||||
|
NMBS_DEBUG_PRINT("a %d\tr %d\tl %d\t fread ", file_number, record_number, count);
|
||||||
|
|
||||||
|
nmbs_error err = send_msg(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (!nmbs->msg.broadcast)
|
||||||
|
return recv_read_file_record_res(nmbs, registers, count);
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error nmbs_write_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, const uint16_t* registers,
|
||||||
|
uint16_t count) {
|
||||||
|
if (file_number == 0x0000)
|
||||||
|
return NMBS_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
|
if (record_number > 0x270F)
|
||||||
|
return NMBS_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
|
uint16_t data_size = count * 2;
|
||||||
|
|
||||||
|
msg_state_req(nmbs, 21);
|
||||||
|
put_req_header(nmbs, 8 + data_size);
|
||||||
|
|
||||||
|
put_1(nmbs, 7 + data_size); // add Byte Count
|
||||||
|
put_1(nmbs, 6); // add Reference Type const
|
||||||
|
put_2(nmbs, file_number);
|
||||||
|
put_2(nmbs, record_number);
|
||||||
|
put_2(nmbs, count);
|
||||||
|
put_regs(nmbs, registers, count);
|
||||||
|
NMBS_DEBUG_PRINT("a %d\tr %d\tl %d\t fwrite ", file_number, record_number, count);
|
||||||
|
|
||||||
|
nmbs_error err = send_msg(nmbs);
|
||||||
|
if (err != NMBS_ERROR_NONE)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
if (!nmbs->msg.broadcast)
|
||||||
|
return recv_write_file_record_res(nmbs, file_number, record_number, registers, count);
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
nmbs_error nmbs_send_raw_pdu(nmbs_t* nmbs, uint8_t fc, const uint8_t* data, uint16_t data_len) {
|
nmbs_error nmbs_send_raw_pdu(nmbs_t* nmbs, uint8_t fc, const uint8_t* data, uint16_t data_len) {
|
||||||
msg_state_req(nmbs, fc);
|
msg_state_req(nmbs, fc);
|
||||||
put_msg_header(nmbs, data_len);
|
put_msg_header(nmbs, data_len);
|
||||||
|
|||||||
34
nanomodbus.h
34
nanomodbus.h
@ -183,6 +183,16 @@ typedef struct nmbs_callbacks {
|
|||||||
nmbs_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, const uint16_t* registers,
|
nmbs_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, const uint16_t* registers,
|
||||||
uint8_t unit_id, void* arg);
|
uint8_t unit_id, void* arg);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef NMBS_SERVER_READ_FILE_RECORD_DISABLED
|
||||||
|
nmbs_error (*read_file_record)(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count,
|
||||||
|
uint8_t unit_id, void* arg);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NMBS_SERVER_WRITE_FILE_RECORD_DISABLED
|
||||||
|
nmbs_error (*write_file_record)(uint16_t file_number, uint16_t record_number, const uint16_t* registers,
|
||||||
|
uint16_t count, uint8_t unit_id, void* arg);
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
char _nonempty; // Struct may become empty, which is undefined behavior
|
char _nonempty; // Struct may become empty, which is undefined behavior
|
||||||
@ -357,6 +367,30 @@ nmbs_error nmbs_write_multiple_coils(nmbs_t* nmbs, uint16_t address, uint16_t qu
|
|||||||
*/
|
*/
|
||||||
nmbs_error nmbs_write_multiple_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const uint16_t* registers);
|
nmbs_error nmbs_write_multiple_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const uint16_t* registers);
|
||||||
|
|
||||||
|
/** Send a FC 20 (0x14) Read File Record
|
||||||
|
* @param nmbs pointer to the nmbs_t instance
|
||||||
|
* @param file_number file number (1 to 65535)
|
||||||
|
* @param record_number record number from file (0000 to 9999)
|
||||||
|
* @param registers array of registers to read
|
||||||
|
* @param count count of registers
|
||||||
|
*
|
||||||
|
* @return NMBS_ERROR_NONE if successful, other errors otherwise.
|
||||||
|
*/
|
||||||
|
nmbs_error nmbs_read_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, uint16_t* registers,
|
||||||
|
uint16_t count);
|
||||||
|
|
||||||
|
/** Send a FC 21 (0x15) Write File Record
|
||||||
|
* @param nmbs pointer to the nmbs_t instance
|
||||||
|
* @param file_number file number (1 to 65535)
|
||||||
|
* @param record_number record number from file (0000 to 9999)
|
||||||
|
* @param registers array of registers to write
|
||||||
|
* @param count count of registers
|
||||||
|
*
|
||||||
|
* @return NMBS_ERROR_NONE if successful, other errors otherwise.
|
||||||
|
*/
|
||||||
|
nmbs_error nmbs_write_file_record(nmbs_t* nmbs, uint16_t file_number, uint16_t record_number, const uint16_t* registers,
|
||||||
|
uint16_t count);
|
||||||
|
|
||||||
/** Send a raw Modbus PDU.
|
/** Send a raw Modbus PDU.
|
||||||
* CRC on RTU will be calculated and sent by this function.
|
* CRC on RTU will be calculated and sent by this function.
|
||||||
* @param nmbs pointer to the nmbs_t instance
|
* @param nmbs pointer to the nmbs_t instance
|
||||||
|
|||||||
@ -68,8 +68,9 @@ int32_t write_wire(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void*
|
|||||||
return (int32_t) written;
|
return (int32_t) written;
|
||||||
}
|
}
|
||||||
|
|
||||||
nmbs_error read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, void* arg) {
|
nmbs_error read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out, uint8_t unit_id, void* arg) {
|
||||||
UNUSED_PARAM(arg);
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
for (int i = 0; i < quantity; i++)
|
for (int i = 0; i < quantity; i++)
|
||||||
nmbs_bitfield_write(coils_out, address + i, 1);
|
nmbs_bitfield_write(coils_out, address + i, 1);
|
||||||
|
|
||||||
|
|||||||
@ -821,6 +821,148 @@ void test_fc16(nmbs_transport transport) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error read_file(uint16_t file_number, uint16_t record_number, uint16_t* registers, uint16_t count, uint8_t unit_id,
|
||||||
|
void* arg) {
|
||||||
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
|
if (file_number == 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (file_number == 2)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
|
|
||||||
|
if (file_number == 3)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE;
|
||||||
|
|
||||||
|
if (file_number == 4 && record_number == 4 && count == 4) {
|
||||||
|
registers[0] = 0;
|
||||||
|
registers[1] = 0xFF;
|
||||||
|
registers[2] = 0xAA55;
|
||||||
|
registers[3] = 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_number == 255 && record_number == 9999 && count == 119)
|
||||||
|
registers[118] = 42;
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test_fc20(nmbs_transport transport) {
|
||||||
|
uint16_t registers[128];
|
||||||
|
nmbs_callbacks callbacks_empty = {0};
|
||||||
|
|
||||||
|
start_client_and_server(transport, &callbacks_empty);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||||
|
expect(nmbs_read_file_record(&CLIENT, 1, 0, NULL, 0) == NMBS_EXCEPTION_ILLEGAL_FUNCTION);
|
||||||
|
|
||||||
|
stop_client_and_server();
|
||||||
|
|
||||||
|
start_client_and_server(transport, &(nmbs_callbacks){.read_file_record = read_file});
|
||||||
|
|
||||||
|
should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with file_number 0");
|
||||||
|
expect(nmbs_read_file_record(&CLIENT, 0, 0, registers, 1) == NMBS_ERROR_INVALID_ARGUMENT);
|
||||||
|
|
||||||
|
should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with record_number > 9999");
|
||||||
|
expect(nmbs_read_file_record(&CLIENT, 1, 10000, registers, 1) == NMBS_ERROR_INVALID_ARGUMENT);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error");
|
||||||
|
expect(nmbs_read_file_record(&CLIENT, 1, 1, registers, 3) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler");
|
||||||
|
expect(nmbs_read_file_record(&CLIENT, 2, 1, registers, 3) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler");
|
||||||
|
expect(nmbs_read_file_record(&CLIENT, 3, 1, registers, 3) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||||
|
|
||||||
|
should("read with no error");
|
||||||
|
check(nmbs_read_file_record(&CLIENT, 4, 4, registers, 4));
|
||||||
|
expect(registers[0] == 0);
|
||||||
|
expect(registers[1] == 0xFF);
|
||||||
|
expect(registers[2] == 0xAA55);
|
||||||
|
expect(registers[3] == 0xFFFF);
|
||||||
|
|
||||||
|
check(nmbs_read_file_record(&CLIENT, 255, 9999, registers, 119));
|
||||||
|
expect(registers[118] == 42);
|
||||||
|
|
||||||
|
stop_client_and_server();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
nmbs_error write_file(uint16_t file_number, uint16_t record_number, const uint16_t* registers, uint16_t count,
|
||||||
|
uint8_t unit_id, void* arg) {
|
||||||
|
UNUSED_PARAM(arg);
|
||||||
|
UNUSED_PARAM(unit_id);
|
||||||
|
|
||||||
|
if (file_number == 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (file_number == 2)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS;
|
||||||
|
|
||||||
|
if (file_number == 3)
|
||||||
|
return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE;
|
||||||
|
|
||||||
|
if (file_number == 4 && record_number == 4 && count == 4) {
|
||||||
|
expect(registers[0] == 0);
|
||||||
|
expect(registers[1] == 0xFF);
|
||||||
|
expect(registers[2] == 0xAA55);
|
||||||
|
expect(registers[3] == 0xFFFF);
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_number == 255 && record_number == 9999 && count == 119)
|
||||||
|
expect(registers[118] == 42);
|
||||||
|
|
||||||
|
return NMBS_ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void test_fc21(nmbs_transport transport) {
|
||||||
|
uint16_t registers[128];
|
||||||
|
nmbs_callbacks callbacks_empty = {0};
|
||||||
|
|
||||||
|
start_client_and_server(transport, &callbacks_empty);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||||
|
expect(nmbs_write_file_record(&CLIENT, 1, 0, NULL, 0) == NMBS_EXCEPTION_ILLEGAL_FUNCTION);
|
||||||
|
|
||||||
|
stop_client_and_server();
|
||||||
|
|
||||||
|
start_client_and_server(transport, &(nmbs_callbacks){.write_file_record = write_file});
|
||||||
|
|
||||||
|
should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with file_number 0");
|
||||||
|
expect(nmbs_write_file_record(&CLIENT, 0, 0, registers, 1) == NMBS_ERROR_INVALID_ARGUMENT);
|
||||||
|
|
||||||
|
should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with record_number > 0x007B");
|
||||||
|
expect(nmbs_write_file_record(&CLIENT, 1, 10000, registers, 1) == NMBS_ERROR_INVALID_ARGUMENT);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error");
|
||||||
|
expect(nmbs_write_file_record(&CLIENT, 1, 1, registers, 1) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler");
|
||||||
|
expect(nmbs_write_file_record(&CLIENT, 2, 2, registers, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||||
|
|
||||||
|
should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler");
|
||||||
|
expect(nmbs_write_file_record(&CLIENT, 3, 3, registers, 3) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||||
|
|
||||||
|
should("write with no error");
|
||||||
|
registers[0] = 0;
|
||||||
|
registers[1] = 0xFF;
|
||||||
|
registers[2] = 0xAA55;
|
||||||
|
registers[3] = 0xFFFF;
|
||||||
|
check(nmbs_write_file_record(&CLIENT, 4, 4, registers, 4));
|
||||||
|
|
||||||
|
registers[118] = 42;
|
||||||
|
check(nmbs_write_file_record(&CLIENT, 255, 9999, registers, 119));
|
||||||
|
|
||||||
|
stop_client_and_server();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
nmbs_transport transports[2] = {NMBS_TRANSPORT_RTU, NMBS_TRANSPORT_TCP};
|
nmbs_transport transports[2] = {NMBS_TRANSPORT_RTU, NMBS_TRANSPORT_TCP};
|
||||||
const char* transports_str[2] = {"RTU", "TCP"};
|
const char* transports_str[2] = {"RTU", "TCP"};
|
||||||
|
|
||||||
@ -855,5 +997,9 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
for_transports(test_fc16, "send and receive FC 16 (0x10) Write Multiple registers");
|
for_transports(test_fc16, "send and receive FC 16 (0x10) Write Multiple registers");
|
||||||
|
|
||||||
|
for_transports(test_fc20, "send and receive FC 20 (0x14) Read File Record");
|
||||||
|
|
||||||
|
for_transports(test_fc21, "send and receive FC 21 (0x15) Write File Record");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user