Examples, fixes
This commit is contained in:
parent
d9d66989d7
commit
07b4440096
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
.idea
|
||||
cmake-build*
|
||||
build*
|
||||
/tests/cmake-build*
|
||||
/tests/build*
|
||||
/examples/cmake-build*
|
||||
/examples/build*
|
||||
|
||||
17
CMakeLists.txt
Normal file
17
CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(modbusino C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -pedantic")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
|
||||
include_directories(tests .)
|
||||
|
||||
#add_definitions(-DMBSN_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)
|
||||
|
||||
target_link_libraries(modbusino_tests pthread)
|
||||
92
examples/client-tcp.c
Normal file
92
examples/client-tcp.c
Normal file
@ -0,0 +1,92 @@
|
||||
#include "modbusino.h"
|
||||
#include "platform.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* This example application connects via TCP to a modbus server at the specified address and port, and sends some
|
||||
* modbus requests to it.
|
||||
*
|
||||
* Since the platform for this example is linux, the platform arg is used to pass (to the linux TCP read/write
|
||||
* functions) a pointer to the file descriptor of our TCP connection
|
||||
*
|
||||
* The platform functions are for Linux systems.
|
||||
*/
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: client-tcp [address] [port]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set up the TCP connection
|
||||
void* conn = connect_tcp(argv[1], argv[2]);
|
||||
if (!conn) {
|
||||
fprintf(stderr, "Error connecting to server\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
mbsn_platform_conf platform_conf;
|
||||
platform_conf.transport = MBSN_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) {
|
||||
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);
|
||||
|
||||
// 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));
|
||||
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));
|
||||
return 1;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// 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));
|
||||
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));
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Register at address 26: %d\n", r_regs[0]);
|
||||
printf("Register at address 27: %d\n", r_regs[1]);
|
||||
|
||||
// Close the TCP connection
|
||||
disconnect(conn);
|
||||
|
||||
// No need to destroy the mbsn instance, bye bye
|
||||
return 0;
|
||||
}
|
||||
231
examples/platform.h
Normal file
231
examples/platform.h
Normal file
@ -0,0 +1,231 @@
|
||||
#include "modbusino.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
// Read/write/sleep platform functions
|
||||
|
||||
int read_byte_fd_linux(uint8_t* b, int32_t timeout_ms, void* arg) {
|
||||
int fd = *(int*) arg;
|
||||
|
||||
fd_set rfds;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(fd, &rfds);
|
||||
|
||||
struct timeval* tv_p = NULL;
|
||||
struct timeval tv;
|
||||
if (timeout_ms >= 0) {
|
||||
tv_p = &tv;
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
}
|
||||
|
||||
int ret = select(fd + 1, &rfds, NULL, NULL, tv_p);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
else if (ret == 1) {
|
||||
ssize_t r = read(fd, b, 1);
|
||||
if (r != 1)
|
||||
return -1;
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int write_byte_fd_linux(uint8_t b, int32_t timeout_ms, void* arg) {
|
||||
int fd = *(int*) arg;
|
||||
|
||||
fd_set wfds;
|
||||
FD_ZERO(&wfds);
|
||||
FD_SET(fd, &wfds);
|
||||
|
||||
struct timeval* tv_p = NULL;
|
||||
struct timeval tv;
|
||||
if (timeout_ms >= 0) {
|
||||
tv_p = &tv;
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
}
|
||||
|
||||
int ret = select(fd + 1, NULL, &wfds, NULL, tv_p);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
}
|
||||
else if (ret == 1) {
|
||||
ssize_t r = write(fd, &b, 1);
|
||||
if (r != 1)
|
||||
return -1;
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
void sleep_linux(uint32_t milliseconds, void* arg) {
|
||||
usleep(milliseconds * 1000);
|
||||
}
|
||||
|
||||
|
||||
// Connection management
|
||||
|
||||
int client_connection = -1;
|
||||
|
||||
void* connect_tcp(const char* address, const char* port) {
|
||||
struct addrinfo ainfo = {0};
|
||||
struct addrinfo *results, *rp;
|
||||
int fd;
|
||||
|
||||
ainfo.ai_family = AF_INET;
|
||||
ainfo.ai_socktype = SOCK_STREAM;
|
||||
int ret = getaddrinfo(address, port, &ainfo, &results);
|
||||
if (ret != 0)
|
||||
return NULL;
|
||||
|
||||
for (rp = results; rp != NULL; rp = rp->ai_next) {
|
||||
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (fd == -1)
|
||||
continue;
|
||||
|
||||
ret = connect(fd, rp->ai_addr, rp->ai_addrlen);
|
||||
if (ret == 0)
|
||||
break;
|
||||
else
|
||||
close(fd);
|
||||
}
|
||||
|
||||
freeaddrinfo(results);
|
||||
|
||||
if (rp == NULL)
|
||||
return NULL;
|
||||
|
||||
client_connection = fd;
|
||||
return &client_connection;
|
||||
}
|
||||
|
||||
|
||||
int server_fd = -1;
|
||||
int client_read_fd = -1;
|
||||
fd_set client_connections;
|
||||
|
||||
void close_server_on_exit(int sig) {
|
||||
if (server_fd != -1)
|
||||
close(server_fd);
|
||||
}
|
||||
|
||||
|
||||
int create_tcp_server(const char* address, const char* port) {
|
||||
struct addrinfo ainfo = {0};
|
||||
struct addrinfo *results, *rp;
|
||||
int fd = -1;
|
||||
|
||||
ainfo.ai_family = AF_INET;
|
||||
ainfo.ai_socktype = SOCK_STREAM;
|
||||
int ret = getaddrinfo(address, port, &ainfo, &results);
|
||||
if (ret != 0)
|
||||
return -1;
|
||||
|
||||
for (rp = results; rp != NULL; rp = rp->ai_next) {
|
||||
fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||
if (fd == -1)
|
||||
continue;
|
||||
|
||||
ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
|
||||
if (ret != 0)
|
||||
return -1;
|
||||
|
||||
ret = bind(fd, rp->ai_addr, rp->ai_addrlen);
|
||||
if (ret != 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = listen(fd, 1);
|
||||
if (ret != 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
freeaddrinfo(results);
|
||||
|
||||
signal(SIGINT, close_server_on_exit);
|
||||
signal(SIGTERM, close_server_on_exit);
|
||||
signal(SIGQUIT, close_server_on_exit);
|
||||
signal(SIGSTOP, close_server_on_exit);
|
||||
signal(SIGHUP, close_server_on_exit);
|
||||
|
||||
server_fd = fd;
|
||||
FD_ZERO(&client_connections);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void* server_poll() {
|
||||
fd_set read_fd_set;
|
||||
FD_ZERO(&read_fd_set);
|
||||
|
||||
while (true) {
|
||||
read_fd_set = client_connections;
|
||||
FD_SET(server_fd, &read_fd_set);
|
||||
|
||||
int ret = select(FD_SETSIZE, &read_fd_set, NULL, NULL, NULL);
|
||||
if (ret < 0)
|
||||
return NULL;
|
||||
|
||||
for (int i = 0; i < FD_SETSIZE; ++i) {
|
||||
if (FD_ISSET(i, &read_fd_set)) {
|
||||
if (i == server_fd) {
|
||||
struct sockaddr_in client_addr;
|
||||
socklen_t client_addr_size = sizeof(client_addr);
|
||||
|
||||
int client = accept(server_fd, (struct sockaddr*) &client_addr, &client_addr_size);
|
||||
if (client < 0) {
|
||||
fprintf(stderr, "Error accepting client connection from %s - %s\n",
|
||||
inet_ntoa(client_addr.sin_addr), strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
FD_SET(client, &client_connections);
|
||||
printf("Accepted connection from %s\n", inet_ntoa(client_addr.sin_addr));
|
||||
}
|
||||
else {
|
||||
client_read_fd = i;
|
||||
return &client_read_fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void disconnect(void* conn) {
|
||||
int fd = *(int*) conn;
|
||||
close(fd);
|
||||
}
|
||||
|
||||
|
||||
void close_server() {
|
||||
close(server_fd);
|
||||
}
|
||||
63
examples/server-handlers.h
Normal file
63
examples/server-handlers.h
Normal file
@ -0,0 +1,63 @@
|
||||
#include "modbusino.h"
|
||||
|
||||
// Server (slave) handler functions
|
||||
|
||||
// This server data model will support coils addresses 0 to 100 and registers addresses from 0 to 26
|
||||
#define COILS_ADDR_MAX 100
|
||||
#define REGS_ADDR_MAX 32
|
||||
|
||||
// A single mbsn_bitfield variable can keep 2000 coils
|
||||
mbsn_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) {
|
||||
if (address + quantity > COILS_ADDR_MAX + 1)
|
||||
return MBSN_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);
|
||||
}
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
mbsn_error handle_write_multiple_coils(uint16_t address, uint16_t quantity, const mbsn_bitfield coils) {
|
||||
if (address + quantity > COILS_ADDR_MAX + 1)
|
||||
return MBSN_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));
|
||||
}
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
mbsn_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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
mbsn_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;
|
||||
|
||||
// Write registers values to our server_registers
|
||||
for (int i = 0; i < quantity; i++)
|
||||
server_registers[address + i] = registers[i];
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
85
examples/server-tcp.c
Normal file
85
examples/server-tcp.c
Normal file
@ -0,0 +1,85 @@
|
||||
#include "modbusino.h"
|
||||
#include "platform.h"
|
||||
#include "server-handlers.h"
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
* This example application sets up a TCP server at the specified address and port, and polls from modbus requests
|
||||
* from more than one modbus client (more specifically from maximum 1024 clients, since it uses select())
|
||||
*
|
||||
* This example server supports the following function codes:
|
||||
* FC 01 (0x01) Read Coils
|
||||
* FC 03 (0x03) Read Holding Registers
|
||||
* FC 15 (0x0F) Write Multiple Coils
|
||||
* FC 16 (0x10) Write Multiple registers
|
||||
*
|
||||
* Since the platform for this example is linux, the platform arg is used to pass (to the linux TCP read/write
|
||||
* functions) a pointer to the file descriptor of the current read client connection
|
||||
*
|
||||
* The platform functions are for Linux systems.
|
||||
*/
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: server-tcp [address] [port]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Set up the TCP server
|
||||
int ret = create_tcp_server(argv[1], argv[2]);
|
||||
if (ret != 0) {
|
||||
fprintf(stderr, "Error creating TCP server\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
mbsn_platform_conf platform_conf = {0};
|
||||
platform_conf.transport = MBSN_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};
|
||||
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) {
|
||||
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);
|
||||
|
||||
printf("Modbus TCP server started\n");
|
||||
|
||||
// Our server supports requests from more than one client
|
||||
while (true) {
|
||||
// Our server_poll() function will return the next client TCP connection to read from
|
||||
void* conn = server_poll();
|
||||
if (!conn)
|
||||
break;
|
||||
|
||||
// Set the next connection handler used by the read/write platform functions
|
||||
mbsn_set_platform_arg(&mbsn, conn);
|
||||
|
||||
err = mbsn_server_poll(&mbsn);
|
||||
if (err != MBSN_ERROR_NONE) {
|
||||
fprintf(stderr, "Error polling modbus connection - %s\n", mbsn_strerror(err));
|
||||
// In a more complete example, we should handle this error by closing the connection from our side
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the TCP server
|
||||
close_server();
|
||||
|
||||
// No need to destroy the mbsn instance, bye bye
|
||||
return 0;
|
||||
}
|
||||
172
modbusino.c
172
modbusino.c
@ -60,16 +60,17 @@ static void msg_state_reset(mbsn_t* mbsn) {
|
||||
|
||||
|
||||
static void msg_state_req(mbsn_t* mbsn, uint8_t fc) {
|
||||
static uint16_t TID = 1;
|
||||
if (mbsn->current_tid == UINT16_MAX)
|
||||
mbsn->current_tid = 1;
|
||||
else
|
||||
mbsn->current_tid++;
|
||||
|
||||
msg_state_reset(mbsn);
|
||||
mbsn->msg.unit_id = mbsn->dest_address_rtu;
|
||||
mbsn->msg.fc = fc;
|
||||
mbsn->msg.transaction_id = TID;
|
||||
if (mbsn->msg.unit_id == 0)
|
||||
mbsn->msg.transaction_id = mbsn->current_tid;
|
||||
if (mbsn->msg.unit_id == 0 && mbsn->platform.transport == MBSN_TRANSPORT_RTU)
|
||||
mbsn->msg.broadcast = true;
|
||||
|
||||
TID++;
|
||||
}
|
||||
|
||||
|
||||
@ -103,17 +104,17 @@ mbsn_error mbsn_client_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_c
|
||||
}
|
||||
|
||||
|
||||
mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address, const mbsn_platform_conf* transport_conf,
|
||||
mbsn_callbacks callbacks) {
|
||||
if (address == 0)
|
||||
mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address_rtu, const mbsn_platform_conf* platform_conf,
|
||||
const mbsn_callbacks* callbacks) {
|
||||
if (platform_conf->transport == MBSN_TRANSPORT_RTU && address_rtu == 0)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mbsn_error ret = mbsn_create(mbsn, transport_conf);
|
||||
mbsn_error ret = mbsn_create(mbsn, platform_conf);
|
||||
if (ret != MBSN_ERROR_NONE)
|
||||
return ret;
|
||||
|
||||
mbsn->address_rtu = address;
|
||||
mbsn->callbacks = callbacks;
|
||||
mbsn->address_rtu = address_rtu;
|
||||
mbsn->callbacks = *callbacks;
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
@ -139,9 +140,14 @@ void mbsn_set_destination_rtu_address(mbsn_t* mbsn, uint8_t address) {
|
||||
}
|
||||
|
||||
|
||||
static uint16_t crc_calc(const uint8_t* data, unsigned int length) {
|
||||
void mbsn_set_platform_arg(mbsn_t* mbsn, void* arg) {
|
||||
mbsn->platform.arg = arg;
|
||||
}
|
||||
|
||||
|
||||
static uint16_t crc_calc(const uint8_t* data, uint32_t length) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
for (int i = 0; i < length; i++) {
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
crc ^= (uint16_t) data[i];
|
||||
for (int j = 8; j != 0; j--) {
|
||||
if ((crc & 0x0001) != 0) {
|
||||
@ -158,9 +164,10 @@ static uint16_t crc_calc(const uint8_t* data, unsigned int length) {
|
||||
|
||||
|
||||
static mbsn_error recv(mbsn_t* mbsn, uint32_t count) {
|
||||
int r = 0;
|
||||
uint32_t r = 0;
|
||||
while (r != count) {
|
||||
int ret = mbsn->platform.read_byte(mbsn->msg.buf + mbsn->msg.buf_idx + r, mbsn->byte_timeout_ms);
|
||||
int ret = mbsn->platform.read_byte(mbsn->msg.buf + mbsn->msg.buf_idx + r, mbsn->byte_timeout_ms,
|
||||
mbsn->platform.arg);
|
||||
if (ret == 0) {
|
||||
#ifdef MBSN_DEBUG
|
||||
if (mbsn->address_rtu == 0)
|
||||
@ -195,9 +202,9 @@ static mbsn_error send(mbsn_t* mbsn) {
|
||||
|
||||
for (int i = 0; i < mbsn->msg.buf_idx; i++) {
|
||||
if (spacing_ms != 0)
|
||||
mbsn->platform.sleep(spacing_ms);
|
||||
mbsn->platform.sleep(spacing_ms, mbsn->platform.arg);
|
||||
|
||||
int ret = mbsn->platform.write_byte(mbsn->msg.buf[i], mbsn->read_timeout_ms);
|
||||
int ret = mbsn->platform.write_byte(mbsn->msg.buf[i], mbsn->read_timeout_ms, mbsn->platform.arg);
|
||||
if (ret == 0) {
|
||||
return MBSN_ERROR_TIMEOUT;
|
||||
}
|
||||
@ -307,13 +314,15 @@ static mbsn_error recv_req_header(mbsn_t* mbsn, bool* first_byte_received) {
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
// Check if request is for us
|
||||
if (mbsn->msg.unit_id == MBSN_BROADCAST_ADDRESS)
|
||||
mbsn->msg.broadcast = true;
|
||||
else if (mbsn->msg.unit_id != mbsn->address_rtu)
|
||||
mbsn->msg.ignored = true;
|
||||
else
|
||||
mbsn->msg.ignored = false;
|
||||
if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) {
|
||||
// Check if request is for us
|
||||
if (mbsn->msg.unit_id == MBSN_BROADCAST_ADDRESS)
|
||||
mbsn->msg.broadcast = true;
|
||||
else if (mbsn->msg.unit_id != mbsn->address_rtu)
|
||||
mbsn->msg.ignored = true;
|
||||
else
|
||||
mbsn->msg.ignored = false;
|
||||
}
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
@ -374,9 +383,6 @@ static void send_msg_header(mbsn_t* mbsn, uint16_t data_length) {
|
||||
}
|
||||
|
||||
put_1(mbsn, mbsn->msg.fc);
|
||||
|
||||
if (mbsn->msg.unit_id == MBSN_BROADCAST_ADDRESS)
|
||||
mbsn->msg.broadcast = true;
|
||||
}
|
||||
|
||||
|
||||
@ -776,7 +782,7 @@ static mbsn_error handle_req_fc(mbsn_t* mbsn) {
|
||||
}
|
||||
|
||||
|
||||
mbsn_error mbsn_server_receive(mbsn_t* mbsn) {
|
||||
mbsn_error mbsn_server_poll(mbsn_t* mbsn) {
|
||||
msg_state_reset(mbsn);
|
||||
|
||||
bool first_byte_received = false;
|
||||
@ -788,75 +794,6 @@ mbsn_error mbsn_server_receive(mbsn_t* mbsn) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
// We should wait for the read timeout for the first message byte
|
||||
int32_t old_byte_timeout = mbsn->byte_timeout_ms;
|
||||
mbsn->byte_timeout_ms = mbsn->read_timeout_ms;
|
||||
|
||||
if (mbsn->transport == MBSN_TRANSPORT_RTU) {
|
||||
uint8_t id;
|
||||
mbsn_error err = recv_1(mbsn, &id, &req.crc);
|
||||
|
||||
mbsn->byte_timeout_ms = old_byte_timeout;
|
||||
|
||||
if (err != 0) {
|
||||
if (err == MBSN_ERROR_TIMEOUT)
|
||||
return MBSN_ERROR_NONE;
|
||||
else
|
||||
return err;
|
||||
}
|
||||
|
||||
// Check if request is for us
|
||||
if (id == 0)
|
||||
req.broadcast = true;
|
||||
else if (id != mbsn->address_rtu)
|
||||
req.ignored = true;
|
||||
else
|
||||
req.ignored = false;
|
||||
|
||||
err = recv_1(mbsn, &req.fc, &req.crc);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
}
|
||||
else if (mbsn->transport == MBSN_TRANSPORT_TCP) {
|
||||
mbsn_error err = recv_2(mbsn, &req.transaction_id, NULL);
|
||||
|
||||
mbsn->byte_timeout_ms = old_byte_timeout;
|
||||
|
||||
if (err != 0) {
|
||||
if (err == MBSN_ERROR_TIMEOUT)
|
||||
return MBSN_ERROR_NONE;
|
||||
else
|
||||
return err;
|
||||
}
|
||||
|
||||
uint16_t protocol_id = 0xFFFF;
|
||||
err = recv_2(mbsn, &protocol_id, NULL);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
uint16_t length = 0xFFFF;
|
||||
err = recv_2(mbsn, &length, NULL);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
err = recv_1(mbsn, &req.unit_id, NULL);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
err = recv_1(mbsn, &req.fc, NULL);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
if (protocol_id != 0)
|
||||
return MBSN_ERROR_TRANSPORT;
|
||||
|
||||
// TODO maybe we should actually check the length of the request against this value
|
||||
if (length == 0xFFFF)
|
||||
return MBSN_ERROR_TRANSPORT;
|
||||
}
|
||||
*/
|
||||
|
||||
err = handle_req_fc(mbsn);
|
||||
if (err != MBSN_ERROR_NONE) {
|
||||
if (!mbsn_error_is_exception(err))
|
||||
@ -1159,7 +1096,7 @@ mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_
|
||||
mbsn_error mbsn_send_raw_pdu(mbsn_t* mbsn, uint8_t fc, const void* data, uint32_t data_len) {
|
||||
msg_state_req(mbsn, fc);
|
||||
send_msg_header(mbsn, data_len);
|
||||
for (int i = 0; i < data_len; i++) {
|
||||
for (uint32_t i = 0; i < data_len; i++) {
|
||||
put_1(mbsn, ((uint8_t*) (data))[i]);
|
||||
}
|
||||
|
||||
@ -1176,7 +1113,7 @@ mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
for (int i = 0; i < data_out_len; i++) {
|
||||
for (uint32_t i = 0; i < data_out_len; i++) {
|
||||
((uint8_t*) (data_out))[i] = get_1(mbsn);
|
||||
}
|
||||
|
||||
@ -1186,3 +1123,40 @@ mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
#ifndef MBSN_STRERROR_DISABLED
|
||||
const char* mbsn_strerror(mbsn_error error) {
|
||||
switch (error) {
|
||||
case MBSN_ERROR_TRANSPORT:
|
||||
return "transport error";
|
||||
|
||||
case MBSN_ERROR_TIMEOUT:
|
||||
return "timeout";
|
||||
|
||||
case MBSN_ERROR_INVALID_RESPONSE:
|
||||
return "invalid response received";
|
||||
|
||||
case MBSN_ERROR_INVALID_ARGUMENT:
|
||||
return "invalid argument provided";
|
||||
|
||||
case MBSN_ERROR_NONE:
|
||||
return "no error";
|
||||
|
||||
case MBSN_EXCEPTION_ILLEGAL_FUNCTION:
|
||||
return "modbus exception 1: illegal function";
|
||||
|
||||
case MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS:
|
||||
return "modbus exception 2: illegal data address";
|
||||
|
||||
case MBSN_EXCEPTION_ILLEGAL_DATA_VALUE:
|
||||
return "modbus exception 3: data value";
|
||||
|
||||
case MBSN_EXCEPTION_SERVER_DEVICE_FAILURE:
|
||||
return "modbus exception 4: server device failure";
|
||||
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
23
modbusino.h
23
modbusino.h
@ -3,6 +3,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
typedef enum mbsn_error {
|
||||
@ -30,6 +31,8 @@ typedef uint8_t mbsn_bitfield[250];
|
||||
#define mbsn_bitfield_write(bf, b, v) \
|
||||
(((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & ~(0x1 << ((b) % 8)))))
|
||||
|
||||
#define mbsn_bitfield_reset(bf) memset(bf, 0, sizeof(mbsn_bitfield))
|
||||
|
||||
|
||||
typedef enum mbsn_transport {
|
||||
MBSN_TRANSPORT_RTU = 1,
|
||||
@ -38,9 +41,10 @@ typedef enum mbsn_transport {
|
||||
|
||||
typedef struct mbsn_platform_conf {
|
||||
mbsn_transport transport;
|
||||
int (*read_byte)(uint8_t* b, int32_t);
|
||||
int (*write_byte)(uint8_t b, int32_t);
|
||||
void (*sleep)(uint32_t milliseconds);
|
||||
int (*read_byte)(uint8_t* b, int32_t, void* arg);
|
||||
int (*write_byte)(uint8_t b, int32_t, void* arg);
|
||||
void (*sleep)(uint32_t milliseconds, void* arg);
|
||||
void* arg;
|
||||
} mbsn_platform_conf;
|
||||
|
||||
|
||||
@ -78,6 +82,7 @@ typedef struct mbsn_t {
|
||||
|
||||
uint8_t address_rtu;
|
||||
uint8_t dest_address_rtu;
|
||||
uint16_t current_tid;
|
||||
} mbsn_t;
|
||||
|
||||
|
||||
@ -86,8 +91,8 @@ static const uint8_t MBSN_BROADCAST_ADDRESS = 0;
|
||||
|
||||
mbsn_error mbsn_client_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf);
|
||||
|
||||
mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address, const mbsn_platform_conf* platform_conf,
|
||||
mbsn_callbacks callbacks);
|
||||
mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address_rtu, const mbsn_platform_conf* platform_conf,
|
||||
const mbsn_callbacks* callbacks);
|
||||
|
||||
void mbsn_set_read_timeout(mbsn_t* mbsn, int32_t timeout_ms);
|
||||
|
||||
@ -95,9 +100,11 @@ void mbsn_set_byte_timeout(mbsn_t* mbsn, int32_t timeout_ms);
|
||||
|
||||
void mbsn_set_byte_spacing(mbsn_t* mbsn, uint32_t spacing_ms);
|
||||
|
||||
void mbsn_set_platform_arg(mbsn_t* mbsn, void* arg);
|
||||
|
||||
void mbsn_set_destination_rtu_address(mbsn_t* mbsn, uint8_t address);
|
||||
|
||||
mbsn_error mbsn_server_receive(mbsn_t* mbsn);
|
||||
mbsn_error mbsn_server_poll(mbsn_t* mbsn);
|
||||
|
||||
mbsn_error mbsn_read_coils(mbsn_t* mbsn, uint16_t address, uint16_t quantity, mbsn_bitfield coils_out);
|
||||
|
||||
@ -119,7 +126,9 @@ mbsn_error mbsn_send_raw_pdu(mbsn_t* mbsn, uint8_t fc, const void* data, uint32_
|
||||
|
||||
mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t data_out_len);
|
||||
|
||||
const char* mbsn_strerror(int error);
|
||||
#ifndef MBSN_STRERROR_DISABLED
|
||||
const char* mbsn_strerror(mbsn_error error);
|
||||
#endif
|
||||
|
||||
|
||||
#endif //MODBUSINO_H
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(modbusino_tests C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0 -g")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
|
||||
include_directories(. ..)
|
||||
|
||||
#add_definitions(-DMBSN_DEBUG)
|
||||
|
||||
add_executable(modbusino_tests ../modbusino.c modbusino_tests.c)
|
||||
|
||||
target_link_libraries(modbusino_tests pthread)
|
||||
@ -6,12 +6,18 @@
|
||||
#include <string.h>
|
||||
|
||||
|
||||
int read_byte_empty(uint8_t* b, int32_t timeout) {
|
||||
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) {
|
||||
int write_byte_empty(uint8_t b, int32_t timeout, void* arg) {
|
||||
UNUSED_PARAM(b);
|
||||
UNUSED_PARAM(timeout);
|
||||
UNUSED_PARAM(arg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -25,47 +31,56 @@ void test_server_create(mbsn_transport transport) {
|
||||
.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, (mbsn_callbacks){});
|
||||
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, (mbsn_callbacks){});
|
||||
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, (mbsn_callbacks){});
|
||||
expect(err == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
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, (mbsn_callbacks){});
|
||||
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, (mbsn_callbacks){});
|
||||
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, (mbsn_callbacks){});
|
||||
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) {
|
||||
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) {
|
||||
int read_byte_timeout_third(uint8_t* b, int32_t timeout, void* arg) {
|
||||
UNUSED_PARAM(arg);
|
||||
|
||||
static int stage = 0;
|
||||
switch (stage) {
|
||||
case 0:
|
||||
@ -89,6 +104,7 @@ 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");
|
||||
@ -100,7 +116,7 @@ void test_server_receive_base(mbsn_transport transport) {
|
||||
|
||||
const int32_t read_timeout_ms = 250;
|
||||
|
||||
err = mbsn_server_create(&server, TEST_SERVER_ADDR, &platform_conf, (mbsn_callbacks){});
|
||||
err = mbsn_server_create(&server, TEST_SERVER_ADDR, &platform_conf, &callbacks_empty);
|
||||
check(err);
|
||||
|
||||
mbsn_set_read_timeout(&server, read_timeout_ms);
|
||||
@ -109,12 +125,12 @@ void test_server_receive_base(mbsn_transport transport) {
|
||||
const int polls = 5;
|
||||
for (int i = 0; i < polls; i++) {
|
||||
uint64_t start = now_ms();
|
||||
err = mbsn_server_receive(&server);
|
||||
err = mbsn_server_poll(&server);
|
||||
check(err);
|
||||
|
||||
uint64_t diff = now_ms() - start;
|
||||
|
||||
assert(diff >= read_timeout_ms);
|
||||
assert(diff >= (uint64_t) read_timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
@ -126,13 +142,13 @@ void test_server_receive_base(mbsn_transport transport) {
|
||||
|
||||
const int32_t byte_timeout_ms = 250;
|
||||
|
||||
err = mbsn_server_create(&server, TEST_SERVER_ADDR, &platform_conf, (mbsn_callbacks){});
|
||||
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_receive(&server);
|
||||
err = mbsn_server_poll(&server);
|
||||
expect(err == MBSN_ERROR_TIMEOUT);
|
||||
|
||||
|
||||
@ -193,15 +209,16 @@ mbsn_error read_discrete(uint16_t address, uint16_t quantity, mbsn_bitfield coil
|
||||
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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
start_client_and_server(transport, &(mbsn_callbacks){.read_coils = read_discrete});
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0");
|
||||
assert(mbsn_read_coils(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
@ -259,15 +276,16 @@ void test_fc1(mbsn_transport transport) {
|
||||
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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
start_client_and_server(transport, &(mbsn_callbacks){.read_discrete_inputs = read_discrete});
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0");
|
||||
assert(mbsn_read_discrete_inputs(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
@ -345,15 +363,16 @@ mbsn_error read_registers(uint16_t address, uint16_t quantity, uint16_t* registe
|
||||
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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
start_client_and_server(transport, &(mbsn_callbacks){.read_holding_registers = read_registers});
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0");
|
||||
assert(mbsn_read_holding_registers(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
@ -399,15 +418,16 @@ void test_fc3(mbsn_transport transport) {
|
||||
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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
start_client_and_server(transport, &(mbsn_callbacks){.read_input_registers = read_registers});
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0");
|
||||
assert(mbsn_read_input_registers(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
@ -473,15 +493,16 @@ mbsn_error write_coil(uint16_t address, bool value) {
|
||||
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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
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));
|
||||
@ -537,15 +558,16 @@ mbsn_error write_register(uint16_t address, uint16_t value) {
|
||||
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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
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");
|
||||
assert(mbsn_write_single_register(&CLIENT, 1, 123) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE);
|
||||
@ -617,15 +639,16 @@ 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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
start_client_and_server(transport, &(mbsn_callbacks){.write_multiple_coils = write_coils});
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0");
|
||||
assert(mbsn_write_multiple_coils(&CLIENT, 1, 0, bf) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
@ -730,15 +753,16 @@ 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, (mbsn_callbacks){});
|
||||
start_client_and_server(transport, &callbacks_empty);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(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});
|
||||
start_client_and_server(transport, &(mbsn_callbacks){.write_multiple_registers = write_registers});
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity 0");
|
||||
assert(mbsn_write_multiple_registers(&CLIENT, 1, 0, registers) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
@ -801,7 +825,7 @@ 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 (int t = 0; t < sizeof(transports) / sizeof(mbsn_transport); t++) {
|
||||
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]));
|
||||
}
|
||||
|
||||
@ -16,10 +16,8 @@
|
||||
|
||||
#define test(f) (nesting++, (f), nesting--)
|
||||
|
||||
/*
|
||||
#define htons(w) (((((uint16_t) (w) &0xFF)) << 8) | (((uint16_t) (w) &0xFF00) >> 8))
|
||||
#define ntohs(w) (((((uint16_t) (w) &0xFF)) << 8) | (((uint16_t) (w) &0xFF00) >> 8))
|
||||
*/
|
||||
#define UNUSED_PARAM(x) ((x) = (x))
|
||||
|
||||
|
||||
const uint8_t TEST_SERVER_ADDR = 1;
|
||||
|
||||
@ -35,7 +33,7 @@ mbsn_t CLIENT, SERVER;
|
||||
|
||||
|
||||
#define should(s) \
|
||||
for (int i = 0; i < nesting; i++) { \
|
||||
for (unsigned int i = 0; i < nesting; i++) { \
|
||||
printf("\t"); \
|
||||
} \
|
||||
printf("Should %s\n", (s))
|
||||
@ -48,7 +46,8 @@ uint64_t now_ms() {
|
||||
}
|
||||
|
||||
|
||||
void platform_sleep(uint32_t milliseconds) {
|
||||
void platform_sleep(uint32_t milliseconds, void* arg) {
|
||||
UNUSED_PARAM(arg);
|
||||
usleep(milliseconds * 1000);
|
||||
}
|
||||
|
||||
@ -124,22 +123,26 @@ int write_byte_fd(int fd, uint8_t b, int32_t timeout_ms) {
|
||||
}
|
||||
|
||||
|
||||
int read_byte_socket_server(uint8_t* b, int32_t timeout_ms) {
|
||||
int read_byte_socket_server(uint8_t* b, int32_t timeout_ms, void* arg) {
|
||||
UNUSED_PARAM(arg);
|
||||
return read_byte_fd(sockets[0], b, timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
int write_byte_socket_server(uint8_t b, int32_t timeout_ms) {
|
||||
int write_byte_socket_server(uint8_t b, int32_t timeout_ms, void* arg) {
|
||||
UNUSED_PARAM(arg);
|
||||
return write_byte_fd(sockets[0], b, timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
int read_byte_socket_client(uint8_t* b, int32_t timeout_ms) {
|
||||
int read_byte_socket_client(uint8_t* b, int32_t timeout_ms, void* arg) {
|
||||
UNUSED_PARAM(arg);
|
||||
return read_byte_fd(sockets[1], b, timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
int write_byte_socket_client(uint8_t b, int32_t timeout_ms) {
|
||||
int write_byte_socket_client(uint8_t b, int32_t timeout_ms, void* arg) {
|
||||
UNUSED_PARAM(arg);
|
||||
return write_byte_fd(sockets[1], b, timeout_ms);
|
||||
}
|
||||
|
||||
@ -178,7 +181,7 @@ void* server_listen_thread() {
|
||||
if (is_server_listen_thread_stopped())
|
||||
break;
|
||||
|
||||
check(mbsn_server_receive(&SERVER));
|
||||
check(mbsn_server_poll(&SERVER));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -195,7 +198,7 @@ void stop_client_and_server() {
|
||||
}
|
||||
|
||||
|
||||
void start_client_and_server(mbsn_transport transport, mbsn_callbacks server_callbacks) {
|
||||
void start_client_and_server(mbsn_transport transport, const mbsn_callbacks* server_callbacks) {
|
||||
assert(pthread_mutex_destroy(&server_stopped_m) == 0);
|
||||
assert(pthread_mutex_init(&server_stopped_m, NULL) == 0);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user