From bae72ef652affadbb0c0e0a0e20939cafbfee79e Mon Sep 17 00:00:00 2001 From: Michael Heimpold Date: Mon, 8 Jul 2024 10:31:29 +0200 Subject: [PATCH] SerialCommHub: several improvements (#731) * SerialCommHub: make retry count on error configurable At the moment, an automatic retry up to 2 times is done in case the Modbus operation fails. With some devices or in same use-cases, this is not desired, so let us make the retry count configurable via manifest. So mimic the current behavior and to retain backwards compatibility, let's use a default value of 2. While at, we slightly change the debug messages and unify the loop counting code. Signed-off-by: Michael Heimpold * SerialCommHub: fix fd usage on invalid values open returns -1 on error, any other value has to be considered a valid fd. So we have to initialize with -1 instead of zero (which is stdin usually). This must also be considered in the destructor to not close stdin by accident. Signed-off-by: Michael Heimpold * SerialCommHub: refactor error handling to prevent log flooding When devices do not respond or some other problems occur, then the the log output might be flooded with messages. To have some more central control over the log output, let's refactor the module: - introduce various exceptions and use then in places where previously the log output was directly created - introduce a common helper function which performs the Modbus function which also increases code sharing between different high-level callbacks - emit only a warning in case the last trial of all configured retries fails finally (so in case the call can be completed successfully within the configure retries, only debug messages are created, but no usual user visible output) Signed-off-by: Michael Heimpold * SerialCommHub: improve hexdump of received packet Signed-off-by: Michael Heimpold * SerialCommHub: optimize memory pre-allocation for result For registers, the byte_cnt should always be even. But for coils/inputs, the byte_cnt could be odd, so let's cover this by ensuring that we round upwards. Signed-off-by: Michael Heimpold * SerialCommHub: check for even byte count in response Since Modbus functions which operates on registers (which are defined as 16-bit wide) are expected to return full registers, we can and should check the received response for this. Signed-off-by: Michael Heimpold Signed-off-by: Moritz Barsnick * SerialCommHub: silence warning about unused return value of write Ignoring the return code of write results in a compiler warning as theoretically the write could return without having written the complete packet. So handle it accordingly. In case of error, we can throw an exception. Signed-off-by: Michael Heimpold * SerialCommHub: catch system error for failure to write This is assumed to be fatal and non-recoverable, therefore logging is protected against flooding. A code path for possible recovery is implemented nevertheless. This should be replaced by a proper error reporting and handling solution, yet the error interfaces and handling for this are not in place yet. Signed-off-by: Moritz Barsnick * SerialCommHub: fix reading beyond end of buffer When converting a reply with an odd number of bytes of data ("read coils" or "read discrete inputs"), the byte-to-word conversion was reading beyond the end of the response message buffer. This is fixed by checking for the number of remaining bytes, and copying only what is left. Signed-off-by: Moritz Barsnick --------- Signed-off-by: Michael Heimpold Signed-off-by: Moritz Barsnick Co-authored-by: Michael Heimpold Co-authored-by: Moritz Barsnick --- .../main/serial_communication_hubImpl.cpp | 164 ++++++-------- .../main/serial_communication_hubImpl.hpp | 9 +- modules/SerialCommHub/manifest.yaml | 6 + modules/SerialCommHub/tiny_modbus_rtu.cpp | 205 +++++++++++------- modules/SerialCommHub/tiny_modbus_rtu.hpp | 36 ++- 5 files changed, 243 insertions(+), 177 deletions(-) diff --git a/modules/SerialCommHub/main/serial_communication_hubImpl.cpp b/modules/SerialCommHub/main/serial_communication_hubImpl.cpp index 9a3887011..a8d4fc053 100644 --- a/modules/SerialCommHub/main/serial_communication_hubImpl.cpp +++ b/modules/SerialCommHub/main/serial_communication_hubImpl.cpp @@ -4,9 +4,11 @@ #include "serial_communication_hubImpl.hpp" #include +#include #include #include #include +#include #include namespace module { @@ -38,6 +40,8 @@ void serial_communication_hubImpl::init() { rxtx_gpio_settings.line_number = config.rxtx_gpio_line; rxtx_gpio_settings.inverted = config.rxtx_gpio_tx_high; + system_error_logged = false; + if (!modbus.open_device(config.serial_port, config.baudrate, config.ignore_echo, rxtx_gpio_settings, static_cast(config.parity), config.rtscts, milliseconds(config.initial_timeout_ms), milliseconds(config.within_message_timeout_ms))) { @@ -48,146 +52,102 @@ void serial_communication_hubImpl::init() { void serial_communication_hubImpl::ready() { } -// Commands - types::serial_comm_hub_requests::Result -serial_communication_hubImpl::handle_modbus_read_holding_registers(int& target_device_id, int& first_register_address, - int& num_registers_to_read) { - +serial_communication_hubImpl::perform_modbus_request(uint8_t device_address, tiny_modbus::FunctionCode function, + uint16_t first_register_address, uint16_t register_quantity, + bool wait_for_reply, std::vector request) { + std::scoped_lock lock(serial_mutex); types::serial_comm_hub_requests::Result result; std::vector response; - - { - std::scoped_lock lock(serial_mutex); - - auto retry_counter = this->num_resends_on_error; - while (retry_counter > 0) { - - EVLOG_debug << fmt::format("Try {} Call modbus_client->read_holding_register(id {} addr {} len {})", - (int)retry_counter, (uint8_t)target_device_id, (uint16_t)first_register_address, - (uint16_t)num_registers_to_read); - - response = modbus.txrx(target_device_id, tiny_modbus::FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS, - first_register_address, num_registers_to_read, config.max_packet_size); - if (response.size() > 0) { - break; + auto retry_counter = config.retries + 1; + + while (retry_counter > 0) { + auto current_trial = config.retries + 1 - retry_counter + 1; + + EVLOG_debug << fmt::format("Trial {}/{}: calling {}(id {} addr {}({:#06x}) len {})", current_trial, + config.retries + 1, tiny_modbus::FunctionCode_to_string_with_hex(function), + device_address, first_register_address, first_register_address, register_quantity); + + try { + response = modbus.txrx(device_address, function, first_register_address, register_quantity, + config.max_packet_size, wait_for_reply, request); + } catch (const tiny_modbus::TinyModbusException& e) { + auto logmsg = fmt::format("Modbus call {} for device id {} addr {}({:#06x}) failed: {}", + tiny_modbus::FunctionCode_to_string_with_hex(function), device_address, + first_register_address, first_register_address, e.what()); + + if (retry_counter != 1) + EVLOG_debug << logmsg; + else + EVLOG_warning << logmsg; + } catch (const std::logic_error& e) { + EVLOG_warning << "Logic error in Modbus implementation: " << e.what(); + } catch (const std::system_error& e) { + // FIXME: report this to the infrastructure, as soon as an error interface for this is available + // Log this only once, as we are convinced this will not go away + if (not system_error_logged) { + EVLOG_error << "System error in accessing Modbus: [" << e.code() << "] " << e.what(); + system_error_logged = true; } - retry_counter--; } + + if (response.size() > 0) + break; + + retry_counter--; } - EVLOG_debug << fmt::format("Process response (size {})", response.size()); - // process response if (response.size() > 0) { + EVLOG_debug << fmt::format("Process response (size {})", response.size()); result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Success; result.value = vector_to_int(response); + system_error_logged = false; // reset after success } else { result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Error; } return result; } -types::serial_comm_hub_requests::Result -serial_communication_hubImpl::handle_modbus_read_input_registers(int& target_device_id, int& first_register_address, - int& num_registers_to_read) { - types::serial_comm_hub_requests::Result result; - std::vector response; - - { - std::scoped_lock lock(serial_mutex); +// Commands - uint8_t retry_counter{this->num_resends_on_error}; - while (retry_counter-- > 0) { +types::serial_comm_hub_requests::Result +serial_communication_hubImpl::handle_modbus_read_holding_registers(int& target_device_id, int& first_register_address, + int& num_registers_to_read) { - EVLOG_debug << fmt::format("Try {} Call modbus_client->read_input_register(id {} addr {} len {})", - (int)retry_counter, (uint8_t)target_device_id, (uint16_t)first_register_address, - (uint16_t)num_registers_to_read); + return perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS, + first_register_address, num_registers_to_read); +} - response = modbus.txrx(target_device_id, tiny_modbus::FunctionCode::READ_INPUT_REGISTERS, - first_register_address, num_registers_to_read, config.max_packet_size); - if (response.size() > 0) { - break; - } - } - } +types::serial_comm_hub_requests::Result +serial_communication_hubImpl::handle_modbus_read_input_registers(int& target_device_id, int& first_register_address, + int& num_registers_to_read) { - EVLOG_debug << fmt::format("Process response (size {})", response.size()); - // process response - if (response.size() > 0) { - result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Success; - result.value = vector_to_int(response); - } else { - result.status_code = types::serial_comm_hub_requests::StatusCodeEnum::Error; - } - return result; + return perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::READ_INPUT_REGISTERS, + first_register_address, num_registers_to_read); } types::serial_comm_hub_requests::StatusCodeEnum serial_communication_hubImpl::handle_modbus_write_multiple_registers( int& target_device_id, int& first_register_address, types::serial_comm_hub_requests::VectorUint16& data_raw) { types::serial_comm_hub_requests::Result result; - std::vector response; - std::vector data; append_array(data, data_raw.data); - { - std::scoped_lock lock(serial_mutex); - - uint8_t retry_counter{this->num_resends_on_error}; - while (retry_counter-- > 0) { - - EVLOG_debug << fmt::format("Try {} Call modbus_client->write_multiple_registers(id {} addr {} len {})", - (int)retry_counter, (uint8_t)target_device_id, (uint16_t)first_register_address, - (uint16_t)data.size()); - - response = modbus.txrx(target_device_id, tiny_modbus::FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS, - first_register_address, data.size(), config.max_packet_size, true, data); - if (response.size() > 0) { - break; - } - } - } + result = perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS, + first_register_address, data.size(), true, data); - EVLOG_debug << fmt::format("Done writing (size {})", response.size()); - // process response - if (response.size() > 0) { - return types::serial_comm_hub_requests::StatusCodeEnum::Success; - } else { - return types::serial_comm_hub_requests::StatusCodeEnum::Error; - } + return result.status_code; } types::serial_comm_hub_requests::StatusCodeEnum serial_communication_hubImpl::handle_modbus_write_single_register(int& target_device_id, int& register_address, int& data) { types::serial_comm_hub_requests::Result result; - std::vector response; - - { - std::scoped_lock lock(serial_mutex); - uint8_t retry_counter{this->num_resends_on_error}; - while (retry_counter-- > 0) { + result = perform_modbus_request(target_device_id, tiny_modbus::FunctionCode::WRITE_SINGLE_HOLDING_REGISTER, + register_address, 1, true, {static_cast(data)}); - EVLOG_debug << fmt::format("Try {} Call modbus_client->write_single_register(id {} addr {} data 0x{:04x})", - (int)retry_counter, (uint8_t)target_device_id, (uint16_t)register_address, - (uint16_t)data); - - response = modbus.txrx(target_device_id, tiny_modbus::FunctionCode::WRITE_SINGLE_HOLDING_REGISTER, - register_address, 1, config.max_packet_size, true, {static_cast(data)}); - if (response.size() > 0) { - break; - } - } - } - EVLOG_debug << fmt::format("Done writing (size {})", response.size()); - // process response - if (response.size() > 0) { - return types::serial_comm_hub_requests::StatusCodeEnum::Success; - } else { - return types::serial_comm_hub_requests::StatusCodeEnum::Error; - } + return result.status_code; } void serial_communication_hubImpl::handle_nonstd_write(int& target_device_id, int& first_register_address, diff --git a/modules/SerialCommHub/main/serial_communication_hubImpl.hpp b/modules/SerialCommHub/main/serial_communication_hubImpl.hpp index 2a3933f7b..41b7c98cf 100644 --- a/modules/SerialCommHub/main/serial_communication_hubImpl.hpp +++ b/modules/SerialCommHub/main/serial_communication_hubImpl.hpp @@ -16,6 +16,7 @@ // insert your custom include headers here #include "tiny_modbus_rtu.hpp" #include +#include #include #include #include @@ -36,6 +37,7 @@ struct Conf { int max_packet_size; int initial_timeout_ms; int within_message_timeout_ms; + int retries; }; class serial_communication_hubImpl : public serial_communication_hubImplBase { @@ -80,10 +82,15 @@ class serial_communication_hubImpl : public serial_communication_hubImplBase { // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 // insert your private definitions here - static constexpr uint8_t num_resends_on_error{3}; + types::serial_comm_hub_requests::Result + perform_modbus_request(uint8_t device_address, tiny_modbus::FunctionCode function, uint16_t first_register_address, + uint16_t register_quantity, bool wait_for_reply = true, + std::vector request = std::vector()); + tiny_modbus::TinyModbusRTU modbus; std::mutex serial_mutex; + bool system_error_logged{false}; // ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1 }; diff --git a/modules/SerialCommHub/manifest.yaml b/modules/SerialCommHub/manifest.yaml index 5a62bc7d3..8e7bb4f4c 100644 --- a/modules/SerialCommHub/manifest.yaml +++ b/modules/SerialCommHub/manifest.yaml @@ -58,6 +58,12 @@ provides: description: Timeout in ms for subsequent packets. type: integer default: 100 + retries: + description: Count of retries in case of error in Modbus query. + type: integer + minimum: 0 + maximum: 10 + default: 2 metadata: license: https://opensource.org/licenses/Apache-2.0 authors: diff --git a/modules/SerialCommHub/tiny_modbus_rtu.cpp b/modules/SerialCommHub/tiny_modbus_rtu.cpp index 83d8cc808..dba169305 100644 --- a/modules/SerialCommHub/tiny_modbus_rtu.cpp +++ b/modules/SerialCommHub/tiny_modbus_rtu.cpp @@ -7,28 +7,60 @@ // - implement GPIO to switch rx/tx #include "tiny_modbus_rtu.hpp" - +#include "crc16.hpp" +#include #include #include #include #include +#include +#include +#include #include +#include +#include +#include #include -#include -#include - #include #include #include +#include +#include +#include -#include -#include -#include -#include +namespace tiny_modbus { -#include "crc16.hpp" +std::string FunctionCode_to_string(FunctionCode fc) { + switch (fc) { + case FunctionCode::READ_COILS: + return "READ_COILS"; + case FunctionCode::READ_DISCRETE_INPUTS: + return "READ_DISCRETE_INPUTS"; + case FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS: + return "READ_MULTIPLE_HOLDING_REGISTERS"; + case FunctionCode::READ_INPUT_REGISTERS: + return "READ_INPUT_REGISTERS"; + case FunctionCode::WRITE_SINGLE_COIL: + return "WRITE_SINGLE_COIL"; + case FunctionCode::WRITE_SINGLE_HOLDING_REGISTER: + return "WRITE_SINGLE_HOLDING_REGISTER"; + case FunctionCode::WRITE_MULTIPLE_COILS: + return "WRITE_MULTIPLE_COILS"; + case FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS: + return "WRITE_MULTIPLE_HOLDING_REGISTERS"; + default: + return "unknown"; + } +} -namespace tiny_modbus { +std::string FunctionCode_to_string_with_hex(FunctionCode fc) { + return fmt::format("{}({:#04x})", FunctionCode_to_string(fc), (unsigned int)fc); +} + +std::ostream& operator<<(std::ostream& os, const FunctionCode& fc) { + os << FunctionCode_to_string_with_hex(fc); + return os; +} // This is a replacement for system library tcdrain(). // tcdrain() returns when all bytes are written to the UART, but it actually returns about 10msecs or more after the @@ -53,7 +85,7 @@ static void clear_exception_bit(uint8_t& received_function_code) { static std::string hexdump(const uint8_t* msg, int msg_len) { std::stringstream ss; for (int i = 0; i < msg_len; i++) { - ss << std::hex << (int)msg[i] << " "; + ss << "<" << std::nouppercase << std::setfill('0') << std::setw(2) << std::hex << (int)msg[i] << ">"; } return ss.str(); } @@ -79,18 +111,14 @@ static std::vector decode_reply(const uint8_t* buf, int len, uint8_t e FunctionCode function) { std::vector result; if (len == 0) { - EVLOG_error << fmt::format("Packet receive timeout (device address {}).", expected_device_address); - return result; + throw TimeoutException("Packet receive timeout"); } else if (len < MODBUS_MIN_REPLY_SIZE) { - EVLOG_error << fmt::format("Packet too small: {} bytes (device address {}).", len, expected_device_address); - return result; + throw ShortPacketException(fmt::format("Packet too small: only {} bytes", len)); } if (expected_device_address != buf[DEVICE_ADDRESS_POS]) { - EVLOG_error << fmt::format("Device address mismatch: expected: {} received: {}", expected_device_address, - buf[DEVICE_ADDRESS_POS]) - << ": " << hexdump(buf, len); - - return result; + throw AddressMismatchException(fmt::format("Device address mismatch: expected: {} received: {}", + expected_device_address, buf[DEVICE_ADDRESS_POS]) + + ": " + hexdump(buf, len)); } bool exception = false; @@ -103,92 +131,113 @@ static std::vector decode_reply(const uint8_t* buf, int len, uint8_t e } if (function != function_code_recvd) { - EVLOG_error << fmt::format("Function code mismatch: expected: {} received: {}", - static_cast>(function), function_code_recvd); - return result; + throw FunctionCodeMismatchException(fmt::format("Function code mismatch: expected: {} received: {}", + static_cast>(function), + function_code_recvd)); } if (!validate_checksum(buf, len)) { - EVLOG_error << "Checksum error"; - return result; + throw ChecksumErrorException("Retrieved Modbus checksum does not match calculated value."); } - if (!exception) { - // For a write reply we always get 4 bytes - uint8_t byte_cnt = 4; - int start_of_result = RES_TX_START_OF_PAYLOAD; - - // Was it a read reply? - if (function == FunctionCode::READ_COILS || function == FunctionCode::READ_DISCRETE_INPUTS || - function == FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS || - function == FunctionCode::READ_INPUT_REGISTERS) { - // adapt byte count and starting pos - byte_cnt = buf[RES_RX_LEN_POS]; - start_of_result = RES_RX_START_OF_PAYLOAD; - } - - // check if result is completely in received data - if (start_of_result + byte_cnt > len) { - EVLOG_error << "Result data not completely in received message."; - return result; - } - - // ready to copy actual result data to output - result.reserve(byte_cnt / 2); - - for (int i = start_of_result; i < start_of_result + byte_cnt; i += 2) { - uint16_t t; - memcpy(&t, buf + i, 2); - t = be16toh(t); - result.push_back(t); - } - return result; - } else { + if (exception) { // handle exception message uint8_t err_code = buf[RES_EXCEPTION_CODE]; switch (err_code) { case 0x01: - EVLOG_error << "Modbus exception: Illegal function"; + throw ModbusException("Modbus exception: Illegal function"); break; case 0x02: - EVLOG_error << "Modbus exception: Illegal data address"; + throw ModbusException("Modbus exception: Illegal data address"); break; case 0x03: - EVLOG_error << "Modbus exception: Illegal data value"; + throw ModbusException("Modbus exception: Illegal data value"); break; case 0x04: - EVLOG_error << "Modbus exception: Client device failure"; + throw ModbusException("Modbus exception: Client device failure"); break; case 0x05: - EVLOG_debug << "Modbus ACK"; + throw ModbusException("Modbus ACK"); break; case 0x06: - EVLOG_error << "Modbus exception: Client device busy"; + throw ModbusException("Modbus exception: Client device busy"); break; case 0x07: - EVLOG_error << "Modbus exception: NACK"; + throw ModbusException("Modbus exception: NACK"); break; case 0x08: - EVLOG_error << "Modbus exception: Memory parity error"; + throw ModbusException("Modbus exception: Memory parity error"); break; case 0x09: - EVLOG_error << "Modbus exception: Out of resources"; + throw ModbusException("Modbus exception: Out of resources"); break; case 0x0A: - EVLOG_error << "Modbus exception: Gateway path unavailable"; + throw ModbusException("Modbus exception: Gateway path unavailable"); break; case 0x0B: - EVLOG_error << "Modbus exception: Gateway target device failed to respond"; + throw ModbusException("Modbus exception: Gateway target device failed to respond"); break; default: - EVLOG_error << "Modbus exception: Unknown"; + throw ModbusException("Modbus exception: Unknown"); } - return result; } + + // For a write reply we always get 4 bytes + uint8_t byte_cnt = 4; + int start_of_result = RES_TX_START_OF_PAYLOAD; + bool even_byte_cnt_expected = false; + + // Was it a read reply? + switch (function) { + case FunctionCode::WRITE_SINGLE_COIL: + case FunctionCode::WRITE_SINGLE_HOLDING_REGISTER: + case FunctionCode::WRITE_MULTIPLE_COILS: + case FunctionCode::WRITE_MULTIPLE_HOLDING_REGISTERS: + // no - nothing to do + break; + case FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS: + case FunctionCode::READ_INPUT_REGISTERS: + // yes - for 16-bit wide registers thus we can assume an even byte count + even_byte_cnt_expected = true; + [[fallthrough]]; + case FunctionCode::READ_COILS: + case FunctionCode::READ_DISCRETE_INPUTS: + // yes + // adapt byte count and starting pos + byte_cnt = buf[RES_RX_LEN_POS]; + start_of_result = RES_RX_START_OF_PAYLOAD; + break; + default: + throw std::logic_error("Missing implementation for function code " + FunctionCode_to_string_with_hex(function)); + } + + // check if result is completely in received data + if (start_of_result + byte_cnt > len) { + throw IncompletePacketException("Result data not completely in received message."); + } + + // check even number of bytes + if (even_byte_cnt_expected && byte_cnt % 2 == 1) { + throw OddByteCountException("For " + FunctionCode_to_string_with_hex(function) + + " an even byte count is expected in the response."); + } + + // ready to copy actual result data to output, so pre-allocate enough memory for the output + result.reserve((byte_cnt + 1) / 2); + + for (int i = start_of_result; i < start_of_result + byte_cnt; i += 2) { + uint16_t t = 0; + const size_t num_bytes_to_copy = (i < len - 1) ? 2 : 1; + memcpy(&t, buf + i, num_bytes_to_copy); + t = be16toh(t); + result.push_back(t); + } + + return result; } TinyModbusRTU::~TinyModbusRTU() { - if (fd) + if (fd != -1) close(fd); } @@ -279,7 +328,7 @@ bool TinyModbusRTU::open_device(const std::string& device, int _baud, bool _igno } int TinyModbusRTU::read_reply(uint8_t* rxbuf, int rxbuf_len) { - if (fd <= 0) { + if (fd == -1) { return 0; } @@ -425,7 +474,7 @@ std::vector TinyModbusRTU::txrx_impl(uint8_t device_address, FunctionC uint16_t first_register_address, uint16_t register_quantity, bool wait_for_reply, std::vector request) { { - if (fd <= 0) { + if (fd == -1) { return {}; } @@ -438,7 +487,17 @@ std::vector TinyModbusRTU::txrx_impl(uint8_t device_address, FunctionC // write to serial port rxtx_gpio.set(false); - write(fd, req.data(), req.size()); + + uint8_t* buffer = req.data(); + ssize_t written = 0; + + while (written < req.size()) { + ssize_t c = write(fd, &buffer[written], req.size() - written); + if (c == -1) + throw std::system_error(errno, std::generic_category(), "Could not send Modbus request"); + written += c; + } + if (rxtx_gpio.is_ready()) { // if we are using GPIO to switch between RX/TX, use the fast version of tcdrain with exact timing fast_tcdrain(fd); diff --git a/modules/SerialCommHub/tiny_modbus_rtu.hpp b/modules/SerialCommHub/tiny_modbus_rtu.hpp index abcd76ff7..9d7d33162 100644 --- a/modules/SerialCommHub/tiny_modbus_rtu.hpp +++ b/modules/SerialCommHub/tiny_modbus_rtu.hpp @@ -8,6 +8,8 @@ #define TINY_MODBUS_RTU #include +#include +#include #include #include @@ -51,6 +53,38 @@ enum FunctionCode : uint8_t { WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10, }; +std::string FunctionCode_to_string(FunctionCode fc); +std::string FunctionCode_to_string_with_hex(FunctionCode fc); +std::ostream& operator<<(std::ostream& os, const FunctionCode& fc); + +class TinyModbusException : public std::runtime_error { + using std::runtime_error::runtime_error; +}; +class TimeoutException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; +class ShortPacketException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; +class AddressMismatchException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; +class FunctionCodeMismatchException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; +class ChecksumErrorException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; +class IncompletePacketException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; +class OddByteCountException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; +class ModbusException : public TinyModbusException { + using TinyModbusException::TinyModbusException; +}; + class TinyModbusRTU { public: @@ -66,7 +100,7 @@ class TinyModbusRTU { private: // Serial interface - int fd{0}; + int fd{-1}; bool ignore_echo{false}; std::vector txrx_impl(uint8_t device_address, FunctionCode function, uint16_t first_register_address,