diff --git a/CMakeLists.txt b/CMakeLists.txt index 651005e..c133d2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,10 +13,12 @@ add_library(nanomodbus nanomodbus.c) target_include_directories(nanomodbus PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) if (BUILD_EXAMPLES) - add_executable(client-tcp examples/linux/client-tcp.c) - target_link_libraries(client-tcp nanomodbus) - add_executable(server-tcp examples/linux/server-tcp.c) - target_link_libraries(server-tcp nanomodbus) + if(!WIN32) + add_executable(client-tcp examples/linux/client-tcp.c) + target_link_libraries(client-tcp nanomodbus) + add_executable(server-tcp examples/linux/server-tcp.c) + target_link_libraries(server-tcp nanomodbus) + endif() endif () if (BUILD_TESTS) @@ -33,6 +35,10 @@ if (BUILD_TESTS) target_compile_definitions(multi_server_rtu PUBLIC NMBS_DEBUG) target_link_libraries(multi_server_rtu pthread) + if(WIN32) + target_link_libraries(nanomodbus_tests ws2_32 ) + endif() + enable_testing() add_test(NAME test_general COMMAND $) add_test(NAME test_server_disabled COMMAND $) diff --git a/README.md b/README.md index a68fd00..b3a71a7 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Its main features are: - 21 (0x15) Write File Record - 23 (0x17) Read/Write Multiple registers - 43/14 (0x2B/0x0E) Read Device Identification + - 24 (0x18) Read Fifo Queue - Platform-agnostic - Requires only C99 and its standard library - Data transport read/write functions are implemented by the user @@ -198,6 +199,7 @@ examples. - `NMBS_SERVER_WRITE_FILE_RECORD_DISABLED` - `NMBS_SERVER_READ_WRITE_REGISTERS_DISABLED` - `NMBS_SERVER_READ_DEVICE_IDENTIFICATION_DISABLED` + - `NMBS_SERVER_READ_FIFO_QUEUE` - `NMBS_STRERROR_DISABLED` to disable the code that converts `nmbs_error`s to strings - `NMBS_BITFIELD_MAX` to set the size of the `nmbs_bitfield` type, used to store coil values ( default is `2000`) diff --git a/nanomodbus.c b/nanomodbus.c index 3f99314..b4cd18d 100644 --- a/nanomodbus.c +++ b/nanomodbus.c @@ -900,6 +900,39 @@ nmbs_error recv_read_device_identification_res(nmbs_t* nmbs, uint8_t buffers_cou return recv_msg_footer(nmbs); } +nmbs_error recv_read_fifo_queue_registers(nmbs_t* nmbs, uint16_t* quantity, uint16_t* registers) { + nmbs_error err = recv_res_header(nmbs); + if (err != NMBS_ERROR_NONE) + return err; + + err = recv(nmbs, 2); + if (err != NMBS_ERROR_NONE) + return err; + + const uint8_t registers_bytes = get_2(nmbs); + NMBS_DEBUG_PRINT("b %d\t", registers_bytes); + + if (registers_bytes > 62) + return NMBS_ERROR_INVALID_RESPONSE; + + err = recv(nmbs, registers_bytes); + if (err != NMBS_ERROR_NONE) + return err; + + NMBS_DEBUG_PRINT("regs "); + for (int i = 0; i < registers_bytes / 2; i++) { + uint16_t reg = get_2(nmbs); + if (registers) + registers[i] = reg; + NMBS_DEBUG_PRINT("%d ", reg); + } + err = recv_msg_footer(nmbs); + if (err != NMBS_ERROR_NONE) + return err; + + *quantity = registers_bytes / 2; + return NMBS_ERROR_NONE; +} #ifndef NMBS_SERVER_DISABLED #if !defined(NMBS_SERVER_READ_COILS_DISABLED) || !defined(NMBS_SERVER_READ_DISCRETE_INPUTS_DISABLED) @@ -1805,6 +1838,71 @@ static nmbs_error handle_read_device_identification(nmbs_t* nmbs) { } #endif +#ifndef NMBS_SERVER_READ_FIFO_QUEUE +static nmbs_error handle_read_fifo_queue(nmbs_t* nmbs) { + + nmbs_error err = recv(nmbs, 2); + if (err != NMBS_ERROR_NONE) + return err; + + + const int32_t fifo_pointer_address = get_2(nmbs); + + + NMBS_DEBUG_PRINT("a %d\tq 0", fifo_pointer_address); + + err = recv_msg_footer(nmbs); + if (err != NMBS_ERROR_NONE) + return err; + + if (!nmbs->msg.ignored) { + // trange condition check + // MODBUS Application Protocol Specification + if ((fifo_pointer_address < 0) || (fifo_pointer_address > 65535)) + return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + if (nmbs->callbacks.read_fifo) { + uint16_t regs[32] = {0}; + uint16_t fifo_sz = 0; + err = nmbs->callbacks.read_fifo((uint16_t) fifo_pointer_address, regs, &fifo_sz, nmbs->msg.unit_id, + nmbs->callbacks.arg); + if (err != NMBS_ERROR_NONE) { + if (nmbs_error_is_exception(err)) + return send_exception_msg(nmbs, err); + + return send_exception_msg(nmbs, NMBS_EXCEPTION_SERVER_DEVICE_FAILURE); + } + + if (!nmbs->msg.broadcast) { + const uint8_t regs_bytes = fifo_sz * 2; + put_res_header(nmbs, 2 + regs_bytes); + + put_2(nmbs, regs_bytes); + + NMBS_DEBUG_PRINT("b %d\t", regs_bytes); + + NMBS_DEBUG_PRINT("regs "); + for (int i = 0; i < fifo_sz; i++) { + put_2(nmbs, regs[i]); + NMBS_DEBUG_PRINT("%d ", regs[i]); + } + + err = send_msg(nmbs); + if (err != NMBS_ERROR_NONE) + return err; + } + } + else { + return send_exception_msg(nmbs, NMBS_EXCEPTION_ILLEGAL_FUNCTION); + } + } + else { + //return recv_read_registers_res(nmbs, quantity, NULL); + } + + return NMBS_ERROR_NONE; +} +#endif static nmbs_error handle_req_fc(nmbs_t* nmbs) { NMBS_DEBUG_PRINT("fc %d\t", nmbs->msg.fc); @@ -1882,6 +1980,13 @@ static nmbs_error handle_req_fc(nmbs_t* nmbs) { err = handle_read_device_identification(nmbs); break; #endif + +#ifndef NMBS_SERVER_READ_FIFO_QUEUE_DISABLED + case 24: + err = handle_read_fifo_queue(nmbs); + break; +#endif + default: nmbs->platform.flush(nmbs, nmbs->platform.arg); if (!nmbs->msg.ignored) @@ -2411,6 +2516,26 @@ nmbs_error nmbs_receive_raw_pdu_response(nmbs_t* nmbs, uint8_t* data_out, uint8_ return NMBS_ERROR_NONE; } + +nmbs_error nmbs_read_fifo_queue(nmbs_t* nmbs, uint16_t address, uint16_t* read_quantity, uint16_t* registers_out) { + + + // if ((uint32_t) address > ((uint32_t) 0xFFFF)) + // return NMBS_ERROR_INVALID_ARGUMENT; + + msg_state_req(nmbs, 24); + put_req_header(nmbs, 2); + + put_2(nmbs, address); + + NMBS_DEBUG_PRINT("a %d\tq %d ", address, 0); + + const nmbs_error err = send_msg(nmbs); + if (err != NMBS_ERROR_NONE) + return err; + + return recv_read_fifo_queue_registers(nmbs, read_quantity, registers_out); +} #endif diff --git a/nanomodbus.h b/nanomodbus.h index d913917..f57061d 100644 --- a/nanomodbus.h +++ b/nanomodbus.h @@ -236,6 +236,11 @@ typedef struct nmbs_callbacks { #endif #endif +#ifndef NMBS_SERVER_READ_FIFO_QUEUE + nmbs_error (*read_fifo_sz)(uint16_t address); + nmbs_error (*read_fifo)(uint16_t address, uint16_t* registers_out, uint16_t* fifo_size, uint8_t unit_id, void* arg); +#endif + void* arg; // User data, will be passed to functions above uint32_t initialized; // Reserved, workaround for older user code not calling nmbs_callbacks_create() } nmbs_callbacks; @@ -536,6 +541,17 @@ nmbs_error nmbs_send_raw_pdu(nmbs_t* nmbs, uint8_t fc, const uint8_t* data, uint * @return NMBS_ERROR_NONE if successful, other errors otherwise. */ nmbs_error nmbs_receive_raw_pdu_response(nmbs_t* nmbs, uint8_t* data_out, uint8_t data_out_len); + +/* Send a FC 24 (0x18) Read fifo queues + * @param nmbs pointer to the nmbs_t instance + * @param address starting address + * @param quantity quantity of read registers + * @param registers_out array where the registers will be stored + * + * @return NMBS_ERROR_NONE if successful, other errors otherwise. + */ + +nmbs_error nmbs_read_fifo_queue(nmbs_t* nmbs, uint16_t address, uint16_t* read_quantity, uint16_t* registers_out); #endif /** Calculate the Modbus CRC of some data. @@ -554,6 +570,7 @@ uint16_t nmbs_crc_calc(const uint8_t* data, uint32_t length, void* arg); const char* nmbs_strerror(nmbs_error error); #endif + #ifdef __cplusplus } // extern "C" #endif diff --git a/tests/multi_server_rtu.c b/tests/multi_server_rtu.c index a739065..5565176 100644 --- a/tests/multi_server_rtu.c +++ b/tests/multi_server_rtu.c @@ -1,3 +1,16 @@ +#ifdef _WIN32 +#include +#include +#endif +#ifdef _WIN32 +#include +typedef SOCKET socket_t; +#else +#include +#include +#include +#endif + #include #include #include diff --git a/tests/nanomodbus_tests.c b/tests/nanomodbus_tests.c index d9bd00b..0aec103 100644 --- a/tests/nanomodbus_tests.c +++ b/tests/nanomodbus_tests.c @@ -1,12 +1,21 @@ #include "nanomodbus_tests.h" +#include "nanomodbus.h" +#if defined(_WIN32) || defined(_WIN64) +#else #include +#endif + #include #include #include + +// #define NMBS_DEBUG + static int64_t callbacks_user_data = -64; + uint8_t check_user_data(void* data) { return *((int64_t*) data) == callbacks_user_data; } @@ -1457,9 +1466,134 @@ void test_fc43_14(nmbs_transport transport) { stop_client_and_server(); } +// #define fifo_max_size 31 +enum SZ { fifo_max_size = 31 }; +static uint16_t server_fifo_sz = 0; +static uint16_t fifo_head_adr = 0x0000; +static uint16_t server_regs[fifo_max_size] = {10, 11, 12, 13, 14, 15, 16, 17, 10, 11, 12, 13, 14, 15, 16, 17, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +nmbs_error server_read_fifo(uint16_t address, uint16_t* registers, uint16_t* size, uint8_t unit_id, void* arg) { + + UNUSED_PARAM(arg); + UNUSED_PARAM(unit_id); + + if (check_user_data(arg) != 1) + return NMBS_EXCEPTION_SERVER_DEVICE_FAILURE; + + // Condition check from + // MODBUS Application Protocol Specification + if (address != fifo_head_adr) + return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS; + // Condition check from + // MODBUS Application Protocol Specification + if (!(server_fifo_sz <= fifo_max_size)) + return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE; + + registers[0] = *size = server_fifo_sz; + + for (int i = 0; i < *size; i++) + registers[i + 1] = server_regs[i]; + + + for (int i = 0; i < fifo_max_size; i++) { + if (i < (fifo_max_size - *size)) + server_regs[i] = server_regs[i + *size]; + else + server_regs[i] = 0; + } + + *size += 1; + + return NMBS_ERROR_NONE; +} + +void test_fc24(nmbs_transport transport) { + const uint8_t fc = 24; + uint16_t raw_res[260]; + uint16_t quantity_read = 0; + nmbs_callbacks callbacks_empty; + nmbs_callbacks_create(&callbacks_empty); + start_client_and_server(transport, &callbacks_empty); + should("return NMBS_EXCEPTION_ILLEGAL_FUNCTION when callback is not registered server-side"); + expect(nmbs_read_fifo_queue(&CLIENT, 0, &quantity_read, NULL) == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + // nmbs_error err = NMBS_ERROR_NONE; + // err = nmbs_read_holding_registers(&CLIENT, 0, 1, raw_res); + // err = nmbs_read_fifo_queue(&CLIENT, 0, &quantity_read, raw_res); + // printf("err %d\n", err); + // asm("NOP"); + // expect(err == NMBS_EXCEPTION_ILLEGAL_FUNCTION); + stop_client_and_server(); + + nmbs_callbacks callbacks; + nmbs_callbacks_create(&callbacks); + callbacks.read_fifo = server_read_fifo; + start_client_and_server(transport, &callbacks); + nmbs_set_callbacks_arg(&SERVER, (void*) &callbacks_user_data); + + // NMBS_ERROR_INVALID_ARGUMENT not need + + should("immediately return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS when calling with address 1(is not a haed if fifo " + "queue) "); + expect(nmbs_read_fifo_queue(&CLIENT, 1, 0, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + server_fifo_sz = 33; + expect(nmbs_read_fifo_queue(&CLIENT, 0, 0, NULL) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_VALUE from server when calling with quantity 0"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint8_t*) (uint16_t[]) {htons(0)}, 2)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, (uint8_t*) raw_res, 4) == NMBS_EXCEPTION_ILLEGAL_DATA_VALUE); + + + should("return NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS from server when calling with address 1(is not a haed if fifo"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint8_t*) (uint16_t[]) {htons(0xFFFF)}, 2)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, (uint8_t*) raw_res, 4) == NMBS_EXCEPTION_ILLEGAL_DATA_ADDRESS); + + server_fifo_sz = 8; + uint16_t regs[16]; + should("read with no error"); + check(nmbs_read_fifo_queue(&CLIENT, 0, &quantity_read, regs)); + expect((regs[0] == server_fifo_sz) && (quantity_read == (server_fifo_sz + 1))); + expect(regs[1] == 10); + expect(regs[2] == 11); + expect(regs[3] == 12); + expect(regs[4] == 13); + expect(regs[5] == 14); + expect(regs[6] == 15); + expect(regs[7] == 16); + expect(regs[8] == 17); + + server_fifo_sz = 0; + if (transport == NMBS_TRANSPORT_RTU) { + nmbs_set_destination_rtu_address(&CLIENT, NMBS_BROADCAST_ADDRESS); + + should("receive no response when sending to broadcast address"); + expect(nmbs_read_fifo_queue(&CLIENT, 0, &quantity_read, regs) == NMBS_ERROR_TIMEOUT); + + should("receive no response when sending invalid request to broadcast address"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint8_t*) (uint16_t[]) {htons(0)}, 2)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, (uint8_t*) raw_res, 4) == NMBS_ERROR_TIMEOUT); + + should("receive no response when sending valid request to broadcast address"); + check(nmbs_send_raw_pdu(&CLIENT, fc, (uint8_t*) (uint16_t[]) {htons(0)}, 2)); + expect(nmbs_receive_raw_pdu_response(&CLIENT, (uint8_t*) raw_res, 4) == NMBS_ERROR_TIMEOUT); + } + + stop_client_and_server(); +} + + nmbs_transport transports[2] = {NMBS_TRANSPORT_RTU, NMBS_TRANSPORT_TCP}; const char* transports_str[2] = {"RTU", "TCP"}; +// nmbs_transport transports[1] = {NMBS_TRANSPORT_TCP}; +// const char* transports_str[1] = {"TCP"}; + +// nmbs_transport transports[1] = {NMBS_TRANSPORT_RTU}; +// const char* transports_str[1] = {"RTU"}; + + void for_transports(void (*test_fn)(nmbs_transport), const char* should_str) { for (unsigned long t = 0; t < sizeof(transports) / sizeof(nmbs_transport); t++) { printf("Should %s on %s:\n", should_str, transports_str[t]); @@ -1498,6 +1632,6 @@ int main(int argc, char* argv[]) { for_transports(test_fc23, "send and receive FC 23 (0x17) Read/Write Multiple Registers"); for_transports(test_fc43_14, "send and receive FC 43 / 14 (0x2B / 0x0E) Read Device Identification"); - + for_transports(test_fc24, "send and receive FC 24 (0x18) Read Fifo Queue Registers"); return 0; } diff --git a/tests/nanomodbus_tests.h b/tests/nanomodbus_tests.h index 1566894..75e0fbd 100644 --- a/tests/nanomodbus_tests.h +++ b/tests/nanomodbus_tests.h @@ -1,18 +1,34 @@ + #include "nanomodbus.h" #undef NDEBUG + +#if defined(_WIN32) || defined(_WIN64) +#include +#include +#endif + +#if defined(_WIN32) || defined(_WIN64) +#include +typedef SOCKET socket_t; +#else +#include +#include +#include +#endif + #include #include #include +#include #include #include -#include -#include #include #ifdef __APPLE__ typedef suseconds_t __suseconds_t; #endif #include + #define expect(expr) assert(expr) #define check(err) (expect((err) == NMBS_ERROR_NONE)) @@ -23,11 +39,16 @@ typedef suseconds_t __suseconds_t; #define UNUSED_PARAM(x) ((x) = (x)) - const uint8_t TEST_SERVER_ADDR = 1; +unsigned int nesting = 0; +#ifdef _WIN32 +WSADATA wsaData; +SOCKET socks[2]; +int create_socket_pair(SOCKET socks[2]); +#else +#endif -unsigned int nesting = 0; int sockets[2] = {-1, -1}; @@ -45,9 +66,20 @@ nmbs_t CLIENT, SERVER; uint64_t now_ms(void) { + uint64_t milliseconds; + +#if defined(_WIN32) + LARGE_INTEGER frequency, counter; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&counter); + milliseconds = (counter.QuadPart * 1000000LL) / frequency.QuadPart; +#else struct timespec ts = {0, 0}; clock_gettime(CLOCK_MONOTONIC_RAW, &ts); - return (uint64_t) (ts.tv_sec) * 1000 + (uint64_t) (ts.tv_nsec) / 1000000; + milliseconds = ts.tv_sec * 1000LL + (uint64_t) ts.tv_nsec / 1000000; +#endif + //return (uint64_t) (ts.tv_sec) * 1000 + (uint64_t) (ts.tv_nsec) / 1000000; + return milliseconds; } @@ -58,7 +90,13 @@ void reset_sockets(void) { if (sockets[1] != -1) close(sockets[1]); +#ifdef _WIN32 + expect(create_socket_pair(socks) == 0); + sockets[0] = socks[0]; + sockets[1] = socks[1]; +#else expect(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0); +#endif } @@ -74,7 +112,7 @@ int32_t read_fd(int fd, uint8_t* buf, uint16_t count, int32_t timeout_ms) { if (timeout_ms >= 0) { tv_p = &tv; tv.tv_sec = timeout_ms / 1000; - tv.tv_usec = ((__suseconds_t) timeout_ms % 1000) * 1000; + tv.tv_usec = ((int32_t) timeout_ms % 1000) * 1000; } int ret = select(fd + 1, &rfds, NULL, NULL, tv_p); @@ -83,7 +121,11 @@ int32_t read_fd(int fd, uint8_t* buf, uint16_t count, int32_t timeout_ms) { } if (ret == 1) { +#ifdef _WIN32 + ssize_t r = recv(fd, (char*) buf + total, 1, 0); +#else ssize_t r = read(fd, buf + total, 1); +#endif if (r <= 0) return -1; @@ -109,7 +151,7 @@ int32_t write_fd(int fd, const uint8_t* buf, uint16_t count, int32_t timeout_ms) if (timeout_ms >= 0) { tv_p = &tv; tv.tv_sec = timeout_ms / 1000; - tv.tv_usec = ((__suseconds_t) timeout_ms % 1000) * 1000; + tv.tv_usec = ((int32_t) timeout_ms % 1000) * 1000; } int ret = select(fd + 1, NULL, &wfds, NULL, tv_p); @@ -118,7 +160,11 @@ int32_t write_fd(int fd, const uint8_t* buf, uint16_t count, int32_t timeout_ms) } if (ret == 1) { +#ifdef _WIN32 + ssize_t w = send(fd, (const char*) buf + total, count, 0); +#else ssize_t w = write(fd, buf + total, count); +#endif if (w <= 0) return -1; @@ -190,7 +236,6 @@ void* server_listen_thread(void* arg) { while (true) { if (is_server_listen_thread_stopped()) break; - check(nmbs_server_poll(&SERVER)); } @@ -231,3 +276,36 @@ void start_client_and_server(nmbs_transport transport, const nmbs_callbacks* ser expect(pthread_mutex_unlock(&server_stopped_m) == 0); expect(pthread_create(&server_thread, NULL, server_listen_thread, &SERVER) == 0); } + +#if defined(_WIN32) || defined(_WIN64) +int create_socket_pair(SOCKET socks[2]) { + + WSACleanup(); + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + printf("WSAStartup failed.\n"); + return -1; + } + + SOCKET listener = socket(AF_INET, SOCK_STREAM, 0); + if (listener == INVALID_SOCKET) + return -1; + + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_port = 0; + + bind(listener, (struct sockaddr*) &addr, sizeof(addr)); + listen(listener, 1); + + int addrlen = sizeof(addr); + getsockname(listener, (struct sockaddr*) &addr, &addrlen); + + socks[0] = socket(AF_INET, SOCK_STREAM, 0); + connect(socks[0], (struct sockaddr*) &addr, sizeof(addr)); + + socks[1] = accept(listener, NULL, NULL); + closesocket(listener); + return 0; +} +#endif \ No newline at end of file