FC1 and FC2 tests
This commit is contained in:
parent
1951b1d923
commit
c064e47368
679
modbusino.c
679
modbusino.c
File diff suppressed because it is too large
Load Diff
22
modbusino.h
22
modbusino.h
@ -28,7 +28,7 @@ typedef uint8_t mbsn_bitfield[250];
|
||||
#define mbsn_bitfield_read(bf, b) ((bool) ((bf)[(b) / 8] & (0x1 << ((b) % 8))))
|
||||
|
||||
#define mbsn_bitfield_write(bf, b, v) \
|
||||
(((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & !(0x1 << ((b) % 8)))))
|
||||
(((bf)[(b) / 8]) = ((v) ? (((bf)[(b) / 8]) | (0x1 << ((b) % 8))) : (((bf)[(b) / 8]) & ~(0x1 << ((b) % 8)))))
|
||||
|
||||
|
||||
typedef enum mbsn_transport {
|
||||
@ -57,9 +57,16 @@ typedef struct mbsn_callbacks {
|
||||
|
||||
|
||||
typedef struct mbsn_t {
|
||||
// Private fields
|
||||
uint8_t msg_buf[260];
|
||||
uint16_t msg_buf_idx;
|
||||
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;
|
||||
|
||||
@ -73,6 +80,9 @@ typedef struct mbsn_t {
|
||||
} mbsn_t;
|
||||
|
||||
|
||||
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,
|
||||
@ -102,6 +112,10 @@ mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t qu
|
||||
|
||||
mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_t quantity, const uint16_t* registers);
|
||||
|
||||
mbsn_error mbsn_send_raw_pdu(mbsn_t* mbsn, uint8_t fc, const void* data, uint32_t data_len);
|
||||
|
||||
mbsn_error mbsn_receive_raw_pdu_response(mbsn_t* mbsn, void* data_out, uint32_t data_out_len);
|
||||
|
||||
const char* mbsn_strerror(int error);
|
||||
|
||||
|
||||
|
||||
@ -2,10 +2,11 @@ cmake_minimum_required(VERSION 3.21)
|
||||
project(modbusino_tests C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0")
|
||||
|
||||
include_directories(. ..)
|
||||
|
||||
add_definitions(-DMODBUSINO_USE_PRINTF)
|
||||
add_definitions(-DMBSN_DEBUG)
|
||||
|
||||
add_executable(modbusino_tests ../modbusino.c modbusino_tests.c)
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
#include "modbusino_tests.h"
|
||||
#include "modbusino.h"
|
||||
#include <assert.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
int read_byte_empty(uint8_t* b, int32_t timeout) {
|
||||
return 0;
|
||||
}
|
||||
@ -135,16 +137,128 @@ void test_server_receive_base(mbsn_transport transport) {
|
||||
}
|
||||
|
||||
|
||||
mbsn_error read_coils_and_error(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) {}
|
||||
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;
|
||||
|
||||
mbsn_error read_coils_and_no_error(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) {}
|
||||
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);
|
||||
}
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
void test_fc1(mbsn_transport transport) {
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION on calling a FC with unregistered callback");
|
||||
uint8_t raw_res[260];
|
||||
|
||||
start_client_and_server(transport, (mbsn_callbacks){});
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(mbsn_read_coils(&CLIENT, 1, 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");
|
||||
assert(mbsn_read_coils(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 2000");
|
||||
assert(mbsn_read_coils(&CLIENT, 1, 2001, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 65535");
|
||||
assert(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, 1, (uint16_t[]){htons(1), htons(0)}, 4));
|
||||
assert(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, 1, (uint16_t[]){htons(1), htons(2001)}, 4));
|
||||
assert(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 > 65535");
|
||||
check(mbsn_send_raw_pdu(&CLIENT, 1, (uint16_t[]){htons(65530), htons(7)}, 4));
|
||||
assert(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");
|
||||
assert(mbsn_read_coils(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_SERVER_DEVICE_FAILURE);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS if returned by server handler");
|
||||
assert(mbsn_read_coils(&CLIENT, 2, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS);
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE if returned by server handler");
|
||||
assert(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));
|
||||
assert(mbsn_bitfield_read(bf, 0) == 1);
|
||||
assert(mbsn_bitfield_read(bf, 1) == 0);
|
||||
assert(mbsn_bitfield_read(bf, 2) == 1);
|
||||
|
||||
stop_client_and_server();
|
||||
}
|
||||
|
||||
|
||||
void test_fc2(mbsn_transport transport) {
|
||||
uint8_t raw_res[260];
|
||||
|
||||
start_client_and_server(transport, (mbsn_callbacks){});
|
||||
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side");
|
||||
assert(mbsn_read_discrete_inputs(&CLIENT, 1, 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");
|
||||
assert(mbsn_read_discrete_inputs(&CLIENT, 1, 0, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with quantity > 2000");
|
||||
assert(mbsn_read_discrete_inputs(&CLIENT, 1, 2001, NULL) == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
should("immediately return MBSN_ERROR_INVALID_ARGUMENT when calling with address + quantity > 65535");
|
||||
assert(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, 2, (uint16_t[]){htons(1), htons(0)}, 4));
|
||||
assert(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, 2, (uint16_t[]){htons(1), htons(2001)}, 4));
|
||||
assert(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 > 65535");
|
||||
check(mbsn_send_raw_pdu(&CLIENT, 2, (uint16_t[]){htons(65530), htons(7)}, 4));
|
||||
assert(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");
|
||||
assert(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");
|
||||
assert(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");
|
||||
assert(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));
|
||||
assert(mbsn_bitfield_read(bf, 0) == 1);
|
||||
assert(mbsn_bitfield_read(bf, 1) == 0);
|
||||
assert(mbsn_bitfield_read(bf, 2) == 1);
|
||||
|
||||
stop_client_and_server();
|
||||
}
|
||||
|
||||
@ -155,8 +269,7 @@ const char* transports_str[2] = {"TCP", "RTU"};
|
||||
|
||||
void for_transports(void (*test_fn)(mbsn_transport), const char* should_str) {
|
||||
for (int t = 0; t < sizeof(transports) / sizeof(mbsn_transport); t++) {
|
||||
printf("%s: ", transports_str[t]);
|
||||
should(should_str);
|
||||
printf("Should %s on %s:\n", should_str, transports_str[t]);
|
||||
test(test_fn(transports[t]));
|
||||
}
|
||||
}
|
||||
@ -170,5 +283,8 @@ int main() {
|
||||
|
||||
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");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -16,6 +16,10 @@
|
||||
|
||||
#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))
|
||||
*/
|
||||
|
||||
const uint8_t TEST_SERVER_ADDR = 1;
|
||||
|
||||
@ -174,7 +178,8 @@ void* server_listen_thread() {
|
||||
if (is_server_listen_thread_stopped())
|
||||
break;
|
||||
|
||||
check(mbsn_server_receive(&SERVER));
|
||||
mbsn_error err = mbsn_server_receive(&SERVER);
|
||||
check(err);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -207,6 +212,9 @@ void start_client_and_server(mbsn_transport transport, mbsn_callbacks server_cal
|
||||
mbsn_set_read_timeout(&SERVER, 1000);
|
||||
mbsn_set_byte_timeout(&SERVER, 100);
|
||||
|
||||
mbsn_set_read_timeout(&CLIENT, -1);
|
||||
mbsn_set_byte_timeout(&CLIENT, 100);
|
||||
|
||||
assert(pthread_mutex_lock(&server_stopped_m) == 0);
|
||||
server_stopped = false;
|
||||
assert(pthread_mutex_unlock(&server_stopped_m) == 0);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user