From 95b11722dab00eb138e0b70bf99cf042c81b153d Mon Sep 17 00:00:00 2001 From: Valerio De Benedetto Date: Tue, 18 Jan 2022 04:29:09 +0100 Subject: [PATCH] First commit --- .clang-format | 65 +++++ .gitignore | 3 + modbusino.c | 613 ++++++++++++++++++++++++++++++++++++++++ modbusino.h | 110 +++++++ modbusino_platform.h | 47 +++ tests/CMakeLists.txt | 11 + tests/modbusino_tests.c | 27 ++ 7 files changed, 876 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 modbusino.c create mode 100644 modbusino.h create mode 100644 modbusino_platform.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/modbusino_tests.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f7b0192 --- /dev/null +++ b/.clang-format @@ -0,0 +1,65 @@ +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: None +AlignOperands: Align +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterUnion: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +ColumnLimit: 120 +CompactNamespaces: false +ContinuationIndentWidth: 8 +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Left +ReflowComments: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 4 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Never \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..012da87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +/tests/cmake-build* +/tests/build* diff --git a/modbusino.c b/modbusino.c new file mode 100644 index 0000000..4fa408a --- /dev/null +++ b/modbusino.c @@ -0,0 +1,613 @@ +#include "modbusino.h" +#include "modbusino_platform.h" +#include +#include +#include + +#if !defined(MBSN_BIG_ENDIAN) && !defined(MBSN_LITTLE_ENDIAN) +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || defined(__BIG_ENDIAN__) || defined(__ARMEB__) || \ + defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) +#define MBSN_BIG_ENDIAN +#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \ + defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) +#define MBSN_LITTLE_ENDIAN +#else +#error "Failed to automatically detect system endianness. Please define either MBSN_BIG_ENDIAN or MBSN_LITTLE_ENDIAN" +"in modbusino_platform.h" +#endif +#endif + +#ifdef MBSN_BIG_ENDIAN +#define HTONS(x) void(0) +#define NTOHS(x) void(0) +#else +#define HTONS(x) (((x) >> 8) | ((x) << 8)); +#define NTOHS(x) (((x) >> 8) | ((x) << 8)); +#endif + +#define CLOCKS_PER_MS ((uint64_t) (CLOCKS_PER_SEC / 1000)); + +uint64_t clock_ms() { + return (uint64_t) clock() / CLOCKS_PER_MS; +} + +int mbsn_create(mbsn_t* mbsn, mbsn_transport transport) { + memset(mbsn, 0, sizeof(mbsn_t)); + + mbsn->byte_timeout_ms = -1; + mbsn->response_timeout_ms = -1; + + mbsn->transport = transport; + if (mbsn->transport == MBSN_TRANSPORT_RTU) { + mbsn->transport_read_byte = mbsn_rtu_read_byte; + mbsn->transport_write_byte = mbsn_rtu_write_byte; + } + else if (mbsn->transport == MBSN_TRANSPORT_TCP) { + mbsn->transport_read_byte = mbsn_tcp_read_byte; + mbsn->transport_write_byte = mbsn_tcp_write_byte; + } + else { + return MBSN_ERROR_INVALID_ARGUMENT; + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error mbsn_client_create(mbsn_t* mbsn, mbsn_transport transport) { + return mbsn_create(mbsn, transport); +} + + +mbsn_error mbsn_server_create(mbsn_t* mbsn, mbsn_transport transport, uint8_t address, mbsn_callbacks callbacks) { + mbsn_error ret = mbsn_create(mbsn, transport); + mbsn->address_rtu = address; + mbsn->callbacks = callbacks; + return ret; +} + + +void mbsn_set_byte_timeout(mbsn_t* mbsn, int64_t timeout_ms) { + mbsn->byte_timeout_ms = timeout_ms; +} + + +void mbsn_set_response_timeout(mbsn_t* mbsn, int64_t timeout_ms) { + mbsn->response_timeout_ms = timeout_ms; +} + + +void mbsn_client_set_server_address_rtu(mbsn_t* mbsn, uint8_t address) { + mbsn->server_dest_address_rtu = address; +} + + +/* +mbsn_error read(uint8_t* buf, uint64_t len, uint64_t byte_timeout_ms, mbsn_error (*transport_read_byte)(uint8_t*)) { + uint64_t now = clock_ms(); + int r = 0; + while (r != len) { + int ret = transport_read_byte(buf + r); + + if (byte_timeout_ms > 0 && clock_ms() - now >= byte_timeout_ms) + return MBSN_ERROR_TIMEOUT; + + if (ret == 0) + continue; + else if (ret != 1) + return MBSN_ERROR_TRANSPORT; + + r++; + } + + return 0; +} +*/ + +mbsn_error recv_n(mbsn_t* mbsn, uint8_t* buf, uint32_t count) { + //uint64_t start = clock_ms(); + int r = 0; + while (r != count) { + int ret = mbsn->transport_read_byte(buf + r); + + /* + if (mbsn->byte_timeout_ms > 0 && clock_ms() - start >= mbsn->byte_timeout_ms) + return MBSN_ERROR_TIMEOUT; + */ + + if (ret == 0) + continue; + else if (ret != 1) + return MBSN_ERROR_TRANSPORT; + + r++; + //start = clock_ms(); + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error recv_1(mbsn_t* mbsn, uint8_t* b) { + return recv_n(mbsn, b, 1); +} + + +mbsn_error recv_2(mbsn_t* mbsn, uint16_t* w) { + mbsn_error err = recv_n(mbsn, (uint8_t*) w, 2); + if (err != MBSN_ERROR_NONE) + return err; + + *w = NTOHS(*w); + return MBSN_ERROR_NONE; +} + +/* +void put_send_1(mbsn_t* mbsn, uint8_t w) { + mbsn->send_buf[mbsn->send_idx] = w; + mbsn->send_idx++; + assert(mbsn->send_idx <= SEND_BUF_SIZE); +} + + +void put_send_2(mbsn_t* mbsn, uint16_t w) { + mbsn->send_buf[mbsn->send_idx] = HTONS(w); + mbsn->send_idx += 2; + assert(mbsn->send_idx <= SEND_BUF_SIZE); +} + + +void put_send_n(mbsn_t* mbsn, uint8_t* buf, uint32_t n) { + assert(mbsn->send_idx + n <= SEND_BUF_SIZE); + memcpy(mbsn->send_buf + mbsn->send_idx, buf, n); + mbsn->send_idx += n; +} + + +mbsn_error send(mbsn_t* mbsn) { + const uint32_t target = mbsn->send_idx; + //uint64_t start = clock_ms(); + while (mbsn->send_idx != 0) { + mbsn->send_idx--; + + mbsn_error err = mbsn->transport_write_byte(mbsn->send_buf[target - mbsn->send_idx - 1]); + + if (mbsn->byte_timeout_ms > 0 && clock_ms() - start >= mbsn->byte_timeout_ms) + return MBSN_ERROR_TIMEOUT; + + if (err == 0) + continue; + else if (err != 1) + return MBSN_ERROR_TRANSPORT; + + //start = clock_ms(); + } + + return MBSN_ERROR_NONE; +} +*/ + + +mbsn_error send_n(mbsn_t* mbsn, uint8_t* buf, uint32_t count) { + int w = 0; + while (w != count) { + int ret = mbsn->transport_write_byte(buf[w]); + if (ret == 0) + continue; + else if (ret != 1) + return MBSN_ERROR_TRANSPORT; + + w++; + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error send_1(mbsn_t* mbsn, uint8_t b) { + return send_n(mbsn, &b, 1); +} + + +mbsn_error send_2(mbsn_t* mbsn, uint16_t w) { + w = HTONS(w); + return send_n(mbsn, (uint8_t*) &w, 2); +} + + +void handle_exception(mbsn_t* mbsn, uint16_t fc, uint8_t exception) { + send_1(mbsn, fc + 0x80); + send_1(mbsn, exception); +} + + +mbsn_error handle_read_discrete(mbsn_t* mbsn, uint16_t fc, bool ignored, bool broadcast, + mbsn_error (*callback)(uint16_t, uint16_t, mbsn_bitfield)) { + uint16_t addr; + mbsn_error err = recv_2(mbsn, &addr); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t quantity; + err = recv_2(mbsn, &quantity); + if (err != MBSN_ERROR_NONE) + return err; + + if (quantity < 1 || quantity > 2000) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if ((uint32_t) addr + (uint32_t) quantity > 65535) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (!ignored) { + if (callback) { + mbsn_bitfield bf; + err = callback(addr, quantity, bf); + if (err != MBSN_ERROR_NONE) + return err; + + if (!broadcast) { + err = send_1(mbsn, fc); + if (err != MBSN_ERROR_NONE) + return err; + + uint8_t discrete_bytes = (quantity / 8) + 1; + err = send_1(mbsn, discrete_bytes); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_n(mbsn, bf, discrete_bytes); + if (err != MBSN_ERROR_NONE) + return err; + } + } + else { + return MBSN_EXCEPTION_ILLEGAL_FUNCTION; + } + } +} + + +mbsn_error handle_read_registers(mbsn_t* mbsn, uint16_t fc, bool ignored, bool broadcast, + mbsn_error (*callback)(uint16_t, uint16_t, uint16_t*)) { + uint16_t addr; + mbsn_error err = recv_2(mbsn, &addr); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t quantity; + err = recv_2(mbsn, &quantity); + if (err != MBSN_ERROR_NONE) + return err; + + if (quantity < 1 || quantity > 125) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if ((uint32_t) addr + (uint32_t) quantity > 65535) + return MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS; + + if (!ignored) { + if (callback) { + uint16_t regs[125]; + err = callback(addr, quantity, regs); + if (err != MBSN_ERROR_NONE) + return err; + + for (int i = 0; i < quantity; i++) + regs[i] = HTONS(regs[i]); + + if (!broadcast) { + send_1(mbsn, fc); + + uint8_t regs_bytes = quantity * 2; + send_1(mbsn, regs_bytes); + + err = send_n(mbsn, (uint8_t*) regs, regs_bytes); + if (err != MBSN_ERROR_NONE) + return err; + } + } + else { + return MBSN_EXCEPTION_ILLEGAL_FUNCTION; + } + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error handle_read_coils(mbsn_t* mbsn, bool ignored, bool broadcast) { + return handle_read_discrete(mbsn, 1, ignored, broadcast, mbsn->callbacks.read_coils); +} + + +mbsn_error handle_read_discrete_inputs(mbsn_t* mbsn, bool ignored, bool broadcast) { + return handle_read_discrete(mbsn, 2, ignored, broadcast, mbsn->callbacks.read_discrete_inputs); +} + + +mbsn_error handle_read_holding_registers(mbsn_t* mbsn, bool ignored, bool broadcast) { + return handle_read_registers(mbsn, 3, ignored, broadcast, mbsn->callbacks.read_holding_registers); +} + + +mbsn_error handle_read_input_registers(mbsn_t* mbsn, bool ignored, bool broadcast) { + return handle_read_registers(mbsn, 4, ignored, broadcast, mbsn->callbacks.read_input_registers); +} + + +mbsn_error handle_write_single_coil(mbsn_t* mbsn, bool ignored, bool broadcast) { + uint16_t addr; + mbsn_error err = recv_2(mbsn, &addr); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t value; + err = recv_2(mbsn, &value); + if (err != MBSN_ERROR_NONE) + return err; + + if (value != 0 && value != 0xFF00) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (!ignored) { + if (mbsn->callbacks.write_single_coil) { + err = mbsn->callbacks.write_single_coil(addr, value == 0 ? false : true); + if (err != MBSN_ERROR_NONE) + return err; + } + else { + return MBSN_EXCEPTION_ILLEGAL_FUNCTION; + } + } + + if (!broadcast) { + err = send_1(mbsn, 5); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_2(mbsn, addr); + if (err != MBSN_ERROR_NONE) + return err; + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error handle_write_single_register(mbsn_t* mbsn, bool ignored, bool broadcast) { + uint16_t addr; + mbsn_error err = recv_2(mbsn, &addr); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t value; + err = recv_2(mbsn, &value); + if (err != MBSN_ERROR_NONE) + return err; + + if (!ignored) { + if (mbsn->callbacks.write_single_register) { + err = mbsn->callbacks.write_single_register(addr, value); + if (err != MBSN_ERROR_NONE) + return err; + } + else { + return MBSN_EXCEPTION_ILLEGAL_FUNCTION; + } + } + + if (!broadcast) { + err = send_1(mbsn, 6); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_2(mbsn, addr); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_2(mbsn, value); + if (err != MBSN_ERROR_NONE) + return err; + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error handle_write_multiple_coils(mbsn_t* mbsn, bool ignored, bool broadcast) { + uint16_t addr; + mbsn_error err = recv_2(mbsn, &addr); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t quantity; + err = recv_2(mbsn, &quantity); + if (err != MBSN_ERROR_NONE) + return err; + + uint8_t coils_bytes; + err = recv_1(mbsn, &coils_bytes); + if (err != MBSN_ERROR_NONE) + return err; + + if (quantity < 1 || quantity > 0x07B0) // 0x07B0 == 1968 + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (coils_bytes == 0) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if ((quantity / 8) + 1 != coils_bytes) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + mbsn_bitfield coils; + err = recv_n(mbsn, coils, coils_bytes); + if (err != MBSN_ERROR_NONE) + return err; + + if (!ignored) { + if (mbsn->callbacks.write_multiple_coils) { + err = mbsn->callbacks.write_multiple_coils(addr, quantity, coils); + if (err != MBSN_ERROR_NONE) + return err; + } + else { + return MBSN_EXCEPTION_ILLEGAL_FUNCTION; + } + } + + if (!broadcast) { + err = send_1(mbsn, 15); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_2(mbsn, addr); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_2(mbsn, quantity); + if (err != MBSN_ERROR_NONE) + return err; + } + + return MBSN_ERROR_NONE; +} + + +mbsn_error handle_write_multiple_registers(mbsn_t* mbsn, bool ignored, bool broadcast) { + uint16_t addr; + mbsn_error err = recv_2(mbsn, &addr); + if (err != MBSN_ERROR_NONE) + return err; + + uint16_t quantity; + err = recv_2(mbsn, &quantity); + if (err != MBSN_ERROR_NONE) + return err; + + uint8_t registers_bytes; + err = recv_1(mbsn, ®isters_bytes); + if (err != MBSN_ERROR_NONE) + return err; + + if (quantity < 1 || quantity > 0x007B) // 0x007B == 123 + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (registers_bytes == 0) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + if (registers_bytes != quantity * 2) + return MBSN_EXCEPTION_ILLEGAL_DATA_VALUE; + + uint16_t registers[0x007B]; + err = recv_n(mbsn, (uint8_t*) registers, registers_bytes); + if (err != MBSN_ERROR_NONE) + return err; + + for (int i = 0; i < quantity; i++) + registers[i] = NTOHS(registers[i]); + + if (!ignored) { + if (mbsn->callbacks.write_multiple_registers) { + err = mbsn->callbacks.write_multiple_registers(addr, quantity, registers); + if (err != MBSN_ERROR_NONE) + return err; + } + else { + return MBSN_EXCEPTION_ILLEGAL_FUNCTION; + } + } + + if (!broadcast) { + err = send_1(mbsn, 16); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_2(mbsn, addr); + if (err != MBSN_ERROR_NONE) + return err; + + err = send_2(mbsn, quantity); + if (err != MBSN_ERROR_NONE) + return err; + } + + return MBSN_ERROR_NONE; +} + +int mbsn_server_receive(mbsn_t* mbsn) { + mbsn_error err = MBSN_ERROR_NONE; + uint8_t fc = 0; + bool broadcast = false; + bool ignored = false; + + if (mbsn->transport == MBSN_TRANSPORT_RTU) { + uint8_t id; + err = recv_1(mbsn, &id); + if (err != 0) + return err; + + // Check if request is for us + if (id == 0) + broadcast = true; + else if (id != mbsn->address_rtu) + ignored = true; + else + ignored = false; + + err = recv_1(mbsn, &fc); + if (err != MBSN_ERROR_NONE) + return err; + } + else if (mbsn->transport == MBSN_TRANSPORT_TCP) { + err = recv_1(mbsn, &fc); + if (err != MBSN_ERROR_NONE) + return err; + } + + switch (fc) { + case 1: + err = handle_read_coils(mbsn, ignored, broadcast); + break; + + case 2: + err = handle_read_discrete_inputs(mbsn, ignored, broadcast); + break; + + case 3: + err = handle_read_holding_registers(mbsn, ignored, broadcast); + break; + + case 4: + err = handle_read_input_registers(mbsn, ignored, broadcast); + break; + + case 5: + err = handle_write_single_coil(mbsn, ignored, broadcast); + break; + + case 6: + err = handle_write_single_register(mbsn, ignored, broadcast); + break; + + case 15: + err = handle_write_multiple_coils(mbsn, ignored, broadcast); + break; + + case 16: + err = handle_write_multiple_registers(mbsn, ignored, broadcast); + break; + + default: + err = MBSN_EXCEPTION_ILLEGAL_FUNCTION; + } + + if (err != MBSN_ERROR_NONE) { + if (!broadcast && !ignored && mbsn_error_is_exception(err)) + handle_exception(mbsn, fc, err); + else + return err; + } + + return MBSN_ERROR_NONE; +} \ No newline at end of file diff --git a/modbusino.h b/modbusino.h new file mode 100644 index 0000000..58c8368 --- /dev/null +++ b/modbusino.h @@ -0,0 +1,110 @@ +#ifndef MODBUSINO_H +#define MODBUSINO_H + +#include +#include + + +typedef enum mbsn_error { + // Library errors + MBSN_ERROR_TRANSPORT = -3, + MBSN_ERROR_TIMEOUT = -2, + MBSN_ERROR_INVALID_ARGUMENT = -1, + MBSN_ERROR_NONE = 0, + + // Modbus exceptions + MBSN_EXCEPTION_ILLEGAL_FUNCTION = 1, + MBSN_EXCEPTION_ILLEGAL_DATA_ADDRESS = 2, + MBSN_EXCEPTION_ILLEGAL_DATA_VALUE = 3, + MBSN_EXCEPTION_SERVER_DEVICE_FAILURE = 4, +} mbsn_error; + +#define mbsn_error_is_exception(e) ((e) > 0 && (e) < 5) + + +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))))) + + +typedef enum mbsn_transport { + MBSN_TRANSPORT_RTU = 1, + MBSN_TRANSPORT_TCP = 2, +} mbsn_transport; + + +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; + + +typedef struct { + mbsn_error (*read_coils)(uint16_t address, uint16_t quantity, mbsn_bitfield coils_out); + mbsn_error (*read_discrete_inputs)(uint16_t address, uint16_t quantity, mbsn_bitfield inputs_out); + mbsn_error (*read_holding_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out); + mbsn_error (*read_input_registers)(uint16_t address, uint16_t quantity, uint16_t* registers_out); + mbsn_error (*write_single_coil)(uint16_t address, bool value); + mbsn_error (*write_single_register)(uint16_t address, uint16_t value); + mbsn_error (*write_multiple_coils)(uint16_t address, uint16_t quantity, mbsn_bitfield coils); + mbsn_error (*write_multiple_registers)(uint16_t address, uint16_t quantity, uint16_t* registers); +} mbsn_callbacks; + + +typedef struct mbsn_t { + // Private fields + mbsn_callbacks callbacks; + + int64_t byte_timeout_ms; + int64_t response_timeout_ms; + + mbsn_transport transport; + mbsn_error (*transport_read_byte)(uint8_t*); + mbsn_error (*transport_write_byte)(uint8_t); + + uint8_t address_rtu; + uint8_t server_dest_address_rtu; +} mbsn_t; + + +mbsn_error mbsn_client_create(mbsn_t* mbsn, mbsn_transport transport); + +mbsn_error mbsn_server_create(mbsn_t* mbsn, mbsn_transport transport, uint8_t address, mbsn_callbacks callbacks); + +void mbsn_set_response_timeout(mbsn_t* mbsn, int64_t timeout_ms); + +void mbsn_set_byte_timeout(mbsn_t* mbsn, int64_t timeout_ms); + +void mbsn_client_set_server_address(mbsn_t* mbsn, uint16_t address); + +mbsn_error mbsn_server_poll(mbsn_t* mbsn); + +mbsn_error mbsn_read_coils(uint16_t address, uint16_t quantity, uint8_t* coils_out); + +mbsn_error mbsn_read_discrete_inputs(uint16_t address, uint16_t quantity, uint8_t* inputs_out); + +mbsn_error mbsn_read_holding_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out); + +mbsn_error mbsn_read_input_registers(uint16_t address, uint16_t quantity, uint16_t* registers_out); + +mbsn_error mbsn_write_single_coil(uint16_t address, uint8_t value); + +mbsn_error mbsn_write_single_register(uint16_t address, uint16_t value); + +mbsn_error mbsn_write_multiple_coils(uint16_t address, uint16_t quantity, const uint8_t* values); + +mbsn_error mbsn_write_multiple_registers(uint16_t address, uint16_t quantity, const uint16_t* values); + +const char* mbsn_strerror(int error); + + +#endif //MODBUSINO_H diff --git a/modbusino_platform.h b/modbusino_platform.h new file mode 100644 index 0000000..bc0b05d --- /dev/null +++ b/modbusino_platform.h @@ -0,0 +1,47 @@ +#ifndef MODBUSINO_MODBUSINO_PLATFORM_H +#define MODBUSINO_MODBUSINO_PLATFORM_H + +#include +#include + +/* ### Endianness macros. + * In most cases, endianness will be detected automatically, so they can be left commented +*/ +/* Uncomment if your platform is big endian */ +// #define MBSN_BIG_ENDIAN + +/* Uncomment if your platform is little endian */ +// #define MBSN_LITTLE_ENDIAN + + +/* ### Transport function pointers. + * Point them to your platform-specific methods that read/write data to/from a serial port or a TCP connection and + * flush their receive buffers. + * + * read()/write() methods should block until the requested byte is read/written. + * If your implementation uses a read/write timeout, and the timeout expires, the methods should return 0. + * Their return values should be: + * - 1 in case of success + * - 0 if no data is available immediately or after an internal timeout expiration + * - -1 in case of error + * + * The primary effect of flush() methods should be the flushing of the underlying receive buffer. + * These methods will be called in case of error, in order to "reset" the state of the connection. + * On most platforms + * + * You can leave some of them NULL if you don't plan to use a certain transport. + */ +int (*mbsn_rtu_flush)() = NULL; + +int (*mbsn_rtu_read_byte)(uint8_t* b) = NULL; + +int (*mbsn_rtu_write_byte)(uint8_t b) = NULL; + +int (*mbsn_tcp_flush)() = NULL; + +int (*mbsn_tcp_read_byte)(uint8_t* b) = NULL; + +int (*mbsn_tcp_write_byte)(uint8_t b) = NULL; + + +#endif //MODBUSINO_MODBUSINO_PLATFORM_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..683359a --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.21) +project(modbusino_tests C) + +set(CMAKE_C_STANDARD 99) + +include_directories(. ..) + +add_definitions(-DMODBUSINO_USE_PRINTF) + +add_executable(modbusino_tests ../modbusino.c modbusino_tests.c) + diff --git a/tests/modbusino_tests.c b/tests/modbusino_tests.c new file mode 100644 index 0000000..da6b616 --- /dev/null +++ b/tests/modbusino_tests.c @@ -0,0 +1,27 @@ +#include "modbusino.h" +#include +#include +#include + +int main() { + mbsn_t mbsn = {0}; + mbsn_error err = mbsn_client_create(&mbsn, MBSN_TRANSPORT_TCP); + assert(err == MBSN_ERROR_NONE); + + mbsn_bitfield bf; + memset(bf, 0, sizeof(bf)); + + mbsn_bitfield_write(bf, 7, true); + printf("%d\n", mbsn_bitfield_read(bf, 7)); + + mbsn_bitfield_write(bf, 22, true); + printf("%d\n", mbsn_bitfield_read(bf, 22)); + + mbsn_bitfield_write(bf, 0, true); + printf("%d\n", mbsn_bitfield_read(bf, 0)); + + mbsn_bitfield_write(bf, 24, true); + printf("%d\n", mbsn_bitfield_read(bf, 24)); + + return 0; +}