Skip to content

Commit

Permalink
add more modbus types and refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
markaren committed Sep 27, 2024
1 parent 65e722c commit 2b6f68c
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 51 deletions.
13 changes: 11 additions & 2 deletions include/simple_socket/modbus/HoldingRegister.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,23 @@ namespace simple_socket {
// Method to get a uint32_t value from two registers
uint32_t getUint32(size_t index) const;

// Method to set a uint32_t value at a specific register index (requires 2 registers)
void setUint64(size_t index, uint64_t value);

// Method to get a uint32_t value from two registers
uint64_t getUint64(size_t index) const;

// Method to set a float value (requires 2 registers)
void setFloat(size_t index, float value);

// Method to get a float value from two registers
float getFloat(size_t index) const;

// Method to print all register values (for debugging)
void printRegisters() const;
// Method to set a float value (requires 2 registers)
void setDouble(size_t index, double value);

// Method to get a float value from two registers
double getDouble(size_t index) const;

private:
mutable std::mutex m_;
Expand Down
6 changes: 4 additions & 2 deletions include/simple_socket/modbus/ModbusClient.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@ namespace simple_socket {
std::vector<uint16_t> read_holding_registers(uint16_t address, uint16_t count, uint8_t unit_id = 1);

uint16_t read_uint16(uint16_t address, uint8_t unit_id = 1);

uint32_t read_uint32(uint16_t address, uint8_t unit_id = 1);
uint64_t read_uint64(uint16_t address, uint8_t unit_id = 1);

float read_float(uint16_t address, uint8_t unit_id = 1);
double read_double(uint16_t address, uint8_t unit_id = 1);

bool write_single_register(uint16_t address, uint16_t value, uint8_t unit_id = 1);

bool write_multiple_registers(uint16_t address, const uint16_t* values, size_t size, uint8_t unitID = 1);

template <typename ArrayLike>
bool write_multiple_registers(uint16_t address, const ArrayLike& values, uint8_t unitID = 1) {
static_assert(std::is_same<typename ArrayLike::value_type, uint16_t>::value,
"Buffer must hold uint16_t elements");
return write_multiple_registers(address, values.data(), values.size(), unitID);
}

Expand Down
92 changes: 78 additions & 14 deletions include/simple_socket/modbus/modbus_helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
#ifndef SIMPLE_SOCKET_MODBUS_HELPER_HPP
#define SIMPLE_SOCKET_MODBUS_HELPER_HPP

#include <sstream>
#include <vector>
#include <array>
#include <cstring>
#include <sstream>
#include <vector>

namespace simple_socket {

Expand All @@ -15,6 +15,36 @@ namespace simple_socket {
static_cast<uint32_t>(data[offset + 1]);
}

// Decode 64-bit unsigned integer (8 bytes)
inline uint32_t decode_uint64(const std::vector<uint16_t>& data, size_t offset = 0) {
return (static_cast<uint64_t>(data[offset]) << 48) |
(static_cast<uint64_t>(data[offset + 1]) << 32) |
(static_cast<uint64_t>(data[offset + 2]) << 16) |
(static_cast<uint64_t>(data[offset + 3]));
}

// Decode IEEE 754 32-bit float (4 bytes)
inline float decode_float(const std::vector<uint16_t>& data, size_t offset = 0) {
if (offset + 2 > data.size()) {
throw std::out_of_range("Not enough data to decode a double.");
}
uint32_t raw_value = decode_uint32(data, offset);
float decoded_value;
std::memcpy(&decoded_value, &raw_value, sizeof(decoded_value));// Reinterpret the bits as float
return decoded_value;
}

// Decode IEEE 754 64-bit double (8 bytes)
inline double decode_double(const std::vector<uint16_t>& data, size_t offset = 0) {
if (offset + 4 > data.size()) {
throw std::out_of_range("Not enough data to decode a double.");
}
uint64_t raw_value = decode_uint64(data, offset);
double decoded_value;
std::memcpy(&decoded_value, &raw_value, sizeof(decoded_value));
return decoded_value;
}

// Decode 32-bit unsigned integer (4 bytes)
inline std::string decode_text(const std::vector<uint16_t>& data, size_t num, size_t offset = 0) {
std::stringstream ss;
Expand All @@ -26,42 +56,76 @@ namespace simple_socket {
return ss.str();
}

// Decode IEEE 754 32-bit float (4 bytes)
inline float decode_float(const std::vector<uint16_t>& data, size_t offset = 0) {
uint32_t raw_value = decode_uint32(data, offset);
float decoded_value;
std::memcpy(&decoded_value, &raw_value, sizeof(decoded_value));// Reinterpret the bits as float
return decoded_value;
template <typename ArrayLike>
void encode_uint32(uint32_t value, ArrayLike& buffer, size_t offset = 0) {
static_assert(std::is_same<typename ArrayLike::value_type, uint16_t>::value,
"Buffer must hold uint16_t elements");
buffer[0+offset] = (static_cast<uint16_t>((value >> 16) & 0xFFFF));
buffer[1+offset] = (static_cast<uint16_t>(value & 0xFFFF));
}

inline std::array<uint16_t, 2> encode_uint32(uint32_t value) {
std::array<uint16_t, 2> data{};
encode_uint32(value, data);
return data;
}

data[0] = (static_cast<uint16_t>((value >> 16) & 0xFFFF));// High 16 bits
data[1] = (static_cast<uint16_t>(value & 0xFFFF)); // Low 16 bit
template <typename ArrayLike>
void encode_uint64(uint64_t value, ArrayLike& buffer, size_t offset = 0) {
static_assert(std::is_same<typename ArrayLike::value_type, uint16_t>::value,
"Buffer must hold uint16_t elements");
buffer[0+offset] = (static_cast<uint16_t>((value >> 48) & 0xFFFF));
buffer[1+offset] = (static_cast<uint16_t>(value >> 32 & 0xFFFF));
buffer[2+offset] = (static_cast<uint16_t>((value >> 16) & 0xFFFF));
buffer[3+offset] = (static_cast<uint16_t>(value & 0xFFFF));
}

inline std::array<uint16_t, 4> encode_uint64(uint64_t value) {
std::array<uint16_t, 4> data{};
encode_uint64(value, data);
return data;
}

template <typename ArrayLike>
void encode_float(float value, ArrayLike& buffer, size_t offset = 0) {
static_assert(std::is_same<typename ArrayLike::value_type, uint16_t>::value,
"Buffer must hold uint16_t elements");
uint32_t raw_value;
std::memcpy(&raw_value, &value, sizeof(raw_value));
encode_uint32(raw_value, buffer, offset);
}

inline std::array<uint16_t, 2> encode_float(float value) {
uint32_t raw_value;
std::memcpy(&raw_value, &value, sizeof(raw_value));// Convert float to uint32_t
std::memcpy(&raw_value, &value, sizeof(raw_value));
return encode_uint32(raw_value);
}

template <typename ArrayLike>
void encode_double(double value, ArrayLike& buffer, size_t offset = 0) {
static_assert(std::is_same<typename ArrayLike::value_type, uint16_t>::value,
"Buffer must hold uint16_t elements");
uint64_t raw_value;
std::memcpy(&raw_value, &value, sizeof(raw_value));
encode_uint64(raw_value, buffer, offset);
}

inline std::array<uint16_t, 4> encode_double(double value) {
uint64_t raw_value;
std::memcpy(&raw_value, &value, sizeof(raw_value));
return encode_uint64(raw_value);
}

inline std::vector<uint16_t> encode_text(const std::string& data) {

std::vector<uint16_t> encoded_data;
for (unsigned i = 0; i < data.size(); i += 2) {
uint16_t value;
if (i + 1 < data.size()) {

value = (static_cast<uint16_t>(data[i]) << 8) | static_cast<uint16_t>(data[i + 1]);

} else {
value = static_cast<uint16_t>(data[i]) << 8;
}

encoded_data.emplace_back(value);
}

Expand Down
52 changes: 30 additions & 22 deletions src/simple_socket/modbus/HoldingRegister.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

#include "simple_socket/modbus/HoldingRegister.hpp"

#include <cstring>
#include <iostream>
#include "simple_socket/modbus/modbus_helper.hpp"

#include <stdexcept>

using namespace simple_socket;
Expand All @@ -17,7 +17,7 @@ size_t HoldingRegister::size() const {

void HoldingRegister::setUint16(size_t index, uint16_t value) {
std::lock_guard lck(m_);
checkBounds(index, 1);// Check if index is within bounds
checkBounds(index, 1);
registers_[index] = value;
}

Expand All @@ -29,42 +29,50 @@ uint16_t HoldingRegister::getUint16(size_t index) const {

void HoldingRegister::setUint32(size_t index, uint32_t value) {
std::lock_guard lck(m_);
checkBounds(index, 2);// Check if two registers are available

registers_[index] = static_cast<uint16_t>(value >> 16); // High 16 bits
registers_[index + 1] = static_cast<uint16_t>(value & 0xFFFF);// Low 16 bits
checkBounds(index, 2);
encode_uint32(value, registers_, index);
}

uint32_t HoldingRegister::getUint32(size_t index) const {
std::lock_guard lck(m_);
checkBounds(index, 2);
uint32_t high = static_cast<uint32_t>(registers_[index]) << 16;
uint32_t low = registers_[index + 1];
return high | low;
return decode_uint32(registers_, index);
}

void HoldingRegister::setUint64(size_t index, uint64_t value) {
std::lock_guard lck(m_);
checkBounds(index, 4);
encode_uint64(value, registers_, index);
}

uint64_t HoldingRegister::getUint64(size_t index) const {
std::lock_guard lck(m_);
checkBounds(index, 4);
return decode_uint64(registers_, index);
}

void HoldingRegister::setFloat(size_t index, float value) {
std::lock_guard lck(m_);
checkBounds(index, 2);// Check if two registers are available
uint32_t floatAsInt;
std::memcpy(&floatAsInt, &value, sizeof(float));// Interpret float as uint32
setUint32(index, floatAsInt); // Store as uint32
checkBounds(index, 2);
encode_float(value, registers_, index);
}

float HoldingRegister::getFloat(size_t index) const {
std::lock_guard lck(m_);
checkBounds(index, 2);
uint32_t floatAsInt = getUint32(index);// Get value as uint32
float result;
std::memcpy(&result, &floatAsInt, sizeof(float));// Convert back to float
return result;
return decode_float(registers_, index);
}

void HoldingRegister::printRegisters() const {
void HoldingRegister::setDouble(size_t index, double value) {
std::lock_guard lck(m_);
for (size_t i = 0; i < registers_.size(); ++i) {
std::cout << "Register[" << i << "] = " << registers_[i] << std::endl;
}
checkBounds(index, 4);
encode_double(value, registers_, index);
}

double HoldingRegister::getDouble(size_t index) const {
std::lock_guard lck(m_);
checkBounds(index, 4);
return decode_double(registers_, index);
}

void HoldingRegister::checkBounds(size_t index, size_t count) const {
Expand Down
8 changes: 8 additions & 0 deletions src/simple_socket/modbus/ModbusClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,18 @@ uint32_t ModbusClient::read_uint32(uint16_t address, uint8_t unit_id) {
return decode_uint32(pimpl_->read_holding_registers(address, 2, unit_id));
}

uint64_t ModbusClient::read_uint64(uint16_t address, uint8_t unit_id) {
return decode_uint64(pimpl_->read_holding_registers(address, 4, unit_id));
}

float ModbusClient::read_float(uint16_t address, uint8_t unit_id) {
return decode_float(pimpl_->read_holding_registers(address, 2, unit_id));
}

double ModbusClient::read_double(uint16_t address, uint8_t unit_id) {
return decode_double(pimpl_->read_holding_registers(address, 4, unit_id));
}

bool ModbusClient::write_single_register(uint16_t address, uint16_t value, uint8_t unit_id) {
return pimpl_->write_single_register(address, value, unit_id);
}
Expand Down
51 changes: 40 additions & 11 deletions tests/test_modbus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#include <catch2/matchers/catch_matchers_floating_point.hpp>

#include "simple_socket/modbus/HoldingRegister.hpp"
#include "simple_socket/modbus/ModbusServer.hpp"
#include "simple_socket/modbus/ModbusClient.hpp"
#include "simple_socket/modbus/ModbusServer.hpp"

#include "simple_socket/util/port_query.hpp"

Expand All @@ -15,7 +15,7 @@ using namespace simple_socket;
TEST_CASE("HoldingRegister basic operations", "[HoldingRegister]") {
SECTION("Initialization and size check") {
HoldingRegister reg(10);
REQUIRE(reg.size() == 10); // Check if size is correct after initialization
REQUIRE(reg.size() == 10);// Check if size is correct after initialization
}

SECTION("Setting and getting uint16_t values") {
Expand All @@ -26,8 +26,8 @@ TEST_CASE("HoldingRegister basic operations", "[HoldingRegister]") {
REQUIRE(reg.getUint16(0) == 0x1234);

// Check out-of-bounds behavior
REQUIRE_THROWS_AS(reg.setUint16(10, 0x1234), std::out_of_range); // Index out of bounds
REQUIRE_THROWS_AS(reg.getUint16(10), std::out_of_range); // Index out of bounds
REQUIRE_THROWS_AS(reg.setUint16(10, 0x1234), std::out_of_range);// Index out of bounds
REQUIRE_THROWS_AS(reg.getUint16(10), std::out_of_range); // Index out of bounds
}

SECTION("Setting and getting uint32_t values") {
Expand All @@ -38,12 +38,30 @@ TEST_CASE("HoldingRegister basic operations", "[HoldingRegister]") {
REQUIRE(reg.getUint32(1) == 0x12345678);

// Check that high and low parts are correctly placed
REQUIRE(reg.getUint16(1) == 0x1234); // High 16 bits
REQUIRE(reg.getUint16(2) == 0x5678); // Low 16 bits
REQUIRE(reg.getUint16(1) == 0x1234);// High 16 bits
REQUIRE(reg.getUint16(2) == 0x5678);// Low 16 bits

// Check out-of-bounds behavior
REQUIRE_THROWS_AS(reg.setUint32(9, 0x12345678), std::out_of_range); // Not enough space for two registers
REQUIRE_THROWS_AS(reg.getUint32(9), std::out_of_range); // Not enough space for two registers
REQUIRE_THROWS_AS(reg.setUint32(9, 0x12345678), std::out_of_range);// Not enough space for two registers
REQUIRE_THROWS_AS(reg.getUint32(9), std::out_of_range); // Not enough space for two registers
}

SECTION("Setting and getting uint64_t values") {
HoldingRegister reg(10);

// Set and get uint64_t at a valid index
reg.setUint64(1, 0x123456789ABCDEF0);
REQUIRE(reg.getUint64(1) == 0x123456789ABCDEF0);

// Check that the high and low parts are correctly placed in four 16-bit registers
REQUIRE(reg.getUint16(1) == 0x1234);// Highest 16 bits
REQUIRE(reg.getUint16(2) == 0x5678);// Next 16 bits
REQUIRE(reg.getUint16(3) == 0x9ABC);// Next 16 bits
REQUIRE(reg.getUint16(4) == 0xDEF0);// Lowest 16 bits

// Check out-of-bounds behavior
REQUIRE_THROWS_AS(reg.setUint64(7, 0x123456789ABCDEF0), std::out_of_range);// Not enough space for four registers
REQUIRE_THROWS_AS(reg.getUint64(7), std::out_of_range); // Not enough space for four registers
}

SECTION("Setting and getting float values") {
Expand All @@ -55,10 +73,22 @@ TEST_CASE("HoldingRegister basic operations", "[HoldingRegister]") {
REQUIRE_THAT(reg.getFloat(0), Catch::Matchers::WithinRel(value));

// Check out-of-bounds behavior
REQUIRE_THROWS_AS(reg.setFloat(9, value), std::out_of_range); // Not enough space for two registers
REQUIRE_THROWS_AS(reg.getFloat(9), std::out_of_range); // Not enough space for two registers
REQUIRE_THROWS_AS(reg.setFloat(9, value), std::out_of_range);// Not enough space for two registers
REQUIRE_THROWS_AS(reg.getFloat(9), std::out_of_range); // Not enough space for two registers
}

SECTION("Setting and getting double values") {
HoldingRegister reg(10);
double value = 3.141592653589793;

// Set and get double at valid index
reg.setDouble(0, value);
REQUIRE_THAT(reg.getDouble(0), Catch::Matchers::WithinRel(value));

// Check out-of-bounds behavior
REQUIRE_THROWS_AS(reg.setDouble(7, value), std::out_of_range);// Not enough space for four registers
REQUIRE_THROWS_AS(reg.getDouble(7), std::out_of_range); // Not enough space for four registers
}
}

TEST_CASE("Test modbus client/server") {
Expand All @@ -84,5 +114,4 @@ TEST_CASE("Test modbus client/server") {
REQUIRE_THAT(client.read_float(3), Catch::Matchers::WithinRel(v3));

server.stop();

}

0 comments on commit 2b6f68c

Please sign in to comment.