Minimum functionality
This commit is contained in:
parent
2d87d6451b
commit
1951b1d923
124
modbusino.c
124
modbusino.c
@ -25,16 +25,19 @@
|
||||
(m)->msg_buf_idx++
|
||||
|
||||
#ifdef MBSN_BIG_ENDIAN
|
||||
#define get_2(m) ((m)->msg_buf[(m)->msg_buf[(m)->msg_buf_idx]); \
|
||||
#define get_2(m) \
|
||||
(*(uint16_t*) ((m)->msg_buf + (m)->msg_buf_idx)); \
|
||||
(m)->msg_buf_idx += 2
|
||||
#define put_2(m, w) (m)->msg_buf(m)[(m)->msg_buf_idx]] = (w); \
|
||||
#define put_2(m, w) \
|
||||
(*(uint16_t*) ((m)->msg_buf + (m)->msg_buf_idx)) = (w); \
|
||||
(m)->msg_buf_idx += 2
|
||||
#else
|
||||
#define get_2(m) \
|
||||
((m)->msg_buf[(m)->msg_buf_idx] >> 8 | (m)->msg_buf[(m)->msg_buf_idx] << 8); \
|
||||
((uint16_t) ((m)->msg_buf[(m)->msg_buf_idx + 1])) | (((uint16_t) (m)->msg_buf[(m)->msg_buf_idx] << 8)); \
|
||||
(m)->msg_buf_idx += 2
|
||||
#define put_2(m, w) \
|
||||
(m)->msg_buf[(m)->msg_buf_idx] = ((w) >> 8 | (w) << 8); \
|
||||
(m)->msg_buf[(m)->msg_buf_idx] = ((uint8_t) ((((uint16_t) (w)) & 0xFF00) >> 8)); \
|
||||
(m)->msg_buf[(m)->msg_buf_idx + 1] = ((uint8_t) (((uint16_t) (w)) & 0x00FF)); \
|
||||
(m)->msg_buf_idx += 2
|
||||
#endif
|
||||
|
||||
@ -62,7 +65,7 @@ static msg_state res_from_req(msg_state* req) {
|
||||
|
||||
|
||||
static msg_state req_create(uint8_t unit_id, uint8_t fc) {
|
||||
static uint16_t TID = 0;
|
||||
static uint16_t TID = 0x0102;
|
||||
|
||||
msg_state req = msg_state_create();
|
||||
req.unit_id = unit_id;
|
||||
@ -76,7 +79,7 @@ static msg_state req_create(uint8_t unit_id, uint8_t fc) {
|
||||
}
|
||||
|
||||
|
||||
int mbsn_create(mbsn_t* mbsn, mbsn_transport_conf transport_conf) {
|
||||
int mbsn_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf) {
|
||||
if (!mbsn)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
@ -85,26 +88,28 @@ int mbsn_create(mbsn_t* mbsn, mbsn_transport_conf transport_conf) {
|
||||
mbsn->byte_timeout_ms = -1;
|
||||
mbsn->read_timeout_ms = -1;
|
||||
|
||||
mbsn->transport = transport_conf.transport;
|
||||
if (mbsn->transport != MBSN_TRANSPORT_RTU && mbsn->transport != MBSN_TRANSPORT_TCP)
|
||||
if (!platform_conf)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (!transport_conf.read_byte || !transport_conf.write_byte)
|
||||
if (platform_conf->transport != MBSN_TRANSPORT_RTU && platform_conf->transport != MBSN_TRANSPORT_TCP)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mbsn->transport_read_byte = transport_conf.read_byte;
|
||||
mbsn->transport_write_byte = transport_conf.write_byte;
|
||||
if (!platform_conf->read_byte || !platform_conf->write_byte || !platform_conf->sleep)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
mbsn->platform = *platform_conf;
|
||||
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
|
||||
|
||||
mbsn_error mbsn_client_create(mbsn_t* mbsn, mbsn_transport_conf transport_conf) {
|
||||
return mbsn_create(mbsn, transport_conf);
|
||||
mbsn_error mbsn_client_create(mbsn_t* mbsn, const mbsn_platform_conf* platform_conf) {
|
||||
return mbsn_create(mbsn, platform_conf);
|
||||
}
|
||||
|
||||
|
||||
mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address, mbsn_transport_conf transport_conf,
|
||||
mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address, const mbsn_platform_conf* transport_conf,
|
||||
mbsn_callbacks callbacks) {
|
||||
if (address == 0)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
@ -130,8 +135,8 @@ void mbsn_set_byte_timeout(mbsn_t* mbsn, int32_t timeout_ms) {
|
||||
}
|
||||
|
||||
|
||||
void mbsn_client_set_server_address_rtu(mbsn_t* mbsn, uint8_t address) {
|
||||
mbsn->server_dest_address_rtu = address;
|
||||
void mbsn_set_destination_rtu_address(mbsn_t* mbsn, uint8_t address) {
|
||||
mbsn->dest_address_rtu = address;
|
||||
}
|
||||
|
||||
|
||||
@ -161,7 +166,7 @@ static void reset_msg_buf(mbsn_t* mbsn) {
|
||||
static mbsn_error recv(mbsn_t* mbsn, uint32_t count) {
|
||||
int r = 0;
|
||||
while (r != count) {
|
||||
int ret = mbsn->transport_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);
|
||||
if (ret == 0)
|
||||
return MBSN_ERROR_TIMEOUT;
|
||||
else if (ret != 1)
|
||||
@ -176,7 +181,7 @@ static mbsn_error recv(mbsn_t* mbsn, uint32_t count) {
|
||||
|
||||
static mbsn_error send(mbsn_t* mbsn) {
|
||||
for (int i = 0; i < mbsn->msg_buf_idx; i++) {
|
||||
int ret = mbsn->transport_write_byte(mbsn->msg_buf[i], mbsn->read_timeout_ms);
|
||||
int ret = mbsn->platform.write_byte(mbsn->msg_buf[i], mbsn->read_timeout_ms);
|
||||
if (ret == 0)
|
||||
return MBSN_ERROR_TIMEOUT;
|
||||
else if (ret != 1)
|
||||
@ -197,7 +202,7 @@ static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_by
|
||||
if (first_byte_received)
|
||||
*first_byte_received = false;
|
||||
|
||||
if (mbsn->transport == MBSN_TRANSPORT_RTU) {
|
||||
if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) {
|
||||
mbsn_error err = recv(mbsn, 1);
|
||||
|
||||
mbsn->byte_timeout_ms = old_byte_timeout;
|
||||
@ -224,7 +229,7 @@ static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_by
|
||||
|
||||
s_out->fc = get_1(mbsn);
|
||||
}
|
||||
else if (mbsn->transport == MBSN_TRANSPORT_TCP) {
|
||||
else if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) {
|
||||
mbsn_error err = recv(mbsn, 1);
|
||||
|
||||
mbsn->byte_timeout_ms = old_byte_timeout;
|
||||
@ -235,12 +240,17 @@ static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_by
|
||||
if (first_byte_received)
|
||||
*first_byte_received = true;
|
||||
|
||||
// Advance buf_idx
|
||||
get_1(mbsn);
|
||||
|
||||
err = recv(mbsn, 7);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
// Starting over
|
||||
reset_msg_buf(mbsn);
|
||||
|
||||
s_out->transaction_id = get_2(mbsn);
|
||||
uint16_t protocol_id = get_2(mbsn);
|
||||
uint16_t length = get_2(mbsn); // We should actually check the length of the request against this value
|
||||
s_out->unit_id = get_1(mbsn);
|
||||
@ -258,13 +268,15 @@ static mbsn_error recv_msg_header(mbsn_t* mbsn, msg_state* s_out, bool* first_by
|
||||
|
||||
|
||||
static mbsn_error recv_msg_footer(mbsn_t* mbsn) {
|
||||
if (mbsn->transport == MBSN_TRANSPORT_RTU) {
|
||||
if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) {
|
||||
uint16_t crc = crc_calc(mbsn->msg_buf, mbsn->msg_buf_idx);
|
||||
|
||||
mbsn_error err = recv(mbsn, 2);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
uint16_t recv_crc = get_2(mbsn);
|
||||
uint16_t crc = crc_calc(mbsn->msg_buf, mbsn->msg_buf_idx);
|
||||
|
||||
if (recv_crc != crc)
|
||||
return MBSN_ERROR_TRANSPORT;
|
||||
}
|
||||
@ -273,17 +285,17 @@ static mbsn_error recv_msg_footer(mbsn_t* mbsn) {
|
||||
}
|
||||
|
||||
|
||||
static void send_msg_header(mbsn_t* mbsn, msg_state* s, uint8_t data_length) {
|
||||
static void send_msg_header(mbsn_t* mbsn, msg_state* s, uint16_t data_length) {
|
||||
reset_msg_buf(mbsn);
|
||||
|
||||
if (mbsn->transport == MBSN_TRANSPORT_RTU) {
|
||||
if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) {
|
||||
put_1(mbsn, s->unit_id);
|
||||
}
|
||||
else if (mbsn->transport == MBSN_TRANSPORT_TCP) {
|
||||
else if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) {
|
||||
put_2(mbsn, s->transaction_id);
|
||||
put_2(mbsn, 0);
|
||||
put_2(mbsn, 1 + 1 + data_length);
|
||||
put_2(mbsn, s->unit_id);
|
||||
put_2(mbsn, (uint16_t) (1 + 1 + data_length));
|
||||
put_1(mbsn, s->unit_id);
|
||||
}
|
||||
|
||||
put_1(mbsn, s->fc);
|
||||
@ -291,7 +303,7 @@ static void send_msg_header(mbsn_t* mbsn, msg_state* s, uint8_t data_length) {
|
||||
|
||||
|
||||
static mbsn_error send_msg_footer(mbsn_t* mbsn) {
|
||||
if (mbsn->transport == MBSN_TRANSPORT_RTU) {
|
||||
if (mbsn->platform.transport == MBSN_TRANSPORT_RTU) {
|
||||
uint16_t crc = crc_calc(mbsn->msg_buf, mbsn->msg_buf_idx);
|
||||
put_2(mbsn, crc);
|
||||
}
|
||||
@ -303,7 +315,7 @@ static mbsn_error send_msg_footer(mbsn_t* mbsn) {
|
||||
|
||||
static mbsn_error handle_exception(mbsn_t* mbsn, msg_state* req, uint8_t exception) {
|
||||
msg_state res = *req;
|
||||
req->fc += 0x80;
|
||||
res.fc += 0x80;
|
||||
|
||||
send_msg_header(mbsn, &res, 1);
|
||||
put_1(mbsn, exception);
|
||||
@ -318,14 +330,14 @@ static mbsn_error handle_read_discrete(mbsn_t* mbsn, msg_state* req,
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t quantity = get_2(mbsn);
|
||||
|
||||
err = recv_msg_footer(mbsn);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
if (!req->ignored) {
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t quantity = get_2(mbsn);
|
||||
|
||||
if (quantity < 1 || quantity > 2000)
|
||||
return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||
|
||||
@ -374,14 +386,14 @@ static mbsn_error handle_read_registers(mbsn_t* mbsn, msg_state* req,
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t quantity = get_2(mbsn);
|
||||
|
||||
err = recv_msg_footer(mbsn);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
if (!req->ignored) {
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t quantity = get_2(mbsn);
|
||||
|
||||
if (quantity < 1 || quantity > 125)
|
||||
return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||
|
||||
@ -449,14 +461,14 @@ static mbsn_error handle_write_single_coil(mbsn_t* mbsn, msg_state* req) {
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t value = get_2(mbsn);
|
||||
|
||||
err = recv_msg_footer(mbsn);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
if (!req->ignored) {
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t value = get_2(mbsn);
|
||||
|
||||
if (value != 0 && value != 0xFF00)
|
||||
return handle_exception(mbsn, req, MBSN_EXCEPTION_ILLEGAL_DATA_VALUE);
|
||||
|
||||
@ -495,14 +507,14 @@ static mbsn_error handle_write_single_register(mbsn_t* mbsn, msg_state* req) {
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t value = get_2(mbsn);
|
||||
|
||||
err = recv_msg_footer(mbsn);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
if (!req->ignored) {
|
||||
uint16_t address = get_2(mbsn);
|
||||
uint16_t value = get_2(mbsn);
|
||||
|
||||
if (mbsn->callbacks.write_single_register) {
|
||||
err = mbsn->callbacks.write_single_register(address, value);
|
||||
if (err != MBSN_ERROR_NONE) {
|
||||
@ -790,14 +802,30 @@ static mbsn_error recv_res_header(mbsn_t* mbsn, msg_state* req, msg_state* res_o
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
if (mbsn->platform.transport == MBSN_TRANSPORT_TCP) {
|
||||
if (res_out->transaction_id != req->transaction_id)
|
||||
return MBSN_ERROR_INVALID_RESPONSE;
|
||||
}
|
||||
|
||||
if (res_out->ignored)
|
||||
return MBSN_ERROR_INVALID_RESPONSE;
|
||||
|
||||
if (res_out->fc != req->fc)
|
||||
if (res_out->fc != req->fc) {
|
||||
if (res_out->fc - 0x80 == req->fc) {
|
||||
err = recv(mbsn, 1);
|
||||
if (err != MBSN_ERROR_NONE)
|
||||
return err;
|
||||
|
||||
uint8_t exception = get_1(mbsn);
|
||||
if (exception < 1 || exception > 4)
|
||||
return MBSN_ERROR_INVALID_RESPONSE;
|
||||
else
|
||||
return exception;
|
||||
}
|
||||
else {
|
||||
return MBSN_ERROR_INVALID_RESPONSE;
|
||||
}
|
||||
}
|
||||
|
||||
return MBSN_ERROR_NONE;
|
||||
}
|
||||
@ -807,7 +835,7 @@ static mbsn_error read_discrete(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint
|
||||
if (quantity < 1 || quantity > 2000)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
msg_state req = req_create(mbsn->server_dest_address_rtu, fc);
|
||||
msg_state req = req_create(mbsn->dest_address_rtu, fc);
|
||||
send_msg_header(mbsn, &req, 4);
|
||||
|
||||
put_2(mbsn, address);
|
||||
@ -858,7 +886,7 @@ mbsn_error read_registers(mbsn_t* mbsn, uint8_t fc, uint16_t address, uint16_t q
|
||||
if (quantity < 1 || quantity > 125)
|
||||
return MBSN_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
msg_state req = req_create(mbsn->server_dest_address_rtu, fc);
|
||||
msg_state req = req_create(mbsn->dest_address_rtu, fc);
|
||||
send_msg_header(mbsn, &req, 4);
|
||||
|
||||
put_2(mbsn, address);
|
||||
@ -909,7 +937,7 @@ mbsn_error mbsn_read_input_registers(mbsn_t* mbsn, uint16_t address, uint16_t qu
|
||||
|
||||
|
||||
mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) {
|
||||
msg_state req = req_create(mbsn->server_dest_address_rtu, 5);
|
||||
msg_state req = req_create(mbsn->dest_address_rtu, 5);
|
||||
send_msg_header(mbsn, &req, 4);
|
||||
|
||||
put_2(mbsn, address);
|
||||
@ -946,7 +974,7 @@ mbsn_error mbsn_write_single_coil(mbsn_t* mbsn, uint16_t address, bool value) {
|
||||
|
||||
|
||||
mbsn_error mbsn_write_single_register(mbsn_t* mbsn, uint16_t address, uint16_t value) {
|
||||
msg_state req = req_create(mbsn->server_dest_address_rtu, 5);
|
||||
msg_state req = req_create(mbsn->dest_address_rtu, 5);
|
||||
send_msg_header(mbsn, &req, 4);
|
||||
|
||||
put_2(mbsn, address);
|
||||
@ -988,7 +1016,7 @@ mbsn_error mbsn_write_multiple_coils(mbsn_t* mbsn, uint16_t address, uint16_t qu
|
||||
|
||||
uint8_t coils_bytes = (quantity / 8) + 1;
|
||||
|
||||
msg_state req = req_create(mbsn->server_dest_address_rtu, 15);
|
||||
msg_state req = req_create(mbsn->dest_address_rtu, 15);
|
||||
send_msg_header(mbsn, &req, 5 + coils_bytes);
|
||||
|
||||
put_2(mbsn, address);
|
||||
@ -1035,7 +1063,7 @@ mbsn_error mbsn_write_multiple_registers(mbsn_t* mbsn, uint16_t address, uint16_
|
||||
|
||||
uint8_t registers_bytes = quantity * 2;
|
||||
|
||||
msg_state req = req_create(mbsn->server_dest_address_rtu, 16);
|
||||
msg_state req = req_create(mbsn->dest_address_rtu, 16);
|
||||
send_msg_header(mbsn, &req, 5 + registers_bytes);
|
||||
|
||||
put_2(mbsn, address);
|
||||
|
||||
29
modbusino.h
29
modbusino.h
@ -36,23 +36,12 @@ typedef enum mbsn_transport {
|
||||
MBSN_TRANSPORT_TCP = 2,
|
||||
} mbsn_transport;
|
||||
|
||||
typedef struct mbsn_transport_conf {
|
||||
typedef struct mbsn_platform_conf {
|
||||
mbsn_transport transport;
|
||||
int (*read_byte)(uint8_t* b, int32_t);
|
||||
int (*write_byte)(uint8_t b, int32_t);
|
||||
} mbsn_transport_conf;
|
||||
|
||||
|
||||
typedef struct {
|
||||
char* vendor_name;
|
||||
char* product_code;
|
||||
char* major_minor_revision;
|
||||
char* vendor_uri;
|
||||
char* product_name;
|
||||
char* model_name;
|
||||
char* user_application_name;
|
||||
char* extended;
|
||||
} mbsn_device_identification_data_t;
|
||||
void (*sleep)(uint32_t milliseconds);
|
||||
} mbsn_platform_conf;
|
||||
|
||||
|
||||
typedef struct mbsn_callbacks {
|
||||
@ -77,25 +66,23 @@ typedef struct mbsn_t {
|
||||
int32_t byte_timeout_ms;
|
||||
int32_t read_timeout_ms;
|
||||
|
||||
mbsn_transport transport;
|
||||
mbsn_error (*transport_read_byte)(uint8_t*, int32_t);
|
||||
mbsn_error (*transport_write_byte)(uint8_t, int32_t);
|
||||
mbsn_platform_conf platform;
|
||||
|
||||
uint8_t address_rtu;
|
||||
uint8_t server_dest_address_rtu;
|
||||
uint8_t dest_address_rtu;
|
||||
} mbsn_t;
|
||||
|
||||
|
||||
mbsn_error mbsn_client_create(mbsn_t* mbsn, mbsn_transport_conf transport_conf);
|
||||
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, mbsn_transport_conf transport_conf,
|
||||
mbsn_error mbsn_server_create(mbsn_t* mbsn, uint8_t address, const mbsn_platform_conf* platform_conf,
|
||||
mbsn_callbacks callbacks);
|
||||
|
||||
void mbsn_set_read_timeout(mbsn_t* mbsn, int32_t timeout_ms);
|
||||
|
||||
void mbsn_set_byte_timeout(mbsn_t* mbsn, int32_t timeout_ms);
|
||||
|
||||
void mbsn_client_set_server_address_rtu(mbsn_t* mbsn, uint8_t address);
|
||||
void mbsn_set_destination_rtu_address(mbsn_t* mbsn, uint8_t address);
|
||||
|
||||
mbsn_error mbsn_server_receive(mbsn_t* mbsn);
|
||||
|
||||
|
||||
@ -9,3 +9,4 @@ add_definitions(-DMODBUSINO_USE_PRINTF)
|
||||
|
||||
add_executable(modbusino_tests ../modbusino.c modbusino_tests.c)
|
||||
|
||||
target_link_libraries(modbusino_tests pthread)
|
||||
|
||||
@ -4,72 +4,110 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int read_byte_empty(uint8_t* b, int32_t timeout) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int write_byte_empty(uint8_t b, int32_t timeout) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void test_server_create(mbsn_transport transport) {
|
||||
mbsn_t mbsn;
|
||||
mbsn_error err;
|
||||
|
||||
mbsn_transport_conf transport_conf_empty = {.transport = transport,
|
||||
mbsn_platform_conf platform_conf_empty = {.transport = transport,
|
||||
.read_byte = read_byte_empty,
|
||||
.write_byte = write_byte_empty};
|
||||
.write_byte = write_byte_empty,
|
||||
.sleep = platform_sleep};
|
||||
|
||||
should("create a modbus server");
|
||||
reset(mbsn);
|
||||
err = mbsn_server_create(&mbsn, TEST_SERVER_ADDR, transport_conf_empty, (mbsn_callbacks){});
|
||||
err = mbsn_server_create(&mbsn, TEST_SERVER_ADDR, &platform_conf_empty, (mbsn_callbacks){});
|
||||
check(err);
|
||||
|
||||
should("check parameters and fail to create a modbus server");
|
||||
reset(mbsn);
|
||||
err = mbsn_server_create(NULL, TEST_SERVER_ADDR, transport_conf_empty, (mbsn_callbacks){});
|
||||
err = mbsn_server_create(NULL, TEST_SERVER_ADDR, &platform_conf_empty, (mbsn_callbacks){});
|
||||
expect(err == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
reset(mbsn);
|
||||
err = mbsn_server_create(&mbsn, 0, transport_conf_empty, (mbsn_callbacks){});
|
||||
err = mbsn_server_create(&mbsn, 0, &platform_conf_empty, (mbsn_callbacks){});
|
||||
expect(err == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
reset(mbsn);
|
||||
mbsn_transport_conf t = transport_conf_empty;
|
||||
t.transport = 3;
|
||||
err = mbsn_server_create(&mbsn, 0, t, (mbsn_callbacks){});
|
||||
mbsn_platform_conf p = platform_conf_empty;
|
||||
p.transport = 3;
|
||||
err = mbsn_server_create(&mbsn, 0, &p, (mbsn_callbacks){});
|
||||
expect(err == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
reset(mbsn);
|
||||
t = transport_conf_empty;
|
||||
t.read_byte = NULL;
|
||||
err = mbsn_server_create(&mbsn, 0, t, (mbsn_callbacks){});
|
||||
p = platform_conf_empty;
|
||||
p.read_byte = NULL;
|
||||
err = mbsn_server_create(&mbsn, 0, &p, (mbsn_callbacks){});
|
||||
expect(err == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
|
||||
reset(mbsn);
|
||||
t = transport_conf_empty;
|
||||
t.write_byte = NULL;
|
||||
err = mbsn_server_create(&mbsn, 0, t, (mbsn_callbacks){});
|
||||
p = platform_conf_empty;
|
||||
p.write_byte = NULL;
|
||||
err = mbsn_server_create(&mbsn, 0, &p, (mbsn_callbacks){});
|
||||
expect(err == MBSN_ERROR_INVALID_ARGUMENT);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
int read_byte_timeout(uint8_t* b, int32_t timeout) {
|
||||
usleep(timeout * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int read_byte_timeout_third(uint8_t* b, int32_t timeout) {
|
||||
static int stage = 0;
|
||||
switch (stage) {
|
||||
case 0:
|
||||
case 1:
|
||||
*b = 1;
|
||||
stage++;
|
||||
return 1;
|
||||
case 2:
|
||||
assert(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 mbsn;
|
||||
mbsn_t server;
|
||||
mbsn_error err;
|
||||
mbsn_transport_conf transport_conf;
|
||||
mbsn_platform_conf platform_conf;
|
||||
|
||||
|
||||
should("honor read_timeout and return normally");
|
||||
reset(mbsn);
|
||||
transport_conf.transport = transport;
|
||||
transport_conf.read_byte = read_byte_timeout_1s;
|
||||
transport_conf.write_byte = write_byte_empty;
|
||||
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(&mbsn, TEST_SERVER_ADDR, transport_conf, (mbsn_callbacks){});
|
||||
err = mbsn_server_create(&server, TEST_SERVER_ADDR, &platform_conf, (mbsn_callbacks){});
|
||||
check(err);
|
||||
|
||||
mbsn_set_read_timeout(&mbsn, read_timeout_ms);
|
||||
mbsn_set_byte_timeout(&mbsn, -1);
|
||||
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_receive(&mbsn);
|
||||
err = mbsn_server_receive(&server);
|
||||
check(err);
|
||||
|
||||
uint64_t diff = now_ms() - start;
|
||||
@ -77,37 +115,60 @@ void test_server_receive_base(mbsn_transport transport) {
|
||||
assert(diff >= read_timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
should("honor byte_timeout and return MBSN_ERROR_TIMEOUT");
|
||||
reset(mbsn);
|
||||
transport_conf.transport = transport;
|
||||
transport_conf.read_byte = read_byte_timeout_third;
|
||||
transport_conf.write_byte = write_byte_empty;
|
||||
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(&mbsn, TEST_SERVER_ADDR, transport_conf, (mbsn_callbacks){});
|
||||
err = mbsn_server_create(&server, TEST_SERVER_ADDR, &platform_conf, (mbsn_callbacks){});
|
||||
check(err);
|
||||
|
||||
mbsn_set_read_timeout(&mbsn, 1000);
|
||||
mbsn_set_byte_timeout(&mbsn, byte_timeout_ms);
|
||||
mbsn_set_read_timeout(&server, 1000);
|
||||
mbsn_set_byte_timeout(&server, byte_timeout_ms);
|
||||
|
||||
err = mbsn_server_receive(&mbsn);
|
||||
err = mbsn_server_receive(&server);
|
||||
expect(err == MBSN_ERROR_TIMEOUT);
|
||||
}
|
||||
|
||||
|
||||
mbsn_error read_coils_and_error(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) {}
|
||||
|
||||
|
||||
mbsn_error read_coils_and_no_error(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out) {}
|
||||
|
||||
|
||||
void test_fc1(mbsn_transport transport) {
|
||||
should("return MBSN_EXCEPTION_ILLEGAL_FUNCTION on calling a FC with unregistered callback");
|
||||
start_client_and_server(transport, (mbsn_callbacks){});
|
||||
assert(mbsn_read_coils(&CLIENT, 1, 1, NULL) == MBSN_EXCEPTION_ILLEGAL_FUNCTION);
|
||||
stop_client_and_server();
|
||||
}
|
||||
|
||||
|
||||
mbsn_transport transports[2] = {MBSN_TRANSPORT_TCP, MBSN_TRANSPORT_RTU};
|
||||
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);
|
||||
test(test_fn(transports[t]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main() {
|
||||
should("create a RTU modbus server");
|
||||
test(test_server_create(MBSN_TRANSPORT_TCP));
|
||||
|
||||
should("create a TCP modbus server");
|
||||
test(test_server_create(MBSN_TRANSPORT_TCP));
|
||||
for_transports(test_server_create, "create a modbus server");
|
||||
|
||||
should("receive no messages as RTU server without failing");
|
||||
test(test_server_receive_base(MBSN_TRANSPORT_RTU));
|
||||
for_transports(test_server_receive_base, "receive no messages without failing");
|
||||
|
||||
should("receive no messages as TCP server without failing");
|
||||
test(test_server_receive_base(MBSN_TRANSPORT_TCP));
|
||||
for_transports(test_fc1, "send and receive FC 01 (0x01) Read Coils");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,20 +1,14 @@
|
||||
#include "modbusino.h"
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
unsigned int nesting = 0;
|
||||
|
||||
#define should(s) \
|
||||
{ \
|
||||
for (int i = 0; i < nesting; i++) { \
|
||||
printf("\t"); \
|
||||
} \
|
||||
printf("Should %s\n", (s)); \
|
||||
}
|
||||
|
||||
#define expect(c) assert(c)
|
||||
#define expect(c) (assert(c))
|
||||
|
||||
#define check(err) (expect((err) == MBSN_ERROR_NONE))
|
||||
|
||||
@ -26,6 +20,23 @@ unsigned int nesting = 0;
|
||||
const uint8_t TEST_SERVER_ADDR = 1;
|
||||
|
||||
|
||||
unsigned int nesting = 0;
|
||||
|
||||
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;
|
||||
|
||||
|
||||
#define should(s) \
|
||||
for (int i = 0; i < nesting; i++) { \
|
||||
printf("\t"); \
|
||||
} \
|
||||
printf("Should %s\n", (s))
|
||||
|
||||
|
||||
uint64_t now_ms() {
|
||||
struct timespec ts = {0, 0};
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||||
@ -33,37 +44,171 @@ uint64_t now_ms() {
|
||||
}
|
||||
|
||||
|
||||
int read_byte_empty(uint8_t* b, int32_t timeout) {
|
||||
return 0;
|
||||
void platform_sleep(uint32_t milliseconds) {
|
||||
usleep(milliseconds * 1000);
|
||||
}
|
||||
|
||||
|
||||
int write_byte_empty(uint8_t b, int32_t timeout) {
|
||||
return 0;
|
||||
void reset_sockets() {
|
||||
if (sockets[0] != -1)
|
||||
close(sockets[0]);
|
||||
|
||||
if (sockets[1] != -1)
|
||||
close(sockets[1]);
|
||||
|
||||
assert(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0);
|
||||
}
|
||||
|
||||
|
||||
int read_byte_timeout_1s(uint8_t* b, int32_t timeout) {
|
||||
usleep(timeout * 1000);
|
||||
return 0;
|
||||
int read_byte_fd(int fd, uint8_t* b, int32_t timeout_ms) {
|
||||
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 read_byte_timeout_third(uint8_t* b, int32_t timeout) {
|
||||
static int stage = 0;
|
||||
switch (stage) {
|
||||
case 0:
|
||||
case 1:
|
||||
*b = 1;
|
||||
stage++;
|
||||
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;
|
||||
case 2:
|
||||
assert(timeout > 0);
|
||||
usleep(timeout * 1000 + 100 * 1000);
|
||||
stage = 0;
|
||||
return 0;
|
||||
default:
|
||||
stage = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int write_byte_fd(int fd, uint8_t b, int32_t timeout_ms) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
int read_byte_pipe_server(uint8_t* b, int32_t timeout_ms) {
|
||||
return read_byte_fd(sockets[0], b, timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
int write_byte_pipe_server(uint8_t b, int32_t timeout_ms) {
|
||||
return write_byte_fd(sockets[0], b, timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
int read_byte_pipe_client(uint8_t* b, int32_t timeout_ms) {
|
||||
return read_byte_fd(sockets[1], b, timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
int write_byte_pipe_client(uint8_t b, int32_t timeout_ms) {
|
||||
return write_byte_fd(sockets[1], b, timeout_ms);
|
||||
}
|
||||
|
||||
|
||||
mbsn_platform_conf mbsn_platform_conf_server;
|
||||
mbsn_platform_conf* platform_conf_pipe_server(mbsn_transport transport) {
|
||||
mbsn_platform_conf_server.transport = transport;
|
||||
mbsn_platform_conf_server.read_byte = read_byte_pipe_server;
|
||||
mbsn_platform_conf_server.write_byte = write_byte_pipe_server;
|
||||
mbsn_platform_conf_server.sleep = platform_sleep;
|
||||
return &mbsn_platform_conf_server;
|
||||
}
|
||||
|
||||
|
||||
mbsn_platform_conf mbsn_platform_conf_client;
|
||||
mbsn_platform_conf* platform_conf_pipe_client(mbsn_transport transport) {
|
||||
mbsn_platform_conf_client.transport = transport;
|
||||
mbsn_platform_conf_client.read_byte = read_byte_pipe_client;
|
||||
mbsn_platform_conf_client.write_byte = write_byte_pipe_client;
|
||||
mbsn_platform_conf_client.sleep = platform_sleep;
|
||||
return &mbsn_platform_conf_client;
|
||||
}
|
||||
|
||||
|
||||
bool is_server_listen_thread_stopped() {
|
||||
bool stopped = false;
|
||||
assert(pthread_mutex_lock(&server_stopped_m) == 0);
|
||||
stopped = server_stopped;
|
||||
assert(pthread_mutex_unlock(&server_stopped_m) == 0);
|
||||
return stopped;
|
||||
}
|
||||
|
||||
|
||||
void* server_listen_thread() {
|
||||
while (true) {
|
||||
if (is_server_listen_thread_stopped())
|
||||
break;
|
||||
|
||||
check(mbsn_server_receive(&SERVER));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void stop_client_and_server() {
|
||||
if (!is_server_listen_thread_stopped()) {
|
||||
assert(pthread_mutex_lock(&server_stopped_m) == 0);
|
||||
server_stopped = true;
|
||||
assert(pthread_mutex_unlock(&server_stopped_m) == 0);
|
||||
assert(pthread_join(server_thread, NULL) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void start_client_and_server(mbsn_transport transport, mbsn_callbacks server_callbacks) {
|
||||
assert(pthread_mutex_destroy(&server_stopped_m) == 0);
|
||||
assert(pthread_mutex_init(&server_stopped_m, NULL) == 0);
|
||||
|
||||
reset_sockets();
|
||||
|
||||
reset(SERVER);
|
||||
reset(CLIENT);
|
||||
|
||||
check(mbsn_server_create(&SERVER, TEST_SERVER_ADDR, platform_conf_pipe_server(transport), server_callbacks));
|
||||
check(mbsn_client_create(&CLIENT, platform_conf_pipe_client(transport)));
|
||||
|
||||
mbsn_set_destination_rtu_address(&CLIENT, TEST_SERVER_ADDR);
|
||||
mbsn_set_read_timeout(&SERVER, 1000);
|
||||
mbsn_set_byte_timeout(&SERVER, 100);
|
||||
|
||||
assert(pthread_mutex_lock(&server_stopped_m) == 0);
|
||||
server_stopped = false;
|
||||
assert(pthread_mutex_unlock(&server_stopped_m) == 0);
|
||||
assert(pthread_create(&server_thread, NULL, server_listen_thread, &SERVER) == 0);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user