Skip to content

Commit

Permalink
added read input register command
Browse files Browse the repository at this point in the history
Add filtering of echoed messages
Make ModubusRTUClient constructor explicit
Bump version to 0.2

Signed-off-by: Christoph Kliemt <[email protected]>
Signed-off-by: Kai-Uwe Hermann <[email protected]>
  • Loading branch information
Christoph Kliemt authored and corneliusclaussen committed Mar 9, 2023
1 parent 46d4342 commit 9d9326f
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 144 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.11)

project(modbus
VERSION 0.1
VERSION 0.2
)

find_package(everest-cmake 0.1 REQUIRED
Expand Down
4 changes: 3 additions & 1 deletion include/consts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ namespace modbus {
namespace consts {

// General MODBUS constants
constexpr uint16_t READ_HOLDING_REGISTER_MESSAGE_LENGTH = 6;
// TODO: this constant should have the value 5...?
constexpr uint16_t READ_REGISTER_COMMAND_LENGTH = 6;
constexpr uint8_t READ_HOLDING_REGISTER_FUNCTION_CODE = 3;
constexpr uint8_t READ_INPUT_REGISTER_FUNCTION_CODE = 4;

// MODBUS/RTU specific constants
namespace rtu {
Expand Down
234 changes: 129 additions & 105 deletions include/modbus/modbus_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,115 +4,139 @@
#ifndef MODBUS_CLIENT_H
#define MODBUS_CLIENT_H

#include <memory>
#include <cstdint>
#include <memory>
#include <vector>

#include <connection/connection.hpp>
#include <consts.hpp>

namespace everest { namespace modbus {

class ModbusClient {
public:
ModbusClient(connection::Connection& conn_);
virtual ~ModbusClient() = default;
// read_holding_register needs to be virtual, since the rtu format differs from the ip/udp formats.
virtual const std::vector<uint8_t> read_holding_register(uint8_t unit_id, uint16_t first_register_address, uint16_t num_registers_to_read, bool return_only_registers_bytes=true) const;

protected:

const virtual std::vector<uint8_t> full_message_from_body(const std::vector<uint8_t>& body, uint16_t message_length, uint8_t unit_id) const = 0;

virtual uint16_t validate_response(const std::vector<uint8_t>& response, const std::vector<uint8_t>& request) const = 0;

// message size including protocol data (addressing, error check, mbap)
virtual std::size_t max_adu_size() const = 0;
// message size without protocol data (addressing, error check, mbap), function code and payload data only
virtual std::size_t max_pdu_size() const = 0;

ModbusClient(const ModbusClient&) = delete;
ModbusClient& operator=(const ModbusClient&) = delete;
connection::Connection& conn;
};

class ModbusIPClient : public ModbusClient {
public:
ModbusIPClient(connection::Connection& conn_);
virtual ~ModbusIPClient() = default;
const std::vector<uint8_t> full_message_from_body(const std::vector<uint8_t>& body, uint16_t message_length, uint8_t unit_id) const override;
uint16_t validate_response(const std::vector<uint8_t>& response, const std::vector<uint8_t>& request) const override;
// message size including protocol data (addressing, error check, mbap)
virtual std::size_t max_adu_size() const override { return everest::modbus::consts::tcp::MAX_ADU; }
// message size without protocol data (addressing, error check, mbap), function code and payload data only
virtual std::size_t max_pdu_size() const override { return everest::modbus::consts::tcp::MAX_PDU; }

};

class ModbusTCPClient : public ModbusIPClient {
public:
ModbusTCPClient(connection::TCPConnection& conn_);
~ModbusTCPClient() override = default;
};

class ModbusUDPClient : public ModbusIPClient {
public:
ModbusUDPClient(connection::UDPConnection& conn_);
~ModbusUDPClient() override = default;
};

using DataVectorUint16 = std::vector<std::uint16_t>;
using DataVectorUint8 = std::vector<std::uint8_t>;

enum struct ByteOrder { BigEndian, LittleEndian };

class ModbusDataContainerUint16 {

public:

ModbusDataContainerUint16( ByteOrder byte_order, // which byteorder does the parameter payload have
const DataVectorUint16& payload ) :
m_byte_order(byte_order),
m_payload( payload )
{}

DataVectorUint8 get_payload_as_bigendian() const;

std::size_t size() const { return m_payload.size(); }

protected:

ByteOrder m_byte_order;
DataVectorUint16 m_payload;

};

class ModbusRTUClient : public ModbusClient {
public:

ModbusRTUClient(connection::Connection& conn_);
virtual ~ModbusRTUClient() override;

// throws derived from std::runtime_error, see include/modbus/exceptions.hpp
const DataVectorUint8 read_holding_register(uint8_t unit_id, uint16_t first_register_address, uint16_t num_registers_to_read, bool return_only_registers_bytes = true ) const override;

// throws derived from std::runtime_error, see include/modbus/exceptions.hpp
DataVectorUint8 write_multiple_registers( uint8_t unit_id, uint16_t first_register_address, uint16_t num_registers_to_write , const ModbusDataContainerUint16& payload, bool return_only_registers_bytes ) const; // errors will be reported by exception std::runtime_error

// message size including protocol data (addressing, error check, mbap)
virtual std::size_t max_adu_size() const override { return everest::modbus::consts::rtu::MAX_ADU; }
// message size without protocol data (addressing, error check, mbap), function code and payload data only
virtual std::size_t max_pdu_size() const override { return everest::modbus::consts::rtu::MAX_PDU; }

static DataVectorUint8 response_without_protocol_data( const DataVectorUint8& raw_response, std::size_t payload_length );


protected:

const DataVectorUint8 full_message_from_body(const DataVectorUint8& body, uint16_t message_length, std::uint8_t unit_id) const override;
uint16_t validate_response(const DataVectorUint8& response, const DataVectorUint8& request) const override;
};

} // namespace modbus
}; // namespace everest
namespace everest {
namespace modbus {

class ModbusClient {
public:
ModbusClient(connection::Connection& conn_);
virtual ~ModbusClient() = default;
// read_holding_register needs to be virtual, since the rtu format differs from the ip/udp formats.
virtual const std::vector<uint8_t> read_holding_register(uint8_t unit_id, uint16_t first_register_address,
uint16_t num_registers_to_read,
bool return_only_registers_bytes = true) const;

protected:
const virtual std::vector<uint8_t> full_message_from_body(const std::vector<uint8_t>& body, uint16_t message_length,
uint8_t unit_id) const = 0;

virtual uint16_t validate_response(const std::vector<uint8_t>& response,
const std::vector<uint8_t>& request) const = 0;

// message size including protocol data (addressing, error check, mbap)
virtual std::size_t max_adu_size() const = 0;
// message size without protocol data (addressing, error check, mbap), function code and payload data only
virtual std::size_t max_pdu_size() const = 0;

ModbusClient(const ModbusClient&) = delete;
ModbusClient& operator=(const ModbusClient&) = delete;
connection::Connection& conn;
};

class ModbusIPClient : public ModbusClient {
public:
ModbusIPClient(connection::Connection& conn_);
virtual ~ModbusIPClient() = default;
const std::vector<uint8_t> full_message_from_body(const std::vector<uint8_t>& body, uint16_t message_length,
uint8_t unit_id) const override;
uint16_t validate_response(const std::vector<uint8_t>& response,
const std::vector<uint8_t>& request) const override;
// message size including protocol data (addressing, error check, mbap)
virtual std::size_t max_adu_size() const override {
return everest::modbus::consts::tcp::MAX_ADU;
}
// message size without protocol data (addressing, error check, mbap), function code and payload data only
virtual std::size_t max_pdu_size() const override {
return everest::modbus::consts::tcp::MAX_PDU;
}
};

class ModbusTCPClient : public ModbusIPClient {
public:
ModbusTCPClient(connection::TCPConnection& conn_);
~ModbusTCPClient() override = default;
};

class ModbusUDPClient : public ModbusIPClient {
public:
ModbusUDPClient(connection::UDPConnection& conn_);
~ModbusUDPClient() override = default;
};

using DataVectorUint16 = std::vector<std::uint16_t>;
using DataVectorUint8 = std::vector<std::uint8_t>;

enum struct ByteOrder {
BigEndian,
LittleEndian
};

class ModbusDataContainerUint16 {

public:
ModbusDataContainerUint16(ByteOrder byte_order, // which byteorder does the parameter payload have
const DataVectorUint16& payload) :
m_byte_order(byte_order), m_payload(payload) {
}

DataVectorUint8 get_payload_as_bigendian() const;

std::size_t size() const {
return m_payload.size();
}

protected:
ByteOrder m_byte_order;
DataVectorUint16 m_payload;
};

class ModbusRTUClient : public ModbusClient {
public:
ModbusRTUClient(connection::Connection& conn_, bool ignore_echo);
explicit ModbusRTUClient(connection::Connection& conn_) : ModbusRTUClient(conn_, false){}
virtual ~ModbusRTUClient() override;

// throws derived from std::runtime_error, see include/modbus/exceptions.hpp
const DataVectorUint8 read_holding_register(uint8_t unit_id, uint16_t first_register_address,
uint16_t num_registers_to_read,
bool return_only_registers_bytes = true) const override;

// HACK warning! No virtual method!
const DataVectorUint8 read_input_register(uint8_t unit_id, uint16_t first_register_address,
uint16_t num_registers_to_read,
bool return_only_registers_bytes = true) const;
// throws derived from std::runtime_error, see include/modbus/exceptions.hpp
DataVectorUint8 write_multiple_registers(
uint8_t unit_id, uint16_t first_register_address, uint16_t num_registers_to_write,
const ModbusDataContainerUint16& payload,
bool return_only_registers_bytes) const; // errors will be reported by exception std::runtime_error

// message size including protocol data (addressing, error check, mbap)
virtual std::size_t max_adu_size() const override {
return everest::modbus::consts::rtu::MAX_ADU;
}
// message size without protocol data (addressing, error check, mbap), function code and payload data only
virtual std::size_t max_pdu_size() const override {
return everest::modbus::consts::rtu::MAX_PDU;
}

static DataVectorUint8 response_without_protocol_data(const DataVectorUint8& raw_response,
std::size_t payload_length);

protected:
const DataVectorUint8 full_message_from_body(const DataVectorUint8& body, uint16_t message_length,
std::uint8_t unit_id) const override;
uint16_t validate_response(const DataVectorUint8& response, const DataVectorUint8& request) const override;
bool ignore_echo;
};

} // namespace modbus
}; // namespace everest
#endif
59 changes: 34 additions & 25 deletions include/modbus/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,39 @@

#include "modbus_client.hpp"

namespace everest { namespace modbus { namespace utils {

// General use utils
std::vector<uint8_t> build_read_holding_register_message_body(uint16_t first_register_address, uint16_t num_registers_to_read);
std::vector<uint8_t> build_write_multiple_register_body( uint16_t first_register_address, uint16_t num_registers_to_write, const ::everest::modbus::ModbusDataContainerUint16& payload );
std::vector<uint8_t> extract_body_from_response(const std::vector<uint8_t>& response, int num_data_bytes);
std::vector<uint8_t> extract_registers_bytes_from_response_body(const std::vector<uint8_t>& response_body);
std::vector<uint8_t> extract_register_bytes_from_response(const std::vector<uint8_t>& response, int num_data_bytes);
void print_message_hex(const std::vector<uint8_t>& message);
void print_message_first_N_bytes(unsigned char *message, int N);

using PayloadType = unsigned char;
using CRCResultType = std::uint16_t;
CRCResultType calcCRC_16_ANSI( const PayloadType* payload ,std::size_t payload_length );

// MODBUS/IP specific utils
namespace ip {
// Utility funcs
std::vector<uint8_t> make_mbap_header(uint16_t message_length, uint8_t unit_id);
uint16_t check_mbap_header(const std::vector<uint8_t>& sent_message, const std::vector<uint8_t>& received_message);
}

} // namespace utils
} // namespace modbus
}; // namespace everest
namespace everest {
namespace modbus {
namespace utils {

// General use utils
std::vector<uint8_t> build_read_command_message_body(std::uint8_t function_code, uint16_t first_register_address,
uint16_t num_registers_to_read);
std::vector<uint8_t> build_read_holding_register_message_body(uint16_t first_register_address,
uint16_t num_registers_to_read);
std::vector<uint8_t> build_read_input_register_message_body(uint16_t first_register_address,
uint16_t num_registers_to_read);
std::vector<uint8_t> build_write_multiple_register_body(uint16_t first_register_address,
uint16_t num_registers_to_write,
const ::everest::modbus::ModbusDataContainerUint16& payload);
std::vector<uint8_t> extract_body_from_response(const std::vector<uint8_t>& response, int num_data_bytes);
std::vector<uint8_t> extract_registers_bytes_from_response_body(const std::vector<uint8_t>& response_body);
std::vector<uint8_t> extract_register_bytes_from_response(const std::vector<uint8_t>& response, int num_data_bytes);
void print_message_hex(const std::vector<uint8_t>& message);
void print_message_first_N_bytes(unsigned char* message, int N);

using PayloadType = unsigned char;
using CRCResultType = std::uint16_t;
CRCResultType calcCRC_16_ANSI(const PayloadType* payload, std::size_t payload_length);

// MODBUS/IP specific utils
namespace ip {
// Utility funcs
std::vector<uint8_t> make_mbap_header(uint16_t message_length, uint8_t unit_id);
uint16_t check_mbap_header(const std::vector<uint8_t>& sent_message, const std::vector<uint8_t>& received_message);
} // namespace ip

} // namespace utils
} // namespace modbus
}; // namespace everest

#endif
2 changes: 1 addition & 1 deletion src/modbus_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const std::vector<uint8_t> ModbusClient::read_holding_register(uint8_t unit_id,
std::vector<uint8_t> body =
utils::build_read_holding_register_message_body(first_register_address, num_registers_to_read);
std::vector<uint8_t> full_message =
full_message_from_body(body, consts::READ_HOLDING_REGISTER_MESSAGE_LENGTH, unit_id);
full_message_from_body(body, consts::READ_REGISTER_COMMAND_LENGTH, unit_id);
conn.send_bytes(full_message);
std::vector<uint8_t> response = conn.receive_bytes(max_adu_size());
int num_register_bytes = validate_response(response, full_message);
Expand Down
31 changes: 29 additions & 2 deletions src/modbus_rtu_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ using namespace everest::modbus;
* This is by design of the older parts of this library.
*/

ModbusRTUClient::ModbusRTUClient(connection::Connection& conn_) : ModbusClient(conn_) {
ModbusRTUClient::ModbusRTUClient(connection::Connection& conn_, bool ignore_echo) : ModbusClient(conn_), ignore_echo(ignore_echo) {
}

ModbusRTUClient::~ModbusRTUClient() {
Expand Down Expand Up @@ -63,7 +63,7 @@ const DataVectorUint8 ModbusRTUClient::read_holding_register(uint8_t unit_id, ui
DataVectorUint8 body =
utils::build_read_holding_register_message_body(first_register_address, num_registers_to_read);

DataVectorUint8 full_message = full_message_from_body(body, consts::READ_HOLDING_REGISTER_MESSAGE_LENGTH, unit_id);
DataVectorUint8 full_message = full_message_from_body(body, consts::READ_REGISTER_COMMAND_LENGTH, unit_id);

conn.send_bytes(full_message);
modbus::DataVectorUint8 response = conn.receive_bytes(max_adu_size());
Expand All @@ -77,6 +77,33 @@ const DataVectorUint8 ModbusRTUClient::read_holding_register(uint8_t unit_id, ui
#endif
}

// HACK: is not a virtual function, not implementing the modbus interface.
const DataVectorUint8 ModbusRTUClient::read_input_register(uint8_t unit_id, uint16_t first_register_address,
uint16_t num_registers_to_read,
bool return_only_registers_bytes) const {

std::vector<uint8_t> body =
utils::build_read_input_register_message_body(first_register_address, num_registers_to_read);
std::vector<uint8_t> full_message = full_message_from_body(body, consts::READ_REGISTER_COMMAND_LENGTH, unit_id);
conn.send_bytes(full_message);
std::vector<uint8_t> response = conn.receive_bytes(max_adu_size());

if (ignore_echo && response.size() > full_message.size()) {
auto request = std::vector<uint8_t>(response.begin(), response.begin() + full_message.size());
if (full_message == request) {
response = std::vector<uint8_t>(response.begin() + full_message.size(), response.end());
}
}

int num_register_bytes = validate_response(response, full_message);

if (return_only_registers_bytes) {
return utils::extract_register_bytes_from_response(response, num_register_bytes + 2);
}

return response;
}

DataVectorUint8 ModbusDataContainerUint16::get_payload_as_bigendian() const {

DataVectorUint8 result;
Expand Down
Loading

0 comments on commit 9d9326f

Please sign in to comment.