Project rename

This commit is contained in:
Valerio De Benedetto 2022-01-25 00:37:54 +01:00
parent 352b03c7f6
commit 1d0321b285
13 changed files with 2541 additions and 2541 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 <stdio.h>
@ -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`

View File

@ -1,4 +1,4 @@
#include "modbusino.h"
#include "nanomodbus.h"
#include "platform.h"
#include <stdio.h>
@ -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;
}

View File

@ -1,4 +1,4 @@
#include "modbusino.h"
#include "nanomodbus.h"
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>

View File

@ -1,4 +1,4 @@
#include "modbusino.h"
#include "nanomodbus.h"
#include "platform.h"
#include <stdio.h>
@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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: <a href="https://github.com/debevv/MODBUSino">https://github.com/debevv/MODBUSino</a>
*
* API reference: \link modbusino.h \endlink
*
*/
#ifndef MODBUSINO_H
#define MODBUSINO_H
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
/**
* 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

1244
nanomodbus.c Normal file

File diff suppressed because it is too large Load Diff

320
nanomodbus.h Normal file
View File

@ -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: <a href="https://github.com/debevv/nanoMODBUS">https://github.com/debevv/nanoMODBUS</a>
*
* API reference: \link nanomodbus.h \endlink
*
*/
#ifndef NANOMODBUS_H
#define NANOMODBUS_H
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
/**
* 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

View File

@ -1,855 +0,0 @@
#include "modbusino_tests.h"
#include "modbusino.h"
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
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;
}

855
tests/nanomodbus_tests.c Normal file
View File

@ -0,0 +1,855 @@
#include "nanomodbus_tests.h"
#include "nanomodbus.h"
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
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;
}

View File

@ -1,4 +1,4 @@
#include "modbusino.h"
#include "nanomodbus.h"
#undef NDEBUG
#include <assert.h>
#include <pthread.h>
@ -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;