diff --git a/modbusino.c b/modbusino.c index 3bc6bbf..86b93df 100644 --- a/modbusino.c +++ b/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 (res_out->transaction_id != req->transaction_id) - return MBSN_ERROR_INVALID_RESPONSE; + 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) - return MBSN_ERROR_INVALID_RESPONSE; + 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); diff --git a/modbusino.h b/modbusino.h index d429338..f93245b 100644 --- a/modbusino.h +++ b/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); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 683359a..f1dddf9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -9,3 +9,4 @@ add_definitions(-DMODBUSINO_USE_PRINTF) add_executable(modbusino_tests ../modbusino.c modbusino_tests.c) +target_link_libraries(modbusino_tests pthread) diff --git a/tests/modbusino_tests.c b/tests/modbusino_tests.c index 69679f6..ec0c5af 100644 --- a/tests/modbusino_tests.c +++ b/tests/modbusino_tests.c @@ -4,72 +4,110 @@ #include #include +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, - .read_byte = read_byte_empty, - .write_byte = write_byte_empty}; + mbsn_platform_conf platform_conf_empty = {.transport = transport, + .read_byte = read_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; } diff --git a/tests/modbusino_tests.h b/tests/modbusino_tests.h index 2f66ee7..71b4efc 100644 --- a/tests/modbusino_tests.h +++ b/tests/modbusino_tests.h @@ -1,20 +1,14 @@ #include "modbusino.h" #include +#include #include +#include +#include +#include #include #include -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++; - return 1; - case 2: - assert(timeout > 0); - usleep(timeout * 1000 + 100 * 1000); - stage = 0; - return 0; - default: - stage = 0; + 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(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); +}