diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b791d9..d454529 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(modbusino C) +project(nanomodbus C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic") @@ -8,10 +8,10 @@ set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") include_directories(tests examples .) -#add_definitions(-DMBSN_DEBUG) +#add_definitions(-DNMBS_DEBUG) -add_executable(modbusino_tests modbusino.c tests/modbusino_tests.c) -add_executable(client-tcp modbusino.c examples/client-tcp.c) -add_executable(server-tcp modbusino.c examples/server-tcp.c) +add_executable(nanomodbus_tests nanomodbus.c tests/nanomodbus_tests.c) +add_executable(client-tcp nanomodbus.c examples/client-tcp.c) +add_executable(server-tcp nanomodbus.c examples/server-tcp.c) -target_link_libraries(modbusino_tests pthread) +target_link_libraries(nanomodbus_tests pthread) diff --git a/Doxyfile b/Doxyfile index 97a2aa2..ac5316e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,5 +1,5 @@ -PROJECT_NAME = MODBUSino -INPUT = modbusino.c modbusino.h +PROJECT_NAME = nanoMODBUS +INPUT = nanomodbus.c nanomodbus.h OUTPUT_DIRECTORY = doxygen OPTIMIZE_OUTPUT_FOR_C = YES GENERATE_LATEX = NO diff --git a/README.md b/README.md index cef305f..b625b6f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# MODBUSino - A compact MODBUS RTU/TCP C library for microcontrollers +# nanoMODBUS - A compact MODBUS RTU/TCP C library for microcontrollers -MODBUSino is a small C library that implements the Modbus protocol. It is especially useful in resource-constrained +nanoMODBUS is a small C library that implements the Modbus protocol. It is especially useful in resource-constrained system like microcontrollers. Its main features are: @@ -29,7 +29,7 @@ Its main features are: ## At a glance ```C -#include "modbusino.h" +#include "nanomodbus.h" #include "my_platform_stuff.h" #include @@ -42,37 +42,37 @@ int main(int argc, char* argv[]) { } // my_transport_read_byte, my_transport_write_byte and my_sleep are implemented by the user - mbsn_platform_conf platform_conf; - platform_conf.transport = MBSN_TRANSPORT_TCP; + nmbs_platform_conf platform_conf; + platform_conf.transport = NMBS_TRANSPORT_TCP; platform_conf.read_byte = my_transport_read_byte; platform_conf.write_byte = my_transport_write_byte; platform_conf.sleep = my_sleep_linux; platform_conf.arg = conn; // Passing our TCP connection handle to the read/write functions // Create the modbus client - mbsn_t mbsn; - mbsn_error err = mbsn_client_create(&mbsn, &platform_conf); - if (err != MBSN_ERROR_NONE) { + nmbs_t nmbs; + nmbs_error err = nmbs_client_create(&nmbs, &platform_conf); + if (err != NMBS_ERROR_NONE) { fprintf(stderr, "Error creating modbus client\n"); return 1; } // Set only the response timeout. Byte timeout will be handled by the TCP connection - mbsn_set_read_timeout(&mbsn, 1000); + nmbs_set_read_timeout(&nmbs, 1000); // Write 2 holding registers at address 26 uint16_t w_regs[2] = {123, 124}; - err = mbsn_write_multiple_registers(&mbsn, 26, 2, w_regs); - if (err != MBSN_ERROR_NONE) { - fprintf(stderr, "Error writing register at address 26 - %s", mbsn_strerror(err)); + err = nmbs_write_multiple_registers(&nmbs, 26, 2, w_regs); + if (err != NMBS_ERROR_NONE) { + fprintf(stderr, "Error writing register at address 26 - %s", nmbs_strerror(err)); return 1; } // Read 2 holding registers from address 26 uint16_t r_regs[2]; - err = mbsn_read_holding_registers(&mbsn, 26, 2, r_regs); - if (err != MBSN_ERROR_NONE) { - fprintf(stderr, "Error reading 2 holding registers at address 26 - %s\n", mbsn_strerror(err)); + err = nmbs_read_holding_registers(&nmbs, 26, 2, r_regs); + if (err != NMBS_ERROR_NONE) { + fprintf(stderr, "Error reading 2 holding registers at address 26 - %s\n", nmbs_strerror(err)); return 1; } @@ -85,15 +85,15 @@ int main(int argc, char* argv[]) { ## Installation -Just copy `modbusino.c` and `modbusino.h` inside your application codebase. +Just copy `nanomodbus.c` and `nanomodbus.h` inside your application codebase. ## API reference -API reference is available in the repository's [GitHub Pages](https://debevv.github.io/MODBUSino/modbusino_8h.html). +API reference is available in the repository's [GitHub Pages](https://debevv.github.io/nanoMODBUS/nanomodbus_8h.html). ## Platform functions -MODBUSino requires the implementation of 3 platform-specific functions, which are passed as function pointers when creating a +nanoMODBUS requires the implementation of 3 platform-specific functions, which are passed as function pointers when creating a client/server instance. ### Transport read/write @@ -124,13 +124,13 @@ This function should sleep for the specified amount of milliseconds. Platform functions can access arbitrary user data through their `void* arg` argument. The argument is useful, for example, to pass to read/write function the connection they should operate on. -Its initial value can be set inside the `mbsn_platform_conf` struct when creating the client/server instance, -and changed at any time via the `mbsn_set_platform_arg` API method. +Its initial value can be set inside the `nmbs_platform_conf` struct when creating the client/server instance, +and changed at any time via the `nmbs_set_platform_arg` API method. ## Platform endianness -MODBUSino will attempt to detect the endianness of the platform at build time. If the automatic detection fails, you can -manually set the endianness of the platform by defining either `MBSN_BIG_ENDIAN` or `MBSN_LITTLE_ENDIAN` in your build +nanoMODBUS will attempt to detect the endianness of the platform at build time. If the automatic detection fails, you can +manually set the endianness of the platform by defining either `NMBS_BIG_ENDIAN` or `NMBS_LITTLE_ENDIAN` in your build flags. ## Tests and examples @@ -145,6 +145,6 @@ make ## Misc -- To reduce code size, you can define `MBSN_STRERROR_DISABLED` to disable the code that converts `mbsn_error`s to +- To reduce code size, you can define `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 `MBSN_DEBUG` +- Debug prints about received and sent messages can be enabled by defining `NMBS_DEBUG` diff --git a/examples/client-tcp.c b/examples/client-tcp.c index 90a3f87..377edea 100644 --- a/examples/client-tcp.c +++ b/examples/client-tcp.c @@ -1,4 +1,4 @@ -#include "modbusino.h" +#include "nanomodbus.h" #include "platform.h" #include @@ -25,65 +25,65 @@ int main(int argc, char* argv[]) { return 1; } - mbsn_platform_conf platform_conf; - platform_conf.transport = MBSN_TRANSPORT_TCP; + nmbs_platform_conf platform_conf; + platform_conf.transport = NMBS_TRANSPORT_TCP; platform_conf.read_byte = read_byte_fd_linux; platform_conf.write_byte = write_byte_fd_linux; platform_conf.sleep = sleep_linux; platform_conf.arg = conn; // Passing our TCP connection handle to the read/write functions // Create the modbus client - mbsn_t mbsn; - mbsn_error err = mbsn_client_create(&mbsn, &platform_conf); - if (err != MBSN_ERROR_NONE) { + nmbs_t nmbs; + nmbs_error err = nmbs_client_create(&nmbs, &platform_conf); + if (err != NMBS_ERROR_NONE) { fprintf(stderr, "Error creating modbus client\n"); - if (!mbsn_error_is_exception(err)) + if (!nmbs_error_is_exception(err)) return 1; } // Set only the response timeout. Byte timeout will be handled by the TCP connection - mbsn_set_read_timeout(&mbsn, 1000); + nmbs_set_read_timeout(&nmbs, 1000); // Write 2 coils from address 64 - mbsn_bitfield coils; - mbsn_bitfield_write(coils, 0, 1); - mbsn_bitfield_write(coils, 1, 1); - err = mbsn_write_multiple_coils(&mbsn, 64, 2, coils); - if (err != MBSN_ERROR_NONE) { - fprintf(stderr, "Error writing coils at address 64 - %s\n", mbsn_strerror(err)); - if (!mbsn_error_is_exception(err)) + nmbs_bitfield coils; + nmbs_bitfield_write(coils, 0, 1); + nmbs_bitfield_write(coils, 1, 1); + err = nmbs_write_multiple_coils(&nmbs, 64, 2, coils); + if (err != NMBS_ERROR_NONE) { + fprintf(stderr, "Error writing coils at address 64 - %s\n", nmbs_strerror(err)); + if (!nmbs_error_is_exception(err)) return 1; } // Read 3 coils from address 64 - mbsn_bitfield_reset(coils); // Reset whole bitfield to zero - err = mbsn_read_coils(&mbsn, 64, 3, coils); - if (err != MBSN_ERROR_NONE) { - fprintf(stderr, "Error reading coils at address 64 - %s\n", mbsn_strerror(err)); - if (!mbsn_error_is_exception(err)) + nmbs_bitfield_reset(coils); // Reset whole bitfield to zero + err = nmbs_read_coils(&nmbs, 64, 3, coils); + if (err != NMBS_ERROR_NONE) { + fprintf(stderr, "Error reading coils at address 64 - %s\n", nmbs_strerror(err)); + if (!nmbs_error_is_exception(err)) return 1; } else { - printf("Coil at address 64 value: %d\n", mbsn_bitfield_read(coils, 0)); - printf("Coil at address 65 value: %d\n", mbsn_bitfield_read(coils, 1)); - printf("Coil at address 66 value: %d\n", mbsn_bitfield_read(coils, 2)); + printf("Coil at address 64 value: %d\n", nmbs_bitfield_read(coils, 0)); + printf("Coil at address 65 value: %d\n", nmbs_bitfield_read(coils, 1)); + printf("Coil at address 66 value: %d\n", nmbs_bitfield_read(coils, 2)); } // Write 2 holding registers at address 26 uint16_t w_regs[2] = {123, 124}; - err = mbsn_write_multiple_registers(&mbsn, 26, 2, w_regs); - if (err != MBSN_ERROR_NONE) { - fprintf(stderr, "Error writing register at address 26 - %s", mbsn_strerror(err)); - if (!mbsn_error_is_exception(err)) + err = nmbs_write_multiple_registers(&nmbs, 26, 2, w_regs); + if (err != NMBS_ERROR_NONE) { + fprintf(stderr, "Error writing register at address 26 - %s", nmbs_strerror(err)); + if (!nmbs_error_is_exception(err)) return 1; } // Read 2 holding registers from address 26 uint16_t r_regs[2]; - err = mbsn_read_holding_registers(&mbsn, 26, 2, r_regs); - if (err != MBSN_ERROR_NONE) { - fprintf(stderr, "Error reading 2 holding registers at address 26 - %s\n", mbsn_strerror(err)); - if (!mbsn_error_is_exception(err)) + err = nmbs_read_holding_registers(&nmbs, 26, 2, r_regs); + if (err != NMBS_ERROR_NONE) { + fprintf(stderr, "Error reading 2 holding registers at address 26 - %s\n", nmbs_strerror(err)); + if (!nmbs_error_is_exception(err)) return 1; } else { @@ -94,6 +94,6 @@ int main(int argc, char* argv[]) { // Close the TCP connection disconnect(conn); - // No need to destroy the mbsn instance, bye bye + // No need to destroy the nmbs instance, bye bye return 0; } diff --git a/examples/platform.h b/examples/platform.h index 7d7dede..e461ac1 100644 --- a/examples/platform.h +++ b/examples/platform.h @@ -1,4 +1,4 @@ -#include "modbusino.h" +#include "nanomodbus.h" #include #include #include diff --git a/examples/server-tcp.c b/examples/server-tcp.c index be68eb6..4688e61 100644 --- a/examples/server-tcp.c +++ b/examples/server-tcp.c @@ -1,4 +1,4 @@ -#include "modbusino.h" +#include "nanomodbus.h" #include "platform.h" #include @@ -23,58 +23,58 @@ #define COILS_ADDR_MAX 100 #define REGS_ADDR_MAX 32 -// A single mbsn_bitfield variable can keep 2000 coils -mbsn_bitfield server_coils = {0}; +// A single nmbs_bitfield variable can keep 2000 coils +nmbs_bitfield server_coils = {0}; uint16_t server_registers[REGS_ADDR_MAX] = {0}; -mbsn_error handle_read_coils(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) { +nmbs_error handle_read_coils(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out) { if (address + quantity > COILS_ADDR_MAX + 1) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; // Read our coils values into coils_out for (int i = 0; i < quantity; i++) { - bool value = mbsn_bitfield_read(server_coils, address + i); - mbsn_bitfield_write(coils_out, i, value); + bool value = nmbs_bitfield_read(server_coils, address + i); + nmbs_bitfield_write(coils_out, i, value); } - return MBSN_ERROR_NONE; + return NMBS_ERROR_NONE; } -mbsn_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, const mbsn_bitfield coils) { +nmbs_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils) { if (address + quantity > COILS_ADDR_MAX + 1) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; // Write coils values to our server_coils for (int i = 0; i < quantity; i++) { - mbsn_bitfield_write(server_coils, address + i, mbsn_bitfield_read(coils, i)); + nmbs_bitfield_write(server_coils, address + i, nmbs_bitfield_read(coils, i)); } - return MBSN_ERROR_NONE; + return NMBS_ERROR_NONE; } -mbsn_error handler_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out) { +nmbs_error handler_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out) { if (address + quantity > REGS_ADDR_MAX + 1) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; // Read our registers values into registers_out for (int i = 0; i < quantity; i++) registers_out[i] = server_registers[address + i]; - return MBSN_ERROR_NONE; + return NMBS_ERROR_NONE; } -mbsn_error handle_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers) { +nmbs_error handle_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* registers) { if (address + quantity > REGS_ADDR_MAX + 1) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; // Write registers values to our server_registers for (int i = 0; i < quantity; i++) server_registers[address + i] = registers[i]; - return MBSN_ERROR_NONE; + return NMBS_ERROR_NONE; } @@ -91,30 +91,30 @@ int main(int argc, char* argv[]) { return 1; } - mbsn_platform_conf platform_conf = {0}; - platform_conf.transport = MBSN_TRANSPORT_TCP; + nmbs_platform_conf platform_conf = {0}; + platform_conf.transport = NMBS_TRANSPORT_TCP; platform_conf.read_byte = read_byte_fd_linux; platform_conf.write_byte = write_byte_fd_linux; platform_conf.sleep = sleep_linux; platform_conf.arg = NULL; // We will set the arg (socket fd) later // These functions are defined in server.h - mbsn_callbacks callbacks = {0}; + nmbs_callbacks callbacks = {0}; callbacks.read_coils = handle_read_coils; callbacks.write_multiple_coils = handle_write_multiple_coils; callbacks.read_holding_registers = handler_read_holding_registers; callbacks.write_multiple_registers = handle_write_multiple_registers; // Create the modbus server. It's ok to set address_rtu to 0 since we are on TCP - mbsn_t mbsn; - mbsn_error err = mbsn_server_create(&mbsn, 0, &platform_conf, &callbacks); - if (err != MBSN_ERROR_NONE) { + nmbs_t nmbs; + nmbs_error err = nmbs_server_create(&nmbs, 0, &platform_conf, &callbacks); + if (err != NMBS_ERROR_NONE) { fprintf(stderr, "Error creating modbus server\n"); return 1; } // Set only the polling timeout. Byte timeout will be handled by the TCP connection - mbsn_set_read_timeout(&mbsn, 1000); + nmbs_set_read_timeout(&nmbs, 1000); printf("Modbus TCP server started\n"); @@ -126,11 +126,11 @@ int main(int argc, char* argv[]) { break; // Set the next connection handler used by the read/write platform functions - mbsn_set_platform_arg(&mbsn, conn); + nmbs_set_platform_arg(&nmbs, conn); - err = mbsn_server_poll(&mbsn); - if (err != MBSN_ERROR_NONE) { - fprintf(stderr, "Error polling modbus connection - %s\n", mbsn_strerror(err)); + err = nmbs_server_poll(&nmbs); + if (err != NMBS_ERROR_NONE) { + fprintf(stderr, "Error polling modbus connection - %s\n", nmbs_strerror(err)); // In a more complete example, we should handle this error by closing the connection from our side break; } @@ -139,6 +139,6 @@ int main(int argc, char* argv[]) { // Close the TCP server close_server(); - // No need to destroy the mbsn instance, bye bye + // No need to destroy the nmbs instance, bye bye return 0; } diff --git a/modbusino.c b/modbusino.c deleted file mode 100644 index a503f52..0000000 --- a/modbusino.c +++ /dev/null @@ -1,1244 +0,0 @@ -#include "modbusino.h" -#include -#include -#include - - -#ifdef MBSN_DEBUG -#include -#define DEBUG(...) printf(__VA_ARGS__) -#else -#define DEBUG(...) (void) (0) -#endif - -#if !defined(MBSN_BIG_ENDIAN) && !defined(MBSN_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 MBSN_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 MBSN_LITTLE_ENDIAN -#else -#error "Failed to automatically detect platform endianness. Please define either MBSN_BIG_ENDIAN or MBSN_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 MBSN_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(mbsn_t* mbsn) { - mbsn->msg.buf_idx = 0; -} - - -static void msg_state_reset(mbsn_t* mbsn) { - msg_buf_reset(mbsn); - mbsn->msg.unit_id = 0; - mbsn->msg.fc = 0; - mbsn->msg.transaction_id = 0; - mbsn->msg.broadcast = false; - mbsn->msg.ignored = 0; -} - - -static void msg_state_req(mbsn_t* mbsn, uint8_t fc) { - if (mbsn->current_tid == UINT16_MAX) - mbsn->current_tid = 1; - else - mbsn->current_tid++; - - msg_state_reset(mbsn); - mbsn->msg.unit_id = mbsn->dest_address_rtu; - mbsn->msg.fc = fc; - mbsn->msg.transaction_id = mbsn->current_tid; - if (mbsn->msg.unit_id == 0 && mbsn->platform.transport == MBSN_TRANSPORT_RTU) - mbsn->msg.broadcast = true; -} - - -int mbsn_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf) { - if (!mbsn) - return MBSN_ERROR_INVALID_ARGUMENT; - - memset(mbsn, 0, sizeof(mbsn_t)); - - mbsn->byte_timeout_ms = -1; - mbsn->read_timeout_ms = -1; - mbsn->byte_spacing_ms = 0; - - if (!platform_conf) - return MBSN_ERROR_INVALID_ARGUMENT; - - if (platform_conf->transport != MBSN_TRANSPORT_RTU && platform_conf->transport != MBSN_TRANSPORT_TCP) - return MBSN_ERROR_INVALID_ARGUMENT; - - if (!platform_conf->read_byte || !platform_conf->write_byte || !platform_conf->sleep) - return MBSN_ERROR_INVALID_ARGUMENT; - - mbsn->platform = *platform_conf; - - return MBSN_ERROR_NONE; -} - - -mbsn_error mbsn_client_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf) { - return mbsn_create(mbsn, platform_conf); -} - - -mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address_rtu, const mbsn_platform_conf* platform_conf, - const mbsn_callbacks* callbacks) { - if (platform_conf->transport == MBSN_TRANSPORT_RTU && address_rtu == 0) - return MBSN_ERROR_INVALID_ARGUMENT; - - mbsn_error ret = mbsn_create(mbsn, platform_conf); - if (ret != MBSN_ERROR_NONE) - return ret; - - mbsn->address_rtu = address_rtu; - mbsn->callbacks = *callbacks; - - return MBSN_ERROR_NONE; -} - - -void mbsn_set_read_timeout(mbsn_t* mbsn, int32_t timeout_ms) { - mbsn->read_timeout_ms = timeout_ms; -} - - -void mbsn_set_byte_timeout(mbsn_t* mbsn, int32_t timeout_ms) { - mbsn->byte_timeout_ms = timeout_ms; -} - - -void mbsn_set_byte_spacing(mbsn_t* mbsn, uint32_t spacing_ms) { - mbsn->byte_spacing_ms = spacing_ms; -} - - -void mbsn_set_destination_rtu_address(mbsn_t* mbsn, uint8_t address) { - mbsn->dest_address_rtu = address; -} - - -void mbsn_set_platform_arg(mbsn_t* mbsn, void* arg) { - mbsn->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 mbsn_error recv(mbsn_t* mbsn, uint32_t count) { - uint32_t r = 0; - while (r != count) { - int ret = mbsn->platform.read_byte(mbsn->msg.buf + mbsn->msg.buf_idx + r, mbsn->byte_timeout_ms, - mbsn->platform.arg); - if (ret == 0) { - return MBSN_ERROR_TIMEOUT; - } - else if (ret != 1) { - return MBSN_ERROR_TRANSPORT; - } - - r++; - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error send(mbsn_t* mbsn) { - uint32_t spacing_ms = 0; - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) - spacing_ms = mbsn->byte_spacing_ms; - - for (int i = 0; i < mbsn->msg.buf_idx; i++) { - if (spacing_ms != 0) - mbsn->platform.sleep(spacing_ms, mbsn->platform.arg); - - int ret = mbsn->platform.write_byte(mbsn->msg.buf[i], mbsn->read_timeout_ms, mbsn->platform.arg); - if (ret == 0) { - return MBSN_ERROR_TIMEOUT; - } - else if (ret != 1) { - return MBSN_ERROR_TRANSPORT; - } - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error recv_msg_footer(mbsn_t* mbsn) { - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - uint16_t crc = crc_calc(mbsn->msg.buf, mbsn->msg.buf_idx); - - mbsn_error err = recv(mbsn, 2); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t recv_crc = get_2(mbsn); - - if (recv_crc != crc) - return MBSN_ERROR_TRANSPORT; - } - - DEBUG("\n"); - - return MBSN_ERROR_NONE; -} - - -static mbsn_error recv_msg_header(mbsn_t* mbsn, bool* first_byte_received) { - // We wait for the read timeout here, just for the first message byte - int32_t old_byte_timeout = mbsn->byte_timeout_ms; - mbsn->byte_timeout_ms = mbsn->read_timeout_ms; - - msg_state_reset(mbsn); - - if (first_byte_received) - *first_byte_received = false; - - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - mbsn_error err = recv(mbsn, 1); - - mbsn->byte_timeout_ms = old_byte_timeout; - - if (err != MBSN_ERROR_NONE) - return err; - - if (first_byte_received) - *first_byte_received = true; - - mbsn->msg.unit_id = get_1(mbsn); - - err = recv(mbsn, 1); - if (err != MBSN_ERROR_NONE) - return err; - - mbsn->msg.fc = get_1(mbsn); - } - else if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) { - mbsn_error err = recv(mbsn, 1); - - mbsn->byte_timeout_ms = old_byte_timeout; - - if (err != MBSN_ERROR_NONE) - return err; - - if (first_byte_received) - *first_byte_received = true; - - // Advance buf_idx - discard_1(mbsn); - - err = recv(mbsn, 7); - if (err != MBSN_ERROR_NONE) - return err; - - // Starting over - msg_buf_reset(mbsn); - - mbsn->msg.transaction_id = get_2(mbsn); - uint16_t protocol_id = get_2(mbsn); - uint16_t length = get_2(mbsn); // We should actually check the length of the request against this value - mbsn->msg.unit_id = get_1(mbsn); - mbsn->msg.fc = get_1(mbsn); - - if (protocol_id != 0) - return MBSN_ERROR_TRANSPORT; - - if (length > 255) - return MBSN_ERROR_TRANSPORT; - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error recv_req_header(mbsn_t* mbsn, bool* first_byte_received) { - mbsn_error err = recv_msg_header(mbsn, first_byte_received); - if (err != MBSN_ERROR_NONE) - return err; - - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - // Check if request is for us - if (mbsn->msg.unit_id == MBSN_BROADCAST_ADDRESS) - mbsn->msg.broadcast = true; - else if (mbsn->msg.unit_id != mbsn->address_rtu) - mbsn->msg.ignored = true; - else - mbsn->msg.ignored = false; - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error recv_res_header(mbsn_t* mbsn) { - uint16_t req_transaction_id = mbsn->msg.transaction_id; - uint8_t req_fc = mbsn->msg.fc; - - mbsn_error err = recv_msg_header(mbsn, NULL); - if (err != MBSN_ERROR_NONE) - return err; - - if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) { - if (mbsn->msg.transaction_id != req_transaction_id) - return MBSN_ERROR_INVALID_RESPONSE; - } - - if (mbsn->msg.ignored) - return MBSN_ERROR_INVALID_RESPONSE; - - if (mbsn->msg.fc != req_fc) { - if (mbsn->msg.fc - 0x80 == req_fc) { - err = recv(mbsn, 1); - if (err != MBSN_ERROR_NONE) - return err; - - uint8_t exception = get_1(mbsn); - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (exception < 1 || exception > 4) - return MBSN_ERROR_INVALID_RESPONSE; - else { - DEBUG("exception %d\n", exception); - return exception; - } - } - else { - return MBSN_ERROR_INVALID_RESPONSE; - } - } - - DEBUG("MBSN res <- fc %d\t", mbsn->msg.fc); - - return MBSN_ERROR_NONE; -} - - -static void send_msg_header(mbsn_t* mbsn, uint16_t data_length) { - msg_buf_reset(mbsn); - - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - put_1(mbsn, mbsn->msg.unit_id); - } - else if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) { - put_2(mbsn, mbsn->msg.transaction_id); - put_2(mbsn, 0); - put_2(mbsn, (uint16_t) (1 + 1 + data_length)); - put_1(mbsn, mbsn->msg.unit_id); - } - - put_1(mbsn, mbsn->msg.fc); -} - - -static mbsn_error send_msg_footer(mbsn_t* mbsn) { - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - uint16_t crc = crc_calc(mbsn->msg.buf, mbsn->msg.buf_idx); - put_2(mbsn, crc); - } - - mbsn_error err = send(mbsn); - - DEBUG("\n"); - - return err; -} - - -static void send_req_header(mbsn_t* mbsn, uint16_t data_length) { - send_msg_header(mbsn, data_length); - DEBUG("MBSN req -> fc %d\t", mbsn->msg.fc); -} - - -static void send_res_header(mbsn_t* mbsn, uint16_t data_length) { - send_msg_header(mbsn, data_length); - DEBUG("MBSN res -> fc %d\t", mbsn->msg.fc); -} - - -static mbsn_error handle_exception(mbsn_t* mbsn, uint8_t exception) { - mbsn->msg.fc += 0x80; - send_msg_header(mbsn, 1); - put_1(mbsn, exception); - - DEBUG("MBSN res -> exception %d\n", exception); - - return send_msg_footer(mbsn); -} - - -static mbsn_error handle_read_discrete(mbsn_t* mbsn, mbsn_error (*callback)(uint16_t, uint16_t, mbsn_bitfield)) { - mbsn_error err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address = get_2(mbsn); - uint16_t quantity = get_2(mbsn); - - DEBUG("a %d\tq %d", address, quantity); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.ignored) { - if (quantity < 1 || quantity > 2000) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - if (callback) { - mbsn_bitfield bf = {0}; - err = callback(address, quantity, bf); - if (err != MBSN_ERROR_NONE) { - if (mbsn_error_is_exception(err)) - return handle_exception(mbsn, err); - else - return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - } - - if (!mbsn->msg.broadcast) { - uint8_t discrete_bytes = (quantity / 8) + 1; - send_res_header(mbsn, discrete_bytes); - - put_1(mbsn, discrete_bytes); - - DEBUG("b %d\t", discrete_bytes); - - DEBUG("coils "); - for (int i = 0; i < discrete_bytes; i++) { - put_1(mbsn, bf[i]); - DEBUG("%d", bf[i]); - } - - err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - } - } - else { - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); - } - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error handle_read_registers(mbsn_t* mbsn, mbsn_error (*callback)(uint16_t, uint16_t, uint16_t*)) { - mbsn_error err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address = get_2(mbsn); - uint16_t quantity = get_2(mbsn); - - DEBUG("a %d\tq %d", address, quantity); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.ignored) { - if (quantity < 1 || quantity > 125) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - if (callback) { - uint16_t regs[125] = {0}; - err = callback(address, quantity, regs); - if (err != MBSN_ERROR_NONE) { - if (mbsn_error_is_exception(err)) - return handle_exception(mbsn, err); - else - return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - } - - if (!mbsn->msg.broadcast) { - uint8_t regs_bytes = quantity * 2; - send_res_header(mbsn, regs_bytes); - - put_1(mbsn, regs_bytes); - - DEBUG("b %d\t", regs_bytes); - - DEBUG("regs "); - for (int i = 0; i < quantity; i++) { - put_2(mbsn, regs[i]); - DEBUG("%d", regs[i]); - } - - err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - } - } - else { - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); - } - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error handle_read_coils(mbsn_t* mbsn) { - return handle_read_discrete(mbsn, mbsn->callbacks.read_coils); -} - - -static mbsn_error handle_read_discrete_inputs(mbsn_t* mbsn) { - return handle_read_discrete(mbsn, mbsn->callbacks.read_discrete_inputs); -} - - -static mbsn_error handle_read_holding_registers(mbsn_t* mbsn) { - return handle_read_registers(mbsn, mbsn->callbacks.read_holding_registers); -} - - -static mbsn_error handle_read_input_registers(mbsn_t* mbsn) { - return handle_read_registers(mbsn, mbsn->callbacks.read_input_registers); -} - - -static mbsn_error handle_write_single_coil(mbsn_t* mbsn) { - mbsn_error err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address = get_2(mbsn); - uint16_t value = get_2(mbsn); - - DEBUG("a %d\tvalue %d", address, value); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.ignored) { - if (mbsn->callbacks.write_single_coil) { - if (value != 0 && value != 0xFF00) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - err = mbsn->callbacks.write_single_coil(address, value == 0 ? false : true); - if (err != MBSN_ERROR_NONE) { - if (mbsn_error_is_exception(err)) - return handle_exception(mbsn, err); - else - return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - } - - if (!mbsn->msg.broadcast) { - send_res_header(mbsn, 4); - - put_2(mbsn, address); - put_2(mbsn, value); - DEBUG("a %d\tvalue %d", address, value); - - err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - } - } - else { - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); - } - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error handle_write_single_register(mbsn_t* mbsn) { - mbsn_error err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address = get_2(mbsn); - uint16_t value = get_2(mbsn); - - DEBUG("a %d\tvalue %d", address, value); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.ignored) { - if (mbsn->callbacks.write_single_register) { - err = mbsn->callbacks.write_single_register(address, value); - if (err != MBSN_ERROR_NONE) { - if (mbsn_error_is_exception(err)) - return handle_exception(mbsn, err); - else - return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - } - - if (!mbsn->msg.broadcast) { - send_res_header(mbsn, 4); - - put_2(mbsn, address); - put_2(mbsn, value); - DEBUG("a %d\tvalue %d", address, value); - - err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - } - } - else { - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); - } - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error handle_write_multiple_coils(mbsn_t* mbsn) { - mbsn_error err = recv(mbsn, 5); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address = get_2(mbsn); - uint16_t quantity = get_2(mbsn); - uint8_t coils_bytes = get_1(mbsn); - - DEBUG("a %d\tq %d\tb %d\tcoils ", address, quantity, coils_bytes); - - err = recv(mbsn, coils_bytes); - if (err != MBSN_ERROR_NONE) - return err; - - mbsn_bitfield coils; - for (int i = 0; i < coils_bytes; i++) { - coils[i] = get_1(mbsn); - DEBUG("%d ", coils[i]); - } - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.ignored) { - if (quantity < 1 || quantity > 0x07B0) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - if (coils_bytes == 0) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if ((quantity / 8) + 1 != coils_bytes) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if (mbsn->callbacks.write_multiple_coils) { - err = mbsn->callbacks.write_multiple_coils(address, quantity, coils); - if (err != MBSN_ERROR_NONE) { - if (mbsn_error_is_exception(err)) - return handle_exception(mbsn, err); - else - return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - } - - if (!mbsn->msg.broadcast) { - send_res_header(mbsn, 4); - - put_2(mbsn, address); - put_2(mbsn, quantity); - DEBUG("a %d\tq %d", address, quantity); - - err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - } - } - else { - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); - } - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error handle_write_multiple_registers(mbsn_t* mbsn) { - mbsn_error err = recv(mbsn, 5); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address = get_2(mbsn); - uint16_t quantity = get_2(mbsn); - uint8_t registers_bytes = get_1(mbsn); - - DEBUG("a %d\tq %d\tb %d\tregs ", address, quantity, registers_bytes); - - err = recv(mbsn, registers_bytes); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t registers[0x007B]; - for (int i = 0; i < registers_bytes / 2; i++) { - registers[i] = get_2(mbsn); - DEBUG("%d ", registers[i]); - } - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.ignored) { - if (quantity < 1 || quantity > 0x007B) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - if (registers_bytes == 0) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if (registers_bytes != quantity * 2) - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - if (mbsn->callbacks.write_multiple_registers) { - err = mbsn->callbacks.write_multiple_registers(address, quantity, registers); - if (err != MBSN_ERROR_NONE) { - if (mbsn_error_is_exception(err)) - return handle_exception(mbsn, err); - else - return handle_exception(mbsn, MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - } - - if (!mbsn->msg.broadcast) { - send_res_header(mbsn, 4); - - put_2(mbsn, address); - put_2(mbsn, quantity); - DEBUG("a %d\tq %d", address, quantity); - - err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - } - } - else { - return handle_exception(mbsn, MBSN_EXCEPTION_ILLEGAL_FUNCTION); - } - } - - return MBSN_ERROR_NONE; -} - - -static mbsn_error handle_req_fc(mbsn_t* mbsn) { - DEBUG("fc %d\t", mbsn->msg.fc); - - mbsn_error err; - switch (mbsn->msg.fc) { - case 1: - err = handle_read_coils(mbsn); - break; - - case 2: - err = handle_read_discrete_inputs(mbsn); - break; - - case 3: - err = handle_read_holding_registers(mbsn); - break; - - case 4: - err = handle_read_input_registers(mbsn); - break; - - case 5: - err = handle_write_single_coil(mbsn); - break; - - case 6: - err = handle_write_single_register(mbsn); - break; - - case 15: - err = handle_write_multiple_coils(mbsn); - break; - - case 16: - err = handle_write_multiple_registers(mbsn); - break; - - default: - err = MBSN_EXCEPTION_ILLEGAL_FUNCTION; - } - - return err; -} - - -mbsn_error mbsn_server_poll(mbsn_t* mbsn) { - msg_state_reset(mbsn); - - bool first_byte_received = false; - mbsn_error err = recv_req_header(mbsn, &first_byte_received); - if (err != MBSN_ERROR_NONE) { - if (!first_byte_received && err == MBSN_ERROR_TIMEOUT) - return MBSN_ERROR_NONE; - else - return err; - } - -#ifdef MBSN_DEBUG - printf("MBSN req <- "); - if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) { - if (mbsn->msg.broadcast) - printf("broadcast\t"); - - printf("client_id %d\t", mbsn->msg.unit_id); - } -#endif - - err = handle_req_fc(mbsn); - if (err != MBSN_ERROR_NONE) { - if (!mbsn_error_is_exception(err)) - return err; - } - - return err; -} - - -static mbsn_error read_discrete(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint16_t quantity, mbsn_bitfield values) { - if (quantity < 1 || quantity > 2000) - return MBSN_ERROR_INVALID_ARGUMENT; - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return MBSN_ERROR_INVALID_ARGUMENT; - - msg_state_req(mbsn, fc); - send_req_header(mbsn, 4); - - put_2(mbsn, address); - put_2(mbsn, quantity); - - DEBUG("a %d\tq %d", address, quantity); - - mbsn_error err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv_res_header(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv(mbsn, 1); - if (err != MBSN_ERROR_NONE) - return err; - - uint8_t coils_bytes = get_1(mbsn); - DEBUG("b %d\t", coils_bytes); - - err = recv(mbsn, coils_bytes); - if (err != MBSN_ERROR_NONE) - return err; - - DEBUG("coils "); - for (int i = 0; i < coils_bytes; i++) { - values[i] = get_1(mbsn); - DEBUG("%d", values[i]); - } - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - return MBSN_ERROR_NONE; -} - - -mbsn_error mbsn_read_coils(mbsn_t* mbsn, uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) { - return read_discrete(mbsn, 1, address, quantity, coils_out); -} - - -mbsn_error mbsn_read_discrete_inputs(mbsn_t* mbsn, uint16_t address, uint16_t quantity, mbsn_bitfield inputs_out) { - return read_discrete(mbsn, 2, address, quantity, inputs_out); -} - - -static mbsn_error read_registers(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint16_t quantity, uint16_t* registers) { - if (quantity < 1 || quantity > 125) - return MBSN_ERROR_INVALID_ARGUMENT; - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return MBSN_ERROR_INVALID_ARGUMENT; - - msg_state_req(mbsn, fc); - send_req_header(mbsn, 4); - - put_2(mbsn, address); - put_2(mbsn, quantity); - - DEBUG("a %d\tq %d ", address, quantity); - - mbsn_error err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv_res_header(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv(mbsn, 1); - if (err != MBSN_ERROR_NONE) - return err; - - uint8_t registers_bytes = get_1(mbsn); - DEBUG("b %d\t", registers_bytes); - - err = recv(mbsn, registers_bytes); - if (err != MBSN_ERROR_NONE) - return err; - - DEBUG("regs "); - for (int i = 0; i < registers_bytes / 2; i++) { - registers[i] = get_2(mbsn); - DEBUG("%d", registers[i]); - } - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (registers_bytes != quantity * 2) - return MBSN_ERROR_INVALID_RESPONSE; - - return MBSN_ERROR_NONE; -} - - -mbsn_error mbsn_read_holding_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, uint16_t* registers_out) { - return read_registers(mbsn, 3, address, quantity, registers_out); -} - - -mbsn_error mbsn_read_input_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, uint16_t* registers_out) { - return read_registers(mbsn, 4, address, quantity, registers_out); -} - - -mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) { - msg_state_req(mbsn, 5); - send_req_header(mbsn, 4); - - uint16_t value_req = value ? 0xFF00 : 0; - - put_2(mbsn, address); - put_2(mbsn, value_req); - - DEBUG("a %d\tvalue %d ", address, value_req); - - mbsn_error err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.broadcast) { - err = recv_res_header(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address_res = get_2(mbsn); - uint16_t value_res = get_2(mbsn); - - DEBUG("a %d\tvalue %d", address, value_res); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; - - if (value_res != value_req) - return MBSN_ERROR_INVALID_RESPONSE; - } - - return MBSN_ERROR_NONE; -} - - -mbsn_error mbsn_write_single_register(mbsn_t* mbsn, uint16_t address, uint16_t value) { - msg_state_req(mbsn, 6); - send_req_header(mbsn, 4); - - put_2(mbsn, address); - put_2(mbsn, value); - - DEBUG("a %d\tvalue %d", address, value); - - mbsn_error err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.broadcast) { - err = recv_res_header(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address_res = get_2(mbsn); - uint16_t value_res = get_2(mbsn); - DEBUG("a %d\tvalue %d ", address, value_res); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; - - if (value_res != value) - return MBSN_ERROR_INVALID_RESPONSE; - } - - return MBSN_ERROR_NONE; -} - - -mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t quantity, const mbsn_bitfield coils) { - if (quantity < 1 || quantity > 0x07B0) - return MBSN_ERROR_INVALID_ARGUMENT; - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return MBSN_ERROR_INVALID_ARGUMENT; - - uint8_t coils_bytes = (quantity / 8) + 1; - - msg_state_req(mbsn, 15); - send_req_header(mbsn, 5 + coils_bytes); - - put_2(mbsn, address); - put_2(mbsn, quantity); - put_1(mbsn, 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(mbsn, coils[i]); - DEBUG("%d ", coils[i]); - } - - mbsn_error err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.broadcast) { - err = recv_res_header(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address_res = get_2(mbsn); - uint16_t quantity_res = get_2(mbsn); - DEBUG("a %d\tq %d", address_res, quantity_res); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; - - if (quantity_res != quantity) - return MBSN_ERROR_INVALID_RESPONSE; - } - - return MBSN_ERROR_NONE; -} - - -mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, const uint16_t* registers) { - if (quantity < 1 || quantity > 0x007B) - return MBSN_ERROR_INVALID_ARGUMENT; - - if ((uint32_t) address + (uint32_t) quantity > 0xFFFF + 1) - return MBSN_ERROR_INVALID_ARGUMENT; - - uint8_t registers_bytes = quantity * 2; - - msg_state_req(mbsn, 16); - send_req_header(mbsn, 5 + registers_bytes); - - put_2(mbsn, address); - put_2(mbsn, quantity); - put_1(mbsn, 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(mbsn, registers[i]); - DEBUG("%d ", registers[i]); - } - - mbsn_error err = send_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (!mbsn->msg.broadcast) { - err = recv_res_header(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv(mbsn, 4); - if (err != MBSN_ERROR_NONE) - return err; - - uint16_t address_res = get_2(mbsn); - uint16_t quantity_res = get_2(mbsn); - DEBUG("a %d\tq %d", address_res, quantity_res); - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - if (address_res != address) - return MBSN_ERROR_INVALID_RESPONSE; - - if (quantity_res != quantity) - return MBSN_ERROR_INVALID_RESPONSE; - } - - return MBSN_ERROR_NONE; -} - - -mbsn_error mbsn_send_raw_pdu(mbsn_t* mbsn, uint8_t fc, const void* data, uint32_t data_len) { - msg_state_req(mbsn, fc); - send_msg_header(mbsn, data_len); - - DEBUG("raw "); - for (uint32_t i = 0; i < data_len; i++) { - put_1(mbsn, ((uint8_t*) (data))[i]); - DEBUG("%d ", ((uint8_t*) (data))[i]); - } - - return send_msg_footer(mbsn); -} - - -mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t data_out_len) { - mbsn_error err = recv_res_header(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - err = recv(mbsn, data_out_len); - if (err != MBSN_ERROR_NONE) - return err; - - for (uint32_t i = 0; i < data_out_len; i++) { - ((uint8_t*) (data_out))[i] = get_1(mbsn); - } - - err = recv_msg_footer(mbsn); - if (err != MBSN_ERROR_NONE) - return err; - - return MBSN_ERROR_NONE; -} - - -#ifndef MBSN_STRERROR_DISABLED -const char* mbsn_strerror(mbsn_error error) { - switch (error) { - case MBSN_ERROR_TRANSPORT: - return "transport error"; - - case MBSN_ERROR_TIMEOUT: - return "timeout"; - - case MBSN_ERROR_INVALID_RESPONSE: - return "invalid response received"; - - case MBSN_ERROR_INVALID_ARGUMENT: - return "invalid argument provided"; - - case MBSN_ERROR_NONE: - return "no error"; - - case MBSN_EXCEPTION_ILLEGAL_FUNCTION: - return "modbus exception 1: illegal function"; - - case MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS: - return "modbus exception 2: illegal data address"; - - case MBSN_EXCEPTION_ILLEGAL_DATA_VALUE: - return "modbus exception 3: data value"; - - case MBSN_EXCEPTION_SERVER_DEVICE_FAILURE: - return "modbus exception 4: server device failure"; - - default: - return "unknown error"; - } -} -#endif diff --git a/modbusino.h b/modbusino.h deleted file mode 100644 index 4c6e342..0000000 --- a/modbusino.h +++ /dev/null @@ -1,320 +0,0 @@ -/** @file */ - -/*! \mainpage MODBUSino - A compact MODBUS RTU/TCP C library for microcontrollers - * MODBUSino is a small C library that implements the Modbus protocol. It is especially useful in resource-constrained - * system like microcontrollers. - * - * GtiHub: https://github.com/debevv/MODBUSino - * - * API reference: \link modbusino.h \endlink - * - */ - -#ifndef MODBUSINO_H -#define MODBUSINO_H - -#include -#include -#include - -/** - * MODBUSino errors. - * Values <= 0 are library errors, > 0 are modbus exceptions. - */ -typedef enum mbsn_error { - // Library errors - MBSN_ERROR_TRANSPORT = -4, /**< Transport error */ - MBSN_ERROR_TIMEOUT = -3, /**< Read/write timeout occurred */ - MBSN_ERROR_INVALID_RESPONSE = -2, /**< Received invalid response from server */ - MBSN_ERROR_INVALID_ARGUMENT = -1, /**< Invalid argument provided */ - MBSN_ERROR_NONE = 0, /**< No error */ - - // Modbus exceptions - MBSN_EXCEPTION_ILLEGAL_FUNCTION = 1, /**< Modbus exception 1 */ - MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2, /**< Modbus exception 2 */ - MBSN_EXCEPTION_ILLEGAL_DATA_VALUE = 3, /**< Modbus exception 3 */ - MBSN_EXCEPTION_SERVER_DEVICE_FAILURE = 4, /**< Modbus exception 4 */ -} mbsn_error; - -/** - * Return whether the mbsn_error is a modbus exception - * @e mbsn_error to check - */ -#define mbsn_error_is_exception(e) ((e) > 0 && (e) < 5) - - -/** - * Bitfield consisting of 2000 coils/discrete inputs - */ -typedef uint8_t mbsn_bitfield[250]; - -/** - * Read a bit from the mbsn_bitfield bf at position b - */ -#define mbsn_bitfield_read(bf, b) ((bool) ((bf)[(b) / 8] & (0x1 << ((b) % 8)))) - -/** - * Write value v to the mbsn_bitfield bf at position b - */ -#define mbsn_bitfield_write(bf, b, v) \ - (((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & ~(0x1 << ((b) % 8))))) - -/** - * Reset (zero) the whole bitfield - */ -#define mbsn_bitfield_reset(bf) memset(bf, 0, sizeof(mbsn_bitfield)) - - -/** - * Modbus transport type. - */ -typedef enum mbsn_transport { - MBSN_TRANSPORT_RTU = 1, - MBSN_TRANSPORT_TCP = 2, -} mbsn_transport; - - -/** - * MODBUSino platform configuration struct. - * Passed to mbsn_server_create() and mbsn_client_create(). - * - * read_byte() and write_byte() are the platform-specific methods that read/write data to/from a serial port or a TCP connection. - * Both methods should block until the requested byte is read/written. - * If your implementation uses a read/write timeout, and the timeout expires, the methods should return 0. - * Their return values should be: - * - `1` in case of success - * - `0` if no data is available immediately or after an internal timeout expiration - * - `-1` in case of error - * - * sleep() is the platform-specific method to pause for a certain amount of milliseconds. - * - * These methods accept a pointer to arbitrary user-data, which is the arg member of this struct. - * After the creation of an instance it can be changed with mbsn_set_platform_arg(). - */ -typedef struct mbsn_platform_conf { - mbsn_transport transport; /*!< Transport type */ - int (*read_byte)(uint8_t* b, int32_t, void* arg); /*!< Byte read transport function pointer */ - int (*write_byte)(uint8_t b, int32_t, void* arg); /*!< Byte write transport function pointer */ - void (*sleep)(uint32_t milliseconds, void* arg); /*!< Sleep function pointer */ - void* arg; /*!< User data, will be passed to functions above */ -} mbsn_platform_conf; - - -/** - * Modbus server request callbacks. Passed to mbsn_server_create(). - */ -typedef struct mbsn_callbacks { - mbsn_error (*read_coils)(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out); - mbsn_error (*read_discrete_inputs)(uint16_t address, uint16_t quantity, mbsn_bitfield inputs_out); - mbsn_error (*read_holding_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out); - mbsn_error (*read_input_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out); - mbsn_error (*write_single_coil)(uint16_t address, bool value); - mbsn_error (*write_single_register)(uint16_t address, uint16_t value); - mbsn_error (*write_multiple_coils)(uint16_t address, uint16_t quantity, const mbsn_bitfield coils); - mbsn_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, const uint16_t* registers); -} mbsn_callbacks; - - -/** - * MODBUSino client/server instance type. All struct members are to be considered private, it is not advisable to read/write them directly. - */ -typedef struct mbsn_t { - struct { - uint8_t buf[260]; - uint16_t buf_idx; - - uint8_t unit_id; - uint8_t fc; - uint16_t transaction_id; - bool broadcast; - bool ignored; - } msg; - - mbsn_callbacks callbacks; - - int32_t byte_timeout_ms; - int32_t read_timeout_ms; - uint32_t byte_spacing_ms; - - mbsn_platform_conf platform; - - uint8_t address_rtu; - uint8_t dest_address_rtu; - uint16_t current_tid; -} mbsn_t; - -/** - * Modbus broadcast address. Can be passed to mbsn_set_destination_rtu_address(). - */ -static const uint8_t MBSN_BROADCAST_ADDRESS = 0; - - -/** Create a new Modbus client. - * @param mbsn pointer to the mbsn_t instance where the client will be created. - * @param platform_conf mbsn_platform_conf struct with platform configuration. - * -* @return MBSN_ERROR_NONE if successful, MBSN_ERROR_INVALID_ARGUMENT otherwise. - */ -mbsn_error mbsn_client_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf); - -/** Create a new Modbus server. - * @param mbsn pointer to the mbsn_t instance where the client will be created. - * @param address_rtu RTU address of this server. Can be 0 if transport is not RTU. - * @param platform_conf mbsn_platform_conf struct with platform configuration. - * @param callbacks mbsn_callbacks struct with server request callbacks. - * - * @return MBSN_ERROR_NONE if successful, MBSN_ERROR_INVALID_ARGUMENT otherwise. - */ -mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address_rtu, const mbsn_platform_conf* platform_conf, - const mbsn_callbacks* callbacks); - -/** Set the request/response timeout. - * If the target instance is a server, sets the timeout of the mbsn_server_poll() function. - * If the target instance is a client, sets the response timeout after sending a request. In case of timeout, the called method will return MBSN_ERROR_TIMEOUT. - * @param mbsn pointer to the mbsn_t instance - * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled. - */ -void mbsn_set_read_timeout(mbsn_t* mbsn, int32_t timeout_ms); - -/** Set the timeout between the reception of two consecutive bytes. - * @param mbsn pointer to the mbsn_t instance - * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled. - */ -void mbsn_set_byte_timeout(mbsn_t* mbsn, int32_t timeout_ms); - -/** Set the spacing between two sent bytes. This value is ignored when transport is not RTU. - * @param mbsn pointer to the mbsn_t instance - * @param timeout_ms time spacing in milliseconds. - */ -void mbsn_set_byte_spacing(mbsn_t* mbsn, uint32_t spacing_ms); - -/** Set the pointer to user data argument passed to platform functions. - * @param mbsn pointer to the mbsn_t instance - * @param arg user data argument - */ -void mbsn_set_platform_arg(mbsn_t* mbsn, void* arg); - -/** Set the recipient server address of the next request on RTU transport. - * @param mbsn pointer to the mbsn_t instance - * @param address server address - */ -void mbsn_set_destination_rtu_address(mbsn_t* mbsn, uint8_t address); - -/** Handle incoming requests to the server. - * This function should be called in a loop in order to serve any incoming request. Its maximum duration, in case of no - * received request, is the value set with mbsn_set_read_timeout() (unless set to < 0). - * @param mbsn pointer to the mbsn_t instance - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_server_poll(mbsn_t* mbsn); - -/** Send a FC 01 (0x01) Read Coils request - * @param mbsn pointer to the mbsn_t instance - * @param address starting address - * @param quantity quantity of coils - * @param coils_out mbsn_bitfield where the coils will be stored - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_read_coils(mbsn_t* mbsn, uint16_t address, uint16_t quantity, mbsn_bitfield coils_out); - -/** Send a FC 02 (0x02) Read Discrete Inputs request - * @param mbsn pointer to the mbsn_t instance - * @param address starting address - * @param quantity quantity of inputs - * @param inputs_out mbsn_bitfield where the discrete inputs will be stored - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_read_discrete_inputs(mbsn_t* mbsn, uint16_t address, uint16_t quantity, mbsn_bitfield inputs_out); - -/** Send a FC 03 (0x03) Read Holding Registers request - * @param mbsn pointer to the mbsn_t instance - * @param address starting address - * @param quantity quantity of registers - * @param registers_out array where the registers will be stored - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_read_holding_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, uint16_t* registers_out); - -/** Send a FC 04 (0x04) Read Input Registers request - * @param mbsn pointer to the mbsn_t instance - * @param address starting address - * @param quantity quantity of registers - * @param registers_out array where the registers will be stored - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_read_input_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, uint16_t* registers_out); - -/** Send a FC 05 (0x05) Write Single Coil request - * @param mbsn pointer to the mbsn_t instance - * @param address coil address - * @param value coil value - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value); - -/** Send a FC 06 (0x06) Write Single Register request - * @param mbsn pointer to the mbsn_t instance - * @param address register address - * @param value register value - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_write_single_register(mbsn_t* mbsn, uint16_t address, uint16_t value); - -/** Send a FC 15 (0x0F) Write Multiple Coils - * @param mbsn pointer to the mbsn_t instance - * @param address starting address - * @param quantity quantity of coils - * @param coils bitfield of coils values - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t quantity, const mbsn_bitfield coils); - -/** Send a FC 16 (0x10) Write Multiple Registers - * @param mbsn pointer to the mbsn_t instance - * @param address starting address - * @param quantity quantity of registers - * @param registers array of registers values - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, const uint16_t* registers); - -/** Send a raw Modbus PDU. - * CRC on RTU will be calculated and sent by this function. - * @param mbsn pointer to the mbsn_t instance - * @param fc request function code - * @param data request data. It's up to the caller to convert this data to network byte order - * @param data_len length of the data parameter - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_send_raw_pdu(mbsn_t* mbsn, uint8_t fc, const void* data, uint32_t data_len); - -/** Receive a raw response Modbus PDU. - * @param mbsn pointer to the mbsn_t instance - * @param data_out response data. It's up to the caller to convert this data to host byte order. - * @param data_out_len length of the data_out parameter - * - * @return MBSN_ERROR_NONE if successful, other errors otherwise. - */ -mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t data_out_len); - -#ifndef MBSN_STRERROR_DISABLED -/** Convert a mbsn_error to string - * @param error error to be converted - * - * @return string representation of the error - */ -const char* mbsn_strerror(mbsn_error error); -#endif - - -#endif //MODBUSINO_H diff --git a/nanomodbus.c b/nanomodbus.c new file mode 100644 index 0000000..db5c50b --- /dev/null +++ b/nanomodbus.c @@ -0,0 +1,1244 @@ +#include "nanomodbus.h" +#include +#include +#include + + +#ifdef NMBS_DEBUG +#include +#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 diff --git a/nanomodbus.h b/nanomodbus.h new file mode 100644 index 0000000..4bd938f --- /dev/null +++ b/nanomodbus.h @@ -0,0 +1,320 @@ +/** @file */ + +/*! \mainpage nanoMODBUS - A compact MODBUS RTU/TCP C library for microcontrollers + * nanoMODBUS is a small C library that implements the Modbus protocol. It is especially useful in resource-constrained + * system like microcontrollers. + * + * GtiHub: https://github.com/debevv/nanoMODBUS + * + * API reference: \link nanomodbus.h \endlink + * + */ + +#ifndef NANOMODBUS_H +#define NANOMODBUS_H + +#include +#include +#include + +/** + * nanoMODBUS errors. + * Values <= 0 are library errors, > 0 are modbus exceptions. + */ +typedef enum nmbs_error { + // Library errors + NMBS_ERROR_TRANSPORT = -4, /**< Transport error */ + NMBS_ERROR_TIMEOUT = -3, /**< Read/write timeout occurred */ + NMBS_ERROR_INVALID_RESPONSE = -2, /**< Received invalid response from server */ + NMBS_ERROR_INVALID_ARGUMENT = -1, /**< Invalid argument provided */ + NMBS_ERROR_NONE = 0, /**< No error */ + + // Modbus exceptions + NMBS_EXCEPTION_ILLEGAL_FUNCTION = 1, /**< Modbus exception 1 */ + NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2, /**< Modbus exception 2 */ + NMBS_EXCEPTION_ILLEGAL_DATA_VALUE = 3, /**< Modbus exception 3 */ + NMBS_EXCEPTION_SERVER_DEVICE_FAILURE = 4, /**< Modbus exception 4 */ +} nmbs_error; + +/** + * Return whether the nmbs_error is a modbus exception + * @e nmbs_error to check + */ +#define nmbs_error_is_exception(e) ((e) > 0 && (e) < 5) + + +/** + * Bitfield consisting of 2000 coils/discrete inputs + */ +typedef uint8_t nmbs_bitfield[250]; + +/** + * Read a bit from the nmbs_bitfield bf at position b + */ +#define nmbs_bitfield_read(bf, b) ((bool) ((bf)[(b) / 8] & (0x1 << ((b) % 8)))) + +/** + * Write value v to the nmbs_bitfield bf at position b + */ +#define nmbs_bitfield_write(bf, b, v) \ + (((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & ~(0x1 << ((b) % 8))))) + +/** + * Reset (zero) the whole bitfield + */ +#define nmbs_bitfield_reset(bf) memset(bf, 0, sizeof(nmbs_bitfield)) + + +/** + * Modbus transport type. + */ +typedef enum nmbs_transport { + NMBS_TRANSPORT_RTU = 1, + NMBS_TRANSPORT_TCP = 2, +} nmbs_transport; + + +/** + * nanoMODBUS platform configuration struct. + * Passed to nmbs_server_create() and nmbs_client_create(). + * + * read_byte() and write_byte() are the platform-specific methods that read/write data to/from a serial port or a TCP connection. + * Both methods should block until the requested byte is read/written. + * If your implementation uses a read/write timeout, and the timeout expires, the methods should return 0. + * Their return values should be: + * - `1` in case of success + * - `0` if no data is available immediately or after an internal timeout expiration + * - `-1` in case of error + * + * sleep() is the platform-specific method to pause for a certain amount of milliseconds. + * + * These methods accept a pointer to arbitrary user-data, which is the arg member of this struct. + * After the creation of an instance it can be changed with nmbs_set_platform_arg(). + */ +typedef struct nmbs_platform_conf { + nmbs_transport transport; /*!< Transport type */ + int (*read_byte)(uint8_t* b, int32_t, void* arg); /*!< Byte read transport function pointer */ + int (*write_byte)(uint8_t b, int32_t, void* arg); /*!< Byte write transport function pointer */ + void (*sleep)(uint32_t milliseconds, void* arg); /*!< Sleep function pointer */ + void* arg; /*!< User data, will be passed to functions above */ +} nmbs_platform_conf; + + +/** + * Modbus server request callbacks. Passed to nmbs_server_create(). + */ +typedef struct nmbs_callbacks { + nmbs_error (*read_coils)(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out); + nmbs_error (*read_discrete_inputs)(uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out); + nmbs_error (*read_holding_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out); + nmbs_error (*read_input_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out); + nmbs_error (*write_single_coil)(uint16_t address, bool value); + nmbs_error (*write_single_register)(uint16_t address, uint16_t value); + nmbs_error (*write_multiple_coils)(uint16_t address, uint16_t quantity, const nmbs_bitfield coils); + nmbs_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, const uint16_t* registers); +} nmbs_callbacks; + + +/** + * nanoMODBUS client/server instance type. All struct members are to be considered private, it is not advisable to read/write them directly. + */ +typedef struct nmbs_t { + struct { + uint8_t buf[260]; + uint16_t buf_idx; + + uint8_t unit_id; + uint8_t fc; + uint16_t transaction_id; + bool broadcast; + bool ignored; + } msg; + + nmbs_callbacks callbacks; + + int32_t byte_timeout_ms; + int32_t read_timeout_ms; + uint32_t byte_spacing_ms; + + nmbs_platform_conf platform; + + uint8_t address_rtu; + uint8_t dest_address_rtu; + uint16_t current_tid; +} nmbs_t; + +/** + * Modbus broadcast address. Can be passed to nmbs_set_destination_rtu_address(). + */ +static const uint8_t NMBS_BROADCAST_ADDRESS = 0; + + +/** Create a new Modbus client. + * @param nmbs pointer to the nmbs_t instance where the client will be created. + * @param platform_conf nmbs_platform_conf struct with platform configuration. + * +* @return NMBS_ERROR_NONE if successful, NMBS_ERROR_INVALID_ARGUMENT otherwise. + */ +nmbs_error nmbs_client_create(nmbs_t* nmbs, const nmbs_platform_conf* platform_conf); + +/** Create a new Modbus server. + * @param nmbs pointer to the nmbs_t instance where the client will be created. + * @param address_rtu RTU address of this server. Can be 0 if transport is not RTU. + * @param platform_conf nmbs_platform_conf struct with platform configuration. + * @param callbacks nmbs_callbacks struct with server request callbacks. + * + * @return NMBS_ERROR_NONE if successful, NMBS_ERROR_INVALID_ARGUMENT otherwise. + */ +nmbs_error nmbs_server_create(nmbs_t* nmbs, uint8_t address_rtu, const nmbs_platform_conf* platform_conf, + const nmbs_callbacks* callbacks); + +/** Set the request/response timeout. + * If the target instance is a server, sets the timeout of the nmbs_server_poll() function. + * If the target instance is a client, sets the response timeout after sending a request. In case of timeout, the called method will return NMBS_ERROR_TIMEOUT. + * @param nmbs pointer to the nmbs_t instance + * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled. + */ +void nmbs_set_read_timeout(nmbs_t* nmbs, int32_t timeout_ms); + +/** Set the timeout between the reception of two consecutive bytes. + * @param nmbs pointer to the nmbs_t instance + * @param timeout_ms timeout in milliseconds. If < 0, the timeout is disabled. + */ +void nmbs_set_byte_timeout(nmbs_t* nmbs, int32_t timeout_ms); + +/** Set the spacing between two sent bytes. This value is ignored when transport is not RTU. + * @param nmbs pointer to the nmbs_t instance + * @param timeout_ms time spacing in milliseconds. + */ +void nmbs_set_byte_spacing(nmbs_t* nmbs, uint32_t spacing_ms); + +/** Set the pointer to user data argument passed to platform functions. + * @param nmbs pointer to the nmbs_t instance + * @param arg user data argument + */ +void nmbs_set_platform_arg(nmbs_t* nmbs, void* arg); + +/** Set the recipient server address of the next request on RTU transport. + * @param nmbs pointer to the nmbs_t instance + * @param address server address + */ +void nmbs_set_destination_rtu_address(nmbs_t* nmbs, uint8_t address); + +/** Handle incoming requests to the server. + * This function should be called in a loop in order to serve any incoming request. Its maximum duration, in case of no + * received request, is the value set with nmbs_set_read_timeout() (unless set to < 0). + * @param nmbs pointer to the nmbs_t instance + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_server_poll(nmbs_t* nmbs); + +/** Send a FC 01 (0x01) Read Coils request + * @param nmbs pointer to the nmbs_t instance + * @param address starting address + * @param quantity quantity of coils + * @param coils_out nmbs_bitfield where the coils will be stored + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_read_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield coils_out); + +/** Send a FC 02 (0x02) Read Discrete Inputs request + * @param nmbs pointer to the nmbs_t instance + * @param address starting address + * @param quantity quantity of inputs + * @param inputs_out nmbs_bitfield where the discrete inputs will be stored + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_read_discrete_inputs(nmbs_t* nmbs, uint16_t address, uint16_t quantity, nmbs_bitfield inputs_out); + +/** Send a FC 03 (0x03) Read Holding Registers request + * @param nmbs pointer to the nmbs_t instance + * @param address starting address + * @param quantity quantity of registers + * @param registers_out array where the registers will be stored + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_read_holding_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out); + +/** Send a FC 04 (0x04) Read Input Registers request + * @param nmbs pointer to the nmbs_t instance + * @param address starting address + * @param quantity quantity of registers + * @param registers_out array where the registers will be stored + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_read_input_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, uint16_t* registers_out); + +/** Send a FC 05 (0x05) Write Single Coil request + * @param nmbs pointer to the nmbs_t instance + * @param address coil address + * @param value coil value + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_write_single_coil(nmbs_t* nmbs, uint16_t address, bool value); + +/** Send a FC 06 (0x06) Write Single Register request + * @param nmbs pointer to the nmbs_t instance + * @param address register address + * @param value register value + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_write_single_register(nmbs_t* nmbs, uint16_t address, uint16_t value); + +/** Send a FC 15 (0x0F) Write Multiple Coils + * @param nmbs pointer to the nmbs_t instance + * @param address starting address + * @param quantity quantity of coils + * @param coils bitfield of coils values + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_write_multiple_coils(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const nmbs_bitfield coils); + +/** Send a FC 16 (0x10) Write Multiple Registers + * @param nmbs pointer to the nmbs_t instance + * @param address starting address + * @param quantity quantity of registers + * @param registers array of registers values + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_write_multiple_registers(nmbs_t* nmbs, uint16_t address, uint16_t quantity, const uint16_t* registers); + +/** Send a raw Modbus PDU. + * CRC on RTU will be calculated and sent by this function. + * @param nmbs pointer to the nmbs_t instance + * @param fc request function code + * @param data request data. It's up to the caller to convert this data to network byte order + * @param data_len length of the data parameter + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_send_raw_pdu(nmbs_t* nmbs, uint8_t fc, const void* data, uint32_t data_len); + +/** Receive a raw response Modbus PDU. + * @param nmbs pointer to the nmbs_t instance + * @param data_out response data. It's up to the caller to convert this data to host byte order. + * @param data_out_len length of the data_out parameter + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ +nmbs_error nmbs_receive_raw_pdu_response(nmbs_t* nmbs, void* data_out, uint32_t data_out_len); + +#ifndef NMBS_STRERROR_DISABLED +/** Convert a nmbs_error to string + * @param error error to be converted + * + * @return string representation of the error + */ +const char* nmbs_strerror(nmbs_error error); +#endif + + +#endif //NANOMODBUS_H diff --git a/tests/modbusino_tests.c b/tests/modbusino_tests.c deleted file mode 100644 index 5cc3144..0000000 --- a/tests/modbusino_tests.c +++ /dev/null @@ -1,855 +0,0 @@ -#include "modbusino_tests.h" -#include "modbusino.h" -#include -#include -#include - - -int read_byte_empty(uint8_t* b, int32_t timeout, void* arg) { - UNUSED_PARAM(b); - UNUSED_PARAM(timeout); - UNUSED_PARAM(arg); - return 0; -} - - -int write_byte_empty(uint8_t b, int32_t timeout, void* arg) { - UNUSED_PARAM(b); - UNUSED_PARAM(timeout); - UNUSED_PARAM(arg); - return 0; -} - - -void test_server_create(mbsn_transport transport) { - mbsn_t mbsn; - mbsn_error err; - - mbsn_platform_conf platform_conf_empty = {.transport = transport, - .read_byte = read_byte_empty, - .write_byte = write_byte_empty, - .sleep = platform_sleep}; - - mbsn_callbacks callbacks_empty; - - should("create a modbus server"); - reset(mbsn); - err = mbsn_server_create(&mbsn, TEST_SERVER_ADDR, &platform_conf_empty, &callbacks_empty); - check(err); - - should("check parameters and fail to create a modbus server"); - reset(mbsn); - err = mbsn_server_create(NULL, TEST_SERVER_ADDR, &platform_conf_empty, &callbacks_empty); - expect(err == MBSN_ERROR_INVALID_ARGUMENT); - - reset(mbsn); - err = mbsn_server_create(&mbsn, 0, &platform_conf_empty, &callbacks_empty); - if (transport == MBSN_TRANSPORT_RTU) - expect(err == MBSN_ERROR_INVALID_ARGUMENT); - else - expect(err == MBSN_ERROR_NONE); - - reset(mbsn); - mbsn_platform_conf p = platform_conf_empty; - p.transport = 3; - err = mbsn_server_create(&mbsn, 0, &p, &callbacks_empty); - expect(err == MBSN_ERROR_INVALID_ARGUMENT); - - reset(mbsn); - p = platform_conf_empty; - p.read_byte = NULL; - err = mbsn_server_create(&mbsn, 0, &p, &callbacks_empty); - expect(err == MBSN_ERROR_INVALID_ARGUMENT); - - reset(mbsn); - p = platform_conf_empty; - p.write_byte = NULL; - err = mbsn_server_create(&mbsn, 0, &p, &callbacks_empty); - expect(err == MBSN_ERROR_INVALID_ARGUMENT); -} - - -int read_byte_timeout(uint8_t* b, int32_t timeout, void* arg) { - UNUSED_PARAM(b); - UNUSED_PARAM(arg); - usleep(timeout * 1000); - return 0; -} - - -int read_byte_timeout_third(uint8_t* b, int32_t timeout, void* arg) { - UNUSED_PARAM(arg); - - static int stage = 0; - switch (stage) { - case 0: - case 1: - *b = 1; - stage++; - return 1; - case 2: - expect(timeout > 0); - usleep(timeout * 1000 + 100 * 1000); - stage = 0; - return 0; - default: - stage = 0; - return -1; - } -} - - -void test_server_receive_base(mbsn_transport transport) { - mbsn_t server, client; - mbsn_error err; - mbsn_platform_conf platform_conf; - mbsn_callbacks callbacks_empty; - - - should("honor read_timeout and return normally"); - reset(server); - platform_conf.transport = transport; - platform_conf.read_byte = read_byte_timeout; - platform_conf.write_byte = write_byte_empty; - platform_conf.sleep = platform_sleep; - - const int32_t read_timeout_ms = 250; - - err = mbsn_server_create(&server, TEST_SERVER_ADDR, &platform_conf, &callbacks_empty); - check(err); - - mbsn_set_read_timeout(&server, read_timeout_ms); - mbsn_set_byte_timeout(&server, -1); - - const int polls = 5; - for (int i = 0; i < polls; i++) { - uint64_t start = now_ms(); - err = mbsn_server_poll(&server); - check(err); - - uint64_t diff = now_ms() - start; - - expect(diff >= (uint64_t) read_timeout_ms); - } - - - should("honor byte_timeout and return MBSN_ERROR_TIMEOUT"); - reset(server); - platform_conf.transport = transport; - platform_conf.read_byte = read_byte_timeout_third; - platform_conf.write_byte = write_byte_empty; - - const int32_t byte_timeout_ms = 250; - - err = mbsn_server_create(&server, TEST_SERVER_ADDR, &platform_conf, &callbacks_empty); - check(err); - - mbsn_set_read_timeout(&server, 1000); - mbsn_set_byte_timeout(&server, byte_timeout_ms); - - err = mbsn_server_poll(&server); - expect(err == MBSN_ERROR_TIMEOUT); - - - should("honor byte spacing on RTU"); - if (transport == MBSN_TRANSPORT_RTU) { - reset(client); - platform_conf.transport = transport; - platform_conf.read_byte = read_byte_socket_client; - platform_conf.write_byte = write_byte_socket_client; - - reset_sockets(); - - check(mbsn_client_create(&client, &platform_conf)); - - mbsn_set_byte_spacing(&client, 200); - - uint64_t start = now_ms(); - check(mbsn_send_raw_pdu(&client, 1, (uint16_t[]){htons(1), htons(1)}, 4)); - uint64_t diff = now_ms() - start; - expect(diff >= 200 * 8); - } -} - - -mbsn_error read_discrete(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) { - if (address == 1) - return -1; - - if (address == 2) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; - - if (address == 3) - return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; - - if (address == 10 && quantity == 3) { - mbsn_bitfield_write(coils_out, 0, 1); - mbsn_bitfield_write(coils_out, 1, 0); - mbsn_bitfield_write(coils_out, 2, 1); - } - - if (address == 65526 && quantity == 10) { - mbsn_bitfield_write(coils_out, 0, 1); - mbsn_bitfield_write(coils_out, 1, 0); - mbsn_bitfield_write(coils_out, 2, 1); - mbsn_bitfield_write(coils_out, 3, 0); - mbsn_bitfield_write(coils_out, 4, 1); - mbsn_bitfield_write(coils_out, 5, 0); - mbsn_bitfield_write(coils_out, 6, 1); - mbsn_bitfield_write(coils_out, 7, 0); - mbsn_bitfield_write(coils_out, 8, 1); - mbsn_bitfield_write(coils_out, 9, 0); - } - - return MBSN_ERROR_NONE; -} - - -void test_fc1(mbsn_transport transport) { - const uint8_t fc = 1; - uint8_t raw_res[260]; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_read_coils(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.read_coils = read_discrete}); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); - expect(mbsn_read_coils(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 2000"); - expect(mbsn_read_coils(&CLIENT, 1, 2001, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); - expect(mbsn_read_coils(&CLIENT, 65530, 7, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(65530), htons(7)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_read_coils(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_read_coils(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_read_coils(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("read with no error"); - mbsn_bitfield bf; - check(mbsn_read_coils(&CLIENT, 10, 3, bf)); - expect(mbsn_bitfield_read(bf, 0) == 1); - expect(mbsn_bitfield_read(bf, 1) == 0); - expect(mbsn_bitfield_read(bf, 2) == 1); - - check(mbsn_read_coils(&CLIENT, 65526, 10, bf)); - expect(mbsn_bitfield_read(bf, 0) == 1); - expect(mbsn_bitfield_read(bf, 1) == 0); - expect(mbsn_bitfield_read(bf, 2) == 1); - expect(mbsn_bitfield_read(bf, 3) == 0); - expect(mbsn_bitfield_read(bf, 4) == 1); - expect(mbsn_bitfield_read(bf, 5) == 0); - expect(mbsn_bitfield_read(bf, 6) == 1); - expect(mbsn_bitfield_read(bf, 7) == 0); - expect(mbsn_bitfield_read(bf, 8) == 1); - expect(mbsn_bitfield_read(bf, 9) == 0); - - stop_client_and_server(); -} - - -void test_fc2(mbsn_transport transport) { - const uint8_t fc = 2; - uint8_t raw_res[260]; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_read_discrete_inputs(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.read_discrete_inputs = read_discrete}); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); - expect(mbsn_read_discrete_inputs(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 2000"); - expect(mbsn_read_discrete_inputs(&CLIENT, 1, 2001, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); - expect(mbsn_read_discrete_inputs(&CLIENT, 65530, 7, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(65530), htons(7)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_read_discrete_inputs(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_read_discrete_inputs(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_read_discrete_inputs(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("read with no error"); - mbsn_bitfield bf; - check(mbsn_read_discrete_inputs(&CLIENT, 10, 3, bf)); - expect(mbsn_bitfield_read(bf, 0) == 1); - expect(mbsn_bitfield_read(bf, 1) == 0); - expect(mbsn_bitfield_read(bf, 2) == 1); - - check(mbsn_read_discrete_inputs(&CLIENT, 65526, 10, bf)); - expect(mbsn_bitfield_read(bf, 0) == 1); - expect(mbsn_bitfield_read(bf, 1) == 0); - expect(mbsn_bitfield_read(bf, 2) == 1); - expect(mbsn_bitfield_read(bf, 3) == 0); - expect(mbsn_bitfield_read(bf, 4) == 1); - expect(mbsn_bitfield_read(bf, 5) == 0); - expect(mbsn_bitfield_read(bf, 6) == 1); - expect(mbsn_bitfield_read(bf, 7) == 0); - expect(mbsn_bitfield_read(bf, 8) == 1); - expect(mbsn_bitfield_read(bf, 9) == 0); - - stop_client_and_server(); -} - - -mbsn_error read_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out) { - if (address == 1) - return -1; - - if (address == 2) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; - - if (address == 3) - return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; - - if (address == 10 && quantity == 3) { - registers_out[0] = 100; - registers_out[1] = 0; - registers_out[2] = 200; - } - - return MBSN_ERROR_NONE; -} - - -void test_fc3(mbsn_transport transport) { - const uint8_t fc = 3; - uint8_t raw_res[260]; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_read_holding_registers(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.read_holding_registers = read_registers}); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); - expect(mbsn_read_holding_registers(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 125"); - expect(mbsn_read_holding_registers(&CLIENT, 1, 126, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); - expect(mbsn_read_holding_registers(&CLIENT, 0xFFFF, 2, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_read_holding_registers(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_read_holding_registers(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_read_holding_registers(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("read with no error"); - uint16_t regs[3]; - check(mbsn_read_holding_registers(&CLIENT, 10, 3, regs)); - expect(regs[0] == 100); - expect(regs[1] == 0); - expect(regs[2] == 200); - - stop_client_and_server(); -} - - -void test_fc4(mbsn_transport transport) { - const uint8_t fc = 4; - uint8_t raw_res[260]; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_read_input_registers(&CLIENT, 0, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.read_input_registers = read_registers}); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); - expect(mbsn_read_input_registers(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 125"); - expect(mbsn_read_input_registers(&CLIENT, 1, 126, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); - expect(mbsn_read_input_registers(&CLIENT, 0xFFFF, 2, NULL) == MBSN_ERROR_INVALID_ARGUMENT); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_read_input_registers(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_read_input_registers(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_read_input_registers(&CLIENT, 3, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("read with no error"); - uint16_t regs[3]; - check(mbsn_read_input_registers(&CLIENT, 10, 3, regs)); - expect(regs[0] == 100); - expect(regs[1] == 0); - expect(regs[2] == 200); - - stop_client_and_server(); -} - - -mbsn_error write_coil(uint16_t address, bool value) { - if (address == 1) - return -1; - - if (address == 2) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; - - if (address == 3) - return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; - - if (address == 4 && !value) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - if (address == 5 && value) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - return MBSN_ERROR_NONE; -} - - -void test_fc5(mbsn_transport transport) { - const uint8_t fc = 5; - uint8_t raw_res[260]; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_write_single_coil(&CLIENT, 0, true) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.write_single_coil = write_coil}); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE when calling with value !0x0000 or 0xFF000"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(6), htons(0x0001)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(6), htons(0xFFFF)}, 4)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_write_single_coil(&CLIENT, 1, true) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_write_single_coil(&CLIENT, 2, true) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_write_single_coil(&CLIENT, 3, true) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("write with no error"); - check(mbsn_write_single_coil(&CLIENT, 4, true)); - check(mbsn_write_single_coil(&CLIENT, 5, false)); - - should("echo request's address and value"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(4), htons(0xFF00)}, 4)); - check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); - - expect(((uint16_t*) raw_res)[0] == ntohs(4)); - expect(((uint16_t*) raw_res)[1] == ntohs(0xFF00)); - - stop_client_and_server(); -} - - -mbsn_error write_register(uint16_t address, uint16_t value) { - if (address == 1) - return -1; - - if (address == 2) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; - - if (address == 3) - return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; - - if (address == 4 && !value) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - if (address == 5 && value) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - return MBSN_ERROR_NONE; -} - - -void test_fc6(mbsn_transport transport) { - const uint8_t fc = 6; - uint8_t raw_res[260]; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_write_single_register(&CLIENT, 0, 123) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.write_single_register = write_register}); - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_write_single_register(&CLIENT, 1, 123) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_write_single_register(&CLIENT, 2, 123) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_write_single_register(&CLIENT, 3, 123) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("write with no error"); - check(mbsn_write_single_register(&CLIENT, 4, true)); - check(mbsn_write_single_register(&CLIENT, 5, false)); - - should("echo request's address and value"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(4), htons(0x123)}, 4)); - check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); - - expect(((uint16_t*) raw_res)[0] == ntohs(4)); - expect(((uint16_t*) raw_res)[1] == ntohs(0x123)); - - stop_client_and_server(); -} - - -mbsn_error write_coils(uint16_t address, uint16_t quantity, const mbsn_bitfield coils) { - if (address == 1) - return -1; - - if (address == 2) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; - - if (address == 3) - return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; - - if (address == 4) { - if (quantity != 4) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - expect(mbsn_bitfield_read(coils, 0) == 1); - expect(mbsn_bitfield_read(coils, 1) == 0); - expect(mbsn_bitfield_read(coils, 2) == 1); - expect(mbsn_bitfield_read(coils, 3) == 0); - - return MBSN_ERROR_NONE; - } - - if (address == 5) { - if (quantity != 27) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - expect(mbsn_bitfield_read(coils, 26) == 1); - - return MBSN_ERROR_NONE; - } - - if (address == 7) { - if (quantity != 1) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - return MBSN_ERROR_NONE; - } - - return MBSN_ERROR_NONE; -} - - -void test_fc15(mbsn_transport transport) { - const uint8_t fc = 15; - uint8_t raw_res[260]; - mbsn_bitfield bf = {0}; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_write_multiple_coils(&CLIENT, 0, 1, bf) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.write_multiple_coils = write_coils}); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); - expect(mbsn_write_multiple_coils(&CLIENT, 1, 0, bf) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 0x07B0"); - expect(mbsn_write_multiple_coils(&CLIENT, 1, 0x07B1, bf) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); - expect(mbsn_write_multiple_coils(&CLIENT, 0xFFFF, 2, bf) == MBSN_ERROR_INVALID_ARGUMENT); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0), htons(0x0100)}, 6)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2000), htons(0x0100)}, 6)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2), htons(0x0100)}, 6)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - /* - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when quantity does not match byte count"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(5), htons(0x0303)}, 6)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - */ - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_write_multiple_coils(&CLIENT, 1, 1, bf) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_write_multiple_coils(&CLIENT, 2, 2, bf) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_write_multiple_coils(&CLIENT, 3, 3, bf) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("write with no error"); - mbsn_bitfield_write(bf, 0, 1); - mbsn_bitfield_write(bf, 1, 0); - mbsn_bitfield_write(bf, 2, 1); - mbsn_bitfield_write(bf, 3, 0); - check(mbsn_write_multiple_coils(&CLIENT, 4, 4, bf)); - - mbsn_bitfield_write(bf, 26, 1); - check(mbsn_write_multiple_coils(&CLIENT, 5, 27, bf)); - - should("echo request's address and value"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(7), htons(1), htons(0x0100)}, 6)); - check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); - - expect(((uint16_t*) raw_res)[0] == ntohs(7)); - expect(((uint16_t*) raw_res)[1] == ntohs(1)); - - stop_client_and_server(); -} - - -mbsn_error write_registers(uint16_t address, uint16_t quantity, const uint16_t* registers) { - if (address == 1) - return -1; - - if (address == 2) - return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; - - if (address == 3) - return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; - - if (address == 4) { - if (quantity != 4) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - expect(registers[0] == 255); - expect(registers[1] == 1); - expect(registers[2] == 2); - expect(registers[3] == 3); - - return MBSN_ERROR_NONE; - } - - if (address == 5) { - if (quantity != 27) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - expect(registers[26] == 26); - - return MBSN_ERROR_NONE; - } - - if (address == 7) { - if (quantity != 1) - return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE; - - return MBSN_ERROR_NONE; - } - - return MBSN_ERROR_NONE; -} - - -void test_fc16(mbsn_transport transport) { - const uint8_t fc = 16; - uint8_t raw_res[260]; - uint16_t registers[125]; - mbsn_callbacks callbacks_empty = {0}; - - start_client_and_server(transport, &callbacks_empty); - - should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); - expect(mbsn_write_multiple_registers(&CLIENT, 0, 1, registers) == MBSN_EXCEPTION_ILLEGAL_FUNCTION); - - stop_client_and_server(); - - start_client_and_server(transport, &(mbsn_callbacks){.write_multiple_registers = write_registers}); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0"); - expect(mbsn_write_multiple_registers(&CLIENT, 1, 0, registers) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 0x007B"); - expect(mbsn_write_multiple_registers(&CLIENT, 1, 0x007C, registers) == MBSN_ERROR_INVALID_ARGUMENT); - - should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); - expect(mbsn_write_multiple_registers(&CLIENT, 0xFFFF, 2, registers) == MBSN_ERROR_INVALID_ARGUMENT); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0), htons(0x0200), htons(0)}, 7)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2000), htons(0x0200), htons(0)}, 7)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2), htons(0x0200), htons(0)}, 7)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - /* - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when quantity does not match byte count"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(5), htons(0x0303)}, 6)); - expect(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 2) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - */ - - should("return MBSN_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); - expect(mbsn_write_multiple_registers(&CLIENT, 1, 1, registers) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); - expect(mbsn_write_multiple_registers(&CLIENT, 2, 2, registers) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS); - - should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); - expect(mbsn_write_multiple_registers(&CLIENT, 3, 3, registers) == MBSN_EXCEPTION_ILLEGAL_DATA_VALUE); - - should("write with no error"); - registers[0] = 255; - registers[1] = 1; - registers[2] = 2; - registers[3] = 3; - check(mbsn_write_multiple_registers(&CLIENT, 4, 4, registers)); - - registers[26] = 26; - check(mbsn_write_multiple_registers(&CLIENT, 6, 27, registers)); - - should("echo request's address and value"); - check(mbsn_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(7), htons(1), htons(0x0200), htons(0)}, 7)); - check(mbsn_receive_raw_pdu_response(&CLIENT, raw_res, 4)); - - expect(((uint16_t*) raw_res)[0] == ntohs(7)); - expect(((uint16_t*) raw_res)[1] == ntohs(1)); - - stop_client_and_server(); -} - - -mbsn_transport transports[2] = {MBSN_TRANSPORT_RTU, MBSN_TRANSPORT_TCP}; -const char* transports_str[2] = {"RTU", "TCP"}; - -void for_transports(void (*test_fn)(mbsn_transport), const char* should_str) { - for (unsigned long t = 0; t < sizeof(transports) / sizeof(mbsn_transport); t++) { - printf("Should %s on %s:\n", should_str, transports_str[t]); - test(test_fn(transports[t])); - } -} - -int main() { - for_transports(test_server_create, "create a modbus server"); - - for_transports(test_server_receive_base, "receive no messages without failing"); - - for_transports(test_fc1, "send and receive FC 01 (0x01) Read Coils"); - - for_transports(test_fc2, "send and receive FC 02 (0x02) Read Discrete Inputs"); - - for_transports(test_fc3, "send and receive FC 03 (0x03) Read Holding Registers"); - - for_transports(test_fc4, "send and receive FC 04 (0x04) Read Input Registers"); - - for_transports(test_fc5, "send and receive FC 05 (0x05) Write Single Coil"); - - for_transports(test_fc6, "send and receive FC 06 (0x06) Write Single Register"); - - for_transports(test_fc15, "send and receive FC 15 (0x0F) Write Multiple Coils"); - - for_transports(test_fc16, "send and receive FC 16 (0x10) Write Multiple registers"); - - return 0; -} diff --git a/tests/nanomodbus_tests.c b/tests/nanomodbus_tests.c new file mode 100644 index 0000000..5561a9a --- /dev/null +++ b/tests/nanomodbus_tests.c @@ -0,0 +1,855 @@ +#include "nanomodbus_tests.h" +#include "nanomodbus.h" +#include +#include +#include + + +int read_byte_empty(uint8_t* b, int32_t timeout, void* arg) { + UNUSED_PARAM(b); + UNUSED_PARAM(timeout); + UNUSED_PARAM(arg); + return 0; +} + + +int write_byte_empty(uint8_t b, int32_t timeout, void* arg) { + UNUSED_PARAM(b); + UNUSED_PARAM(timeout); + UNUSED_PARAM(arg); + return 0; +} + + +void test_server_create(nmbs_transport transport) { + nmbs_t nmbs; + nmbs_error err; + + nmbs_platform_conf platform_conf_empty = {.transport = transport, + .read_byte = read_byte_empty, + .write_byte = write_byte_empty, + .sleep = platform_sleep}; + + nmbs_callbacks callbacks_empty; + + should("create a modbus server"); + reset(nmbs); + err = nmbs_server_create(&nmbs, TEST_SERVER_ADDR, &platform_conf_empty, &callbacks_empty); + check(err); + + should("check parameters and fail to create a modbus server"); + reset(nmbs); + err = nmbs_server_create(NULL, TEST_SERVER_ADDR, &platform_conf_empty, &callbacks_empty); + expect(err == NMBS_ERROR_INVALID_ARGUMENT); + + reset(nmbs); + err = nmbs_server_create(&nmbs, 0, &platform_conf_empty, &callbacks_empty); + if (transport == NMBS_TRANSPORT_RTU) + expect(err == NMBS_ERROR_INVALID_ARGUMENT); + else + expect(err == NMBS_ERROR_NONE); + + reset(nmbs); + nmbs_platform_conf p = platform_conf_empty; + p.transport = 3; + err = nmbs_server_create(&nmbs, 0, &p, &callbacks_empty); + expect(err == NMBS_ERROR_INVALID_ARGUMENT); + + reset(nmbs); + p = platform_conf_empty; + p.read_byte = NULL; + err = nmbs_server_create(&nmbs, 0, &p, &callbacks_empty); + expect(err == NMBS_ERROR_INVALID_ARGUMENT); + + reset(nmbs); + p = platform_conf_empty; + p.write_byte = NULL; + err = nmbs_server_create(&nmbs, 0, &p, &callbacks_empty); + expect(err == NMBS_ERROR_INVALID_ARGUMENT); +} + + +int read_byte_timeout(uint8_t* b, int32_t timeout, void* arg) { + UNUSED_PARAM(b); + UNUSED_PARAM(arg); + usleep(timeout * 1000); + return 0; +} + + +int read_byte_timeout_third(uint8_t* b, int32_t timeout, void* arg) { + UNUSED_PARAM(arg); + + static int stage = 0; + switch (stage) { + case 0: + case 1: + *b = 1; + stage++; + return 1; + case 2: + expect(timeout > 0); + usleep(timeout * 1000 + 100 * 1000); + stage = 0; + return 0; + default: + stage = 0; + return -1; + } +} + + +void test_server_receive_base(nmbs_transport transport) { + nmbs_t server, client; + nmbs_error err; + nmbs_platform_conf platform_conf; + nmbs_callbacks callbacks_empty; + + + should("honor read_timeout and return normally"); + reset(server); + platform_conf.transport = transport; + platform_conf.read_byte = read_byte_timeout; + platform_conf.write_byte = write_byte_empty; + platform_conf.sleep = platform_sleep; + + const int32_t read_timeout_ms = 250; + + err = nmbs_server_create(&server, TEST_SERVER_ADDR, &platform_conf, &callbacks_empty); + check(err); + + nmbs_set_read_timeout(&server, read_timeout_ms); + nmbs_set_byte_timeout(&server, -1); + + const int polls = 5; + for (int i = 0; i < polls; i++) { + uint64_t start = now_ms(); + err = nmbs_server_poll(&server); + check(err); + + uint64_t diff = now_ms() - start; + + expect(diff >= (uint64_t) read_timeout_ms); + } + + + should("honor byte_timeout and return NMBS_ERROR_TIMEOUT"); + reset(server); + platform_conf.transport = transport; + platform_conf.read_byte = read_byte_timeout_third; + platform_conf.write_byte = write_byte_empty; + + const int32_t byte_timeout_ms = 250; + + err = nmbs_server_create(&server, TEST_SERVER_ADDR, &platform_conf, &callbacks_empty); + check(err); + + nmbs_set_read_timeout(&server, 1000); + nmbs_set_byte_timeout(&server, byte_timeout_ms); + + err = nmbs_server_poll(&server); + expect(err == NMBS_ERROR_TIMEOUT); + + + should("honor byte spacing on RTU"); + if (transport == NMBS_TRANSPORT_RTU) { + reset(client); + platform_conf.transport = transport; + platform_conf.read_byte = read_byte_socket_client; + platform_conf.write_byte = write_byte_socket_client; + + reset_sockets(); + + check(nmbs_client_create(&client, &platform_conf)); + + nmbs_set_byte_spacing(&client, 200); + + uint64_t start = now_ms(); + check(nmbs_send_raw_pdu(&client, 1, (uint16_t[]){htons(1), htons(1)}, 4)); + uint64_t diff = now_ms() - start; + expect(diff >= 200 * 8); + } +} + + +nmbs_error read_discrete(uint16_t address, uint16_t quantity, nmbs_bitfield coils_out) { + if (address == 1) + return -1; + + if (address == 2) + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 10 && quantity == 3) { + nmbs_bitfield_write(coils_out, 0, 1); + nmbs_bitfield_write(coils_out, 1, 0); + nmbs_bitfield_write(coils_out, 2, 1); + } + + if (address == 65526 && quantity == 10) { + nmbs_bitfield_write(coils_out, 0, 1); + nmbs_bitfield_write(coils_out, 1, 0); + nmbs_bitfield_write(coils_out, 2, 1); + nmbs_bitfield_write(coils_out, 3, 0); + nmbs_bitfield_write(coils_out, 4, 1); + nmbs_bitfield_write(coils_out, 5, 0); + nmbs_bitfield_write(coils_out, 6, 1); + nmbs_bitfield_write(coils_out, 7, 0); + nmbs_bitfield_write(coils_out, 8, 1); + nmbs_bitfield_write(coils_out, 9, 0); + } + + return NMBS_ERROR_NONE; +} + + +void test_fc1(nmbs_transport transport) { + const uint8_t fc = 1; + uint8_t raw_res[260]; + 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_coils(&CLIENT, 0, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.read_coils = read_discrete}); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + expect(nmbs_read_coils(&CLIENT, 1, 0, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity > 2000"); + expect(nmbs_read_coils(&CLIENT, 1, 2001, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + expect(nmbs_read_coils(&CLIENT, 65530, 7, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(65530), htons(7)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_read_coils(&CLIENT, 1, 1, NULL) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_read_coils(&CLIENT, 2, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_read_coils(&CLIENT, 3, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + nmbs_bitfield bf; + check(nmbs_read_coils(&CLIENT, 10, 3, bf)); + expect(nmbs_bitfield_read(bf, 0) == 1); + expect(nmbs_bitfield_read(bf, 1) == 0); + expect(nmbs_bitfield_read(bf, 2) == 1); + + check(nmbs_read_coils(&CLIENT, 65526, 10, bf)); + expect(nmbs_bitfield_read(bf, 0) == 1); + expect(nmbs_bitfield_read(bf, 1) == 0); + expect(nmbs_bitfield_read(bf, 2) == 1); + expect(nmbs_bitfield_read(bf, 3) == 0); + expect(nmbs_bitfield_read(bf, 4) == 1); + expect(nmbs_bitfield_read(bf, 5) == 0); + expect(nmbs_bitfield_read(bf, 6) == 1); + expect(nmbs_bitfield_read(bf, 7) == 0); + expect(nmbs_bitfield_read(bf, 8) == 1); + expect(nmbs_bitfield_read(bf, 9) == 0); + + stop_client_and_server(); +} + + +void test_fc2(nmbs_transport transport) { + const uint8_t fc = 2; + uint8_t raw_res[260]; + 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_discrete_inputs(&CLIENT, 0, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.read_discrete_inputs = read_discrete}); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + expect(nmbs_read_discrete_inputs(&CLIENT, 1, 0, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity > 2000"); + expect(nmbs_read_discrete_inputs(&CLIENT, 1, 2001, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + expect(nmbs_read_discrete_inputs(&CLIENT, 65530, 7, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(65530), htons(7)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_read_discrete_inputs(&CLIENT, 1, 1, NULL) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_read_discrete_inputs(&CLIENT, 2, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_read_discrete_inputs(&CLIENT, 3, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + nmbs_bitfield bf; + check(nmbs_read_discrete_inputs(&CLIENT, 10, 3, bf)); + expect(nmbs_bitfield_read(bf, 0) == 1); + expect(nmbs_bitfield_read(bf, 1) == 0); + expect(nmbs_bitfield_read(bf, 2) == 1); + + check(nmbs_read_discrete_inputs(&CLIENT, 65526, 10, bf)); + expect(nmbs_bitfield_read(bf, 0) == 1); + expect(nmbs_bitfield_read(bf, 1) == 0); + expect(nmbs_bitfield_read(bf, 2) == 1); + expect(nmbs_bitfield_read(bf, 3) == 0); + expect(nmbs_bitfield_read(bf, 4) == 1); + expect(nmbs_bitfield_read(bf, 5) == 0); + expect(nmbs_bitfield_read(bf, 6) == 1); + expect(nmbs_bitfield_read(bf, 7) == 0); + expect(nmbs_bitfield_read(bf, 8) == 1); + expect(nmbs_bitfield_read(bf, 9) == 0); + + stop_client_and_server(); +} + + +nmbs_error read_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out) { + if (address == 1) + return -1; + + if (address == 2) + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 10 && quantity == 3) { + registers_out[0] = 100; + registers_out[1] = 0; + registers_out[2] = 200; + } + + return NMBS_ERROR_NONE; +} + + +void test_fc3(nmbs_transport transport) { + const uint8_t fc = 3; + uint8_t raw_res[260]; + 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_holding_registers(&CLIENT, 0, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.read_holding_registers = read_registers}); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + expect(nmbs_read_holding_registers(&CLIENT, 1, 0, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity > 125"); + expect(nmbs_read_holding_registers(&CLIENT, 1, 126, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + expect(nmbs_read_holding_registers(&CLIENT, 0xFFFF, 2, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_read_holding_registers(&CLIENT, 1, 1, NULL) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_read_holding_registers(&CLIENT, 2, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_read_holding_registers(&CLIENT, 3, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + uint16_t regs[3]; + check(nmbs_read_holding_registers(&CLIENT, 10, 3, regs)); + expect(regs[0] == 100); + expect(regs[1] == 0); + expect(regs[2] == 200); + + stop_client_and_server(); +} + + +void test_fc4(nmbs_transport transport) { + const uint8_t fc = 4; + uint8_t raw_res[260]; + 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_input_registers(&CLIENT, 0, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.read_input_registers = read_registers}); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + expect(nmbs_read_input_registers(&CLIENT, 1, 0, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity > 125"); + expect(nmbs_read_input_registers(&CLIENT, 1, 126, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + expect(nmbs_read_input_registers(&CLIENT, 0xFFFF, 2, NULL) == NMBS_ERROR_INVALID_ARGUMENT); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2001)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_read_input_registers(&CLIENT, 1, 1, NULL) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_read_input_registers(&CLIENT, 2, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_read_input_registers(&CLIENT, 3, 1, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("read with no error"); + uint16_t regs[3]; + check(nmbs_read_input_registers(&CLIENT, 10, 3, regs)); + expect(regs[0] == 100); + expect(regs[1] == 0); + expect(regs[2] == 200); + + stop_client_and_server(); +} + + +nmbs_error write_coil(uint16_t address, bool value) { + if (address == 1) + return -1; + + if (address == 2) + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4 && !value) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + if (address == 5 && value) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + return NMBS_ERROR_NONE; +} + + +void test_fc5(nmbs_transport transport) { + const uint8_t fc = 5; + uint8_t raw_res[260]; + 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_single_coil(&CLIENT, 0, true) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.write_single_coil = write_coil}); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE when calling with value !0x0000 or 0xFF000"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(6), htons(0x0001)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(6), htons(0xFFFF)}, 4)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_write_single_coil(&CLIENT, 1, true) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_write_single_coil(&CLIENT, 2, true) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_write_single_coil(&CLIENT, 3, true) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + check(nmbs_write_single_coil(&CLIENT, 4, true)); + check(nmbs_write_single_coil(&CLIENT, 5, false)); + + should("echo request's address and value"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(4), htons(0xFF00)}, 4)); + check(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + expect(((uint16_t*) raw_res)[0] == ntohs(4)); + expect(((uint16_t*) raw_res)[1] == ntohs(0xFF00)); + + stop_client_and_server(); +} + + +nmbs_error write_register(uint16_t address, uint16_t value) { + if (address == 1) + return -1; + + if (address == 2) + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4 && !value) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + if (address == 5 && value) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + return NMBS_ERROR_NONE; +} + + +void test_fc6(nmbs_transport transport) { + const uint8_t fc = 6; + uint8_t raw_res[260]; + 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_single_register(&CLIENT, 0, 123) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.write_single_register = write_register}); + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_write_single_register(&CLIENT, 1, 123) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_write_single_register(&CLIENT, 2, 123) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_write_single_register(&CLIENT, 3, 123) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + check(nmbs_write_single_register(&CLIENT, 4, true)); + check(nmbs_write_single_register(&CLIENT, 5, false)); + + should("echo request's address and value"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(4), htons(0x123)}, 4)); + check(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + expect(((uint16_t*) raw_res)[0] == ntohs(4)); + expect(((uint16_t*) raw_res)[1] == ntohs(0x123)); + + stop_client_and_server(); +} + + +nmbs_error write_coils(uint16_t address, uint16_t quantity, const nmbs_bitfield coils) { + if (address == 1) + return -1; + + if (address == 2) + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4) { + if (quantity != 4) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + expect(nmbs_bitfield_read(coils, 0) == 1); + expect(nmbs_bitfield_read(coils, 1) == 0); + expect(nmbs_bitfield_read(coils, 2) == 1); + expect(nmbs_bitfield_read(coils, 3) == 0); + + return NMBS_ERROR_NONE; + } + + if (address == 5) { + if (quantity != 27) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + expect(nmbs_bitfield_read(coils, 26) == 1); + + return NMBS_ERROR_NONE; + } + + if (address == 7) { + if (quantity != 1) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + return NMBS_ERROR_NONE; + } + + return NMBS_ERROR_NONE; +} + + +void test_fc15(nmbs_transport transport) { + const uint8_t fc = 15; + uint8_t raw_res[260]; + nmbs_bitfield bf = {0}; + 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_multiple_coils(&CLIENT, 0, 1, bf) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.write_multiple_coils = write_coils}); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + expect(nmbs_write_multiple_coils(&CLIENT, 1, 0, bf) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity > 0x07B0"); + expect(nmbs_write_multiple_coils(&CLIENT, 1, 0x07B1, bf) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + expect(nmbs_write_multiple_coils(&CLIENT, 0xFFFF, 2, bf) == NMBS_ERROR_INVALID_ARGUMENT); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0), htons(0x0100)}, 6)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2000), htons(0x0100)}, 6)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2), htons(0x0100)}, 6)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + /* + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when quantity does not match byte count"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(5), htons(0x0303)}, 6)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + */ + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_write_multiple_coils(&CLIENT, 1, 1, bf) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_write_multiple_coils(&CLIENT, 2, 2, bf) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_write_multiple_coils(&CLIENT, 3, 3, bf) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + nmbs_bitfield_write(bf, 0, 1); + nmbs_bitfield_write(bf, 1, 0); + nmbs_bitfield_write(bf, 2, 1); + nmbs_bitfield_write(bf, 3, 0); + check(nmbs_write_multiple_coils(&CLIENT, 4, 4, bf)); + + nmbs_bitfield_write(bf, 26, 1); + check(nmbs_write_multiple_coils(&CLIENT, 5, 27, bf)); + + should("echo request's address and value"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(7), htons(1), htons(0x0100)}, 6)); + check(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + expect(((uint16_t*) raw_res)[0] == ntohs(7)); + expect(((uint16_t*) raw_res)[1] == ntohs(1)); + + stop_client_and_server(); +} + + +nmbs_error write_registers(uint16_t address, uint16_t quantity, const uint16_t* registers) { + if (address == 1) + return -1; + + if (address == 2) + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (address == 3) + return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (address == 4) { + if (quantity != 4) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + expect(registers[0] == 255); + expect(registers[1] == 1); + expect(registers[2] == 2); + expect(registers[3] == 3); + + return NMBS_ERROR_NONE; + } + + if (address == 5) { + if (quantity != 27) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + expect(registers[26] == 26); + + return NMBS_ERROR_NONE; + } + + if (address == 7) { + if (quantity != 1) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + return NMBS_ERROR_NONE; + } + + return NMBS_ERROR_NONE; +} + + +void test_fc16(nmbs_transport transport) { + const uint8_t fc = 16; + uint8_t raw_res[260]; + uint16_t registers[125]; + 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_multiple_registers(&CLIENT, 0, 1, registers) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + + stop_client_and_server(); + + start_client_and_server(transport, &(nmbs_callbacks){.write_multiple_registers = write_registers}); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity 0"); + expect(nmbs_write_multiple_registers(&CLIENT, 1, 0, registers) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with quantity > 0x007B"); + expect(nmbs_write_multiple_registers(&CLIENT, 1, 0x007C, registers) == NMBS_ERROR_INVALID_ARGUMENT); + + should("immediately return NMBS_ERROR_INVALID_ARGUMENT when calling with address + quantity > 0xFFFF + 1"); + expect(nmbs_write_multiple_registers(&CLIENT, 0xFFFF, 2, registers) == NMBS_ERROR_INVALID_ARGUMENT); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(0), htons(0x0200), htons(0)}, 7)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity > 2000"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(2000), htons(0x0200), htons(0)}, 7)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address + quantity > 0xFFFF + 1"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(0xFFFF), htons(2), htons(0x0200), htons(0)}, 7)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + /* + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when quantity does not match byte count"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(1), htons(5), htons(0x0303)}, 6)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 2) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + */ + + should("return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE when server handler returns any non-exception error"); + expect(nmbs_write_multiple_registers(&CLIENT, 1, 1, registers) == NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler"); + expect(nmbs_write_multiple_registers(&CLIENT, 2, 2, registers) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler"); + expect(nmbs_write_multiple_registers(&CLIENT, 3, 3, registers) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("write with no error"); + registers[0] = 255; + registers[1] = 1; + registers[2] = 2; + registers[3] = 3; + check(nmbs_write_multiple_registers(&CLIENT, 4, 4, registers)); + + registers[26] = 26; + check(nmbs_write_multiple_registers(&CLIENT, 6, 27, registers)); + + should("echo request's address and value"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint16_t[]){htons(7), htons(1), htons(0x0200), htons(0)}, 7)); + check(nmbs_receive_raw_pdu_response(&CLIENT, raw_res, 4)); + + expect(((uint16_t*) raw_res)[0] == ntohs(7)); + expect(((uint16_t*) raw_res)[1] == ntohs(1)); + + stop_client_and_server(); +} + + +nmbs_transport transports[2] = {NMBS_TRANSPORT_RTU, NMBS_TRANSPORT_TCP}; +const char* transports_str[2] = {"RTU", "TCP"}; + +void for_transports(void (*test_fn)(nmbs_transport), const char* should_str) { + for (unsigned long t = 0; t < sizeof(transports) / sizeof(nmbs_transport); t++) { + printf("Should %s on %s:\n", should_str, transports_str[t]); + test(test_fn(transports[t])); + } +} + +int main() { + for_transports(test_server_create, "create a modbus server"); + + for_transports(test_server_receive_base, "receive no messages without failing"); + + for_transports(test_fc1, "send and receive FC 01 (0x01) Read Coils"); + + for_transports(test_fc2, "send and receive FC 02 (0x02) Read Discrete Inputs"); + + for_transports(test_fc3, "send and receive FC 03 (0x03) Read Holding Registers"); + + for_transports(test_fc4, "send and receive FC 04 (0x04) Read Input Registers"); + + for_transports(test_fc5, "send and receive FC 05 (0x05) Write Single Coil"); + + for_transports(test_fc6, "send and receive FC 06 (0x06) Write Single Register"); + + for_transports(test_fc15, "send and receive FC 15 (0x0F) Write Multiple Coils"); + + for_transports(test_fc16, "send and receive FC 16 (0x10) Write Multiple registers"); + + return 0; +} diff --git a/tests/modbusino_tests.h b/tests/nanomodbus_tests.h similarity index 76% rename from tests/modbusino_tests.h rename to tests/nanomodbus_tests.h index df41be5..5af9bcd 100644 --- a/tests/modbusino_tests.h +++ b/tests/nanomodbus_tests.h @@ -1,4 +1,4 @@ -#include "modbusino.h" +#include "nanomodbus.h" #undef NDEBUG #include #include @@ -11,9 +11,9 @@ #define expect(expr) assert(expr) -#define check(err) (expect((err) == MBSN_ERROR_NONE)) +#define check(err) (expect((err) == NMBS_ERROR_NONE)) -#define reset(mbsn) (memset(&(mbsn), 0, sizeof(mbsn_t))) +#define reset(nmbs) (memset(&(nmbs), 0, sizeof(nmbs_t))) #define test(f) (nesting++, (f), nesting--) @@ -30,7 +30,7 @@ int sockets[2] = {-1, -1}; bool server_stopped = true; pthread_mutex_t server_stopped_m = PTHREAD_MUTEX_INITIALIZER; pthread_t server_thread; -mbsn_t CLIENT, SERVER; +nmbs_t CLIENT, SERVER; #define should(s) \ @@ -148,23 +148,23 @@ int write_byte_socket_client(uint8_t b, int32_t timeout_ms, void* arg) { } -mbsn_platform_conf mbsn_platform_conf_server; -mbsn_platform_conf* platform_conf_socket_server(mbsn_transport transport) { - mbsn_platform_conf_server.transport = transport; - mbsn_platform_conf_server.read_byte = read_byte_socket_server; - mbsn_platform_conf_server.write_byte = write_byte_socket_server; - mbsn_platform_conf_server.sleep = platform_sleep; - return &mbsn_platform_conf_server; +nmbs_platform_conf nmbs_platform_conf_server; +nmbs_platform_conf* platform_conf_socket_server(nmbs_transport transport) { + nmbs_platform_conf_server.transport = transport; + nmbs_platform_conf_server.read_byte = read_byte_socket_server; + nmbs_platform_conf_server.write_byte = write_byte_socket_server; + nmbs_platform_conf_server.sleep = platform_sleep; + return &nmbs_platform_conf_server; } -mbsn_platform_conf mbsn_platform_conf_client; -mbsn_platform_conf* platform_conf_socket_client(mbsn_transport transport) { - mbsn_platform_conf_client.transport = transport; - mbsn_platform_conf_client.read_byte = read_byte_socket_client; - mbsn_platform_conf_client.write_byte = write_byte_socket_client; - mbsn_platform_conf_client.sleep = platform_sleep; - return &mbsn_platform_conf_client; +nmbs_platform_conf nmbs_platform_conf_client; +nmbs_platform_conf* platform_conf_socket_client(nmbs_transport transport) { + nmbs_platform_conf_client.transport = transport; + nmbs_platform_conf_client.read_byte = read_byte_socket_client; + nmbs_platform_conf_client.write_byte = write_byte_socket_client; + nmbs_platform_conf_client.sleep = platform_sleep; + return &nmbs_platform_conf_client; } @@ -182,7 +182,7 @@ void* server_listen_thread() { if (is_server_listen_thread_stopped()) break; - check(mbsn_server_poll(&SERVER)); + check(nmbs_server_poll(&SERVER)); } return NULL; @@ -199,7 +199,7 @@ void stop_client_and_server() { } -void start_client_and_server(mbsn_transport transport, const mbsn_callbacks* server_callbacks) { +void start_client_and_server(nmbs_transport transport, const nmbs_callbacks* server_callbacks) { expect(pthread_mutex_destroy(&server_stopped_m) == 0); expect(pthread_mutex_init(&server_stopped_m, NULL) == 0); @@ -208,15 +208,15 @@ void start_client_and_server(mbsn_transport transport, const mbsn_callbacks* ser reset(SERVER); reset(CLIENT); - check(mbsn_server_create(&SERVER, TEST_SERVER_ADDR, platform_conf_socket_server(transport), server_callbacks)); - check(mbsn_client_create(&CLIENT, platform_conf_socket_client(transport))); + check(nmbs_server_create(&SERVER, TEST_SERVER_ADDR, platform_conf_socket_server(transport), server_callbacks)); + check(nmbs_client_create(&CLIENT, platform_conf_socket_client(transport))); - mbsn_set_destination_rtu_address(&CLIENT, TEST_SERVER_ADDR); - mbsn_set_read_timeout(&SERVER, 500); - mbsn_set_byte_timeout(&SERVER, 100); + nmbs_set_destination_rtu_address(&CLIENT, TEST_SERVER_ADDR); + nmbs_set_read_timeout(&SERVER, 500); + nmbs_set_byte_timeout(&SERVER, 100); - mbsn_set_read_timeout(&CLIENT, 5000); - mbsn_set_byte_timeout(&CLIENT, 100); + nmbs_set_read_timeout(&CLIENT, 5000); + nmbs_set_byte_timeout(&CLIENT, 100); expect(pthread_mutex_lock(&server_stopped_m) == 0); server_stopped = false;