Project rename
This commit is contained in:
parent
352b03c7f6
commit
1d0321b285
@ -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)
|
||||
|
||||
4
Doxyfile
4
Doxyfile
@ -1,5 +1,5 @@
|
||||
PROJECT_NAME = MODBUSino
|
||||
INPUT = modbusino.c modbusino.h
|
||||
PROJECT_NAME = nanoMODBUS
|
||||
INPUT = nanomodbus.c nanomodbus.h
|
||||
OUTPUT_DIRECTORY = doxygen
|
||||
OPTIMIZE_OUTPUT_FOR_C = YES
|
||||
GENERATE_LATEX = NO
|
||||
|
||||
48
README.md
48
README.md
@ -1,6 +1,6 @@
|
||||
# MODBUSino - A compact MODBUS RTU/TCP C library for microcontrollers
|
||||
# nanoMODBUS - A compact MODBUS RTU/TCP C library for microcontrollers
|
||||
|
||||
MODBUSino is a small C library that implements the Modbus protocol. It is especially useful in resource-constrained
|
||||
nanoMODBUS is a small C library that implements the Modbus protocol. It is especially useful in resource-constrained
|
||||
system like microcontrollers.
|
||||
Its main features are:
|
||||
|
||||
@ -29,7 +29,7 @@ Its main features are:
|
||||
## At a glance
|
||||
|
||||
```C
|
||||
#include "modbusino.h"
|
||||
#include "nanomodbus.h"
|
||||
#include "my_platform_stuff.h"
|
||||
#include <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`
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
#include "modbusino.h"
|
||||
#include "nanomodbus.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
1244
modbusino.c
1244
modbusino.c
File diff suppressed because it is too large
Load Diff
320
modbusino.h
320
modbusino.h
@ -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
1244
nanomodbus.c
Normal file
File diff suppressed because it is too large
Load Diff
320
nanomodbus.h
Normal file
320
nanomodbus.h
Normal 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
|
||||
@ -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
855
tests/nanomodbus_tests.c
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
Loading…
Reference in New Issue
Block a user