1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00
VILLASnode/include/villas/nodes/modbus.hpp
Steffen Vogel 9b3a33f31b Remove unused C++20 include which breaks build on C++17 compilers
Signed-off-by: Steffen Vogel <steffen.vogel@opal-rt.com>
2024-02-13 16:23:02 +01:00

300 lines
9 KiB
C++

/* A Modbus node-type supporting RTU and TCP based transports.
*
* Author: Philipp Jungkamp <philipp.jungkamp@opal-rt.com>
* SPDX-FileCopyrightText: 2023 OPAL-RT Germany GmbH
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <algorithm>
#include <numeric>
#include <optional>
#include <stdint.h>
#include <variant>
#include <vector>
#include <modbus/modbus.h>
#include <villas/exceptions.hpp>
#include <villas/node.hpp>
#include <villas/node/config.hpp>
#include <villas/sample.hpp>
#include <villas/task.hpp>
#include <villas/timing.hpp>
namespace villas {
namespace node {
namespace modbus {
using modbus_addr_t = uint16_t;
using modbus_addrdiff_t = int32_t;
enum class Parity : char {
None = 'N',
Even = 'E',
Odd = 'O',
};
enum class Endianess : char {
Big,
Little,
};
// The settings for an RTU modbus connection.
struct Rtu {
std::string device;
Parity parity;
int baudrate;
int data_bits;
int stop_bits;
unsigned char unit;
static Rtu parse(json_t *json);
};
// The settings for an TCP MODBUS connection.
struct Tcp {
std::string remote;
uint16_t port;
std::optional<unsigned char> unit;
static Tcp parse(json_t *json);
};
// Forward declarations
class RegisterMappingSingle;
// A merged block of mappings.
using RegisterMappingBlock = std::vector<RegisterMappingSingle>;
// Either a single mapping or a merged block of mappings.
using RegisterMapping =
std::variant<RegisterMappingSingle, RegisterMappingBlock>;
// Swap the two bytes of a 16 bit integer.
uint16_t byteswap(uint16_t i);
// The start of a single register mapping.
modbus_addr_t blockBegin(RegisterMappingSingle const &single);
// The start of a block of register mappings.
modbus_addr_t blockBegin(RegisterMappingBlock const &block);
// The start of either a single or a block of register mappings.
modbus_addr_t blockBegin(RegisterMapping const &mapping);
// The end of a single register mapping.
modbus_addr_t blockEnd(RegisterMappingSingle const &single);
// The end of a block of register mappings.
modbus_addr_t blockEnd(RegisterMappingBlock const &block);
// The end of either a single or a block of register mappings.
modbus_addr_t blockEnd(RegisterMapping const &mapping);
// The number of mapped registers in a single register mapping.
modbus_addr_t mappedRegisters(RegisterMappingSingle const &single);
// The number of mapped registers in a block of register mappings.
modbus_addr_t mappedRegisters(RegisterMappingBlock const &block);
// The number of mapped registers in either a single or a block of register mappings.
modbus_addr_t mappedRegisters(RegisterMapping const &mapping);
// The distance between two blocks.
modbus_addrdiff_t blockDistance(RegisterMapping const &lhs,
RegisterMapping const &rhs);
// Whether there are overlapping bit mappings between lhs and rhs.
bool hasOverlappingBitMapping(RegisterMapping const &lhs,
RegisterMapping const &rhs);
// The compare the addresses of two mappings.
bool compareBlockAddress(RegisterMapping const &lhs,
RegisterMapping const &rhs);
// Parse an Endianess from a null terminated string.
Endianess parseEndianess(char const *str);
// Parse an Parity from a null terminated string.
Parity parseParity(char const *str);
// The mapping from a register to a signal.
class RegisterMappingSingle {
public:
inline static constexpr size_t MAX_REGISTERS =
sizeof(int64_t) / sizeof(int16_t);
struct IntegerToInteger {
Endianess word_endianess;
Endianess byte_endianess;
modbus_addr_t num_registers;
int64_t read(uint16_t const *registers) const;
void write(int64_t i, uint16_t *registers) const;
};
struct IntegerToFloat {
IntegerToInteger integer_conversion;
double offset;
double scale;
double read(uint16_t const *registers) const;
void write(double d, uint16_t *registers) const;
};
struct FloatToFloat {
Endianess word_endianess;
Endianess byte_endianess;
double offset;
double scale;
double read(uint16_t const *registers) const;
void write(double d, uint16_t *registers) const;
};
struct BitToBool {
uint8_t bit;
bool read(uint16_t reg) const;
};
// Conversion rule for registers.
//
// - IntegerToInteger means merging multiple registers.
// - FloatToFloat converts from IEEE float to float.
// - IntegerToFloat converts registers to integer, casts to float
// and then applies offset and scale to produce a float.
// - BitToBool takes a single bit from a registers and reports it as a boolean.
std::variant<IntegerToInteger, IntegerToFloat, FloatToFloat, BitToBool>
conversion;
RegisterMappingSingle(unsigned int signal_index, modbus_addr_t address);
unsigned int signal_index;
modbus_addr_t address;
static RegisterMappingSingle parse(unsigned int index, Signal::Ptr signal,
json_t *json);
SignalData read(uint16_t const *registers, modbus_addr_t length) const;
void write(SignalData data, uint16_t *registers, modbus_addr_t length) const;
modbus_addr_t num_registers() const;
};
class ModbusNode final : public Node {
private:
// The maximum size of a RegisterMappingBlock created during mergeMappings.
// The size of a block here is defined as the difference between blockBegin and blockEnd.
modbus_addr_t max_block_size;
// The minimum block usage of a RegisterMappingBlock created during mergeMappings.
// The usage of a block is defined as the ration of registers used in mappings to the size of the block.
// The size of a block here is defined as the difference between blockBegin and blockEnd.
float min_block_usage;
// The type of connection settings used to initialize the modbus_context.
std::variant<std::monostate, Tcp, Rtu> connection_settings;
// The rate used for periodically querying the modbus device registers.
double rate;
// The timeout in seconds when waiting for a response from a modbus slave/server.
double response_timeout;
// Mappings used to create the input signals from the read registers.
std::vector<RegisterMapping> in_mappings;
// Number of in signals.
unsigned int num_in_signals;
// Mappings used to create the input signals from the read registers.
std::vector<RegisterMapping> out_mappings;
// Number of out signals.
unsigned int num_out_signals;
// The interval in seconds for trying to reconnect on connection loss.
double reconnect_interval;
std::vector<uint16_t> read_buffer;
std::vector<uint16_t> write_buffer;
modbus_t *modbus_context;
Task read_task;
std::atomic<bool> reconnecting;
bool isReconnecting();
void reconnect();
static void mergeMappingInplace(RegisterMapping &lhs,
RegisterMappingBlock const &rhs);
static void mergeMappingInplace(RegisterMapping &lhs,
RegisterMappingSingle const &rhs);
bool tryMergeMappingInplace(RegisterMapping &lhs, RegisterMapping const &rhs);
void mergeMappings(std::vector<RegisterMapping> &mappings,
modbus_addrdiff_t max_block_distance);
unsigned int parseMappings(std::vector<RegisterMapping> &mappings,
json_t *json);
int readMapping(RegisterMappingSingle const &mapping,
uint16_t const *registers, modbus_addr_t num_registers,
SignalData *signals, unsigned int num_signals);
int readMapping(RegisterMappingBlock const &mapping,
uint16_t const *registers, modbus_addr_t num_registers,
SignalData *signals, unsigned int num_signals);
int readMapping(RegisterMapping const &mapping, uint16_t const *registers,
modbus_addr_t num_registers, SignalData *signals,
unsigned int num_signals);
int readBlock(RegisterMapping const &mapping, SignalData *signals,
size_t num_signals);
virtual int _read(struct Sample *smps[], unsigned int cnt);
int writeMapping(RegisterMappingSingle const &mapping, uint16_t *registers,
modbus_addr_t num_registers, SignalData const *signals,
unsigned int num_signals);
int writeMapping(RegisterMappingBlock const &mapping, uint16_t *registers,
modbus_addr_t num_registers, SignalData const *signals,
unsigned int num_signals);
int writeMapping(RegisterMapping const &mapping, uint16_t *registers,
modbus_addr_t num_registers, SignalData const *signals,
unsigned int num_signals);
int writeBlock(RegisterMapping const &mapping, SignalData const *signals,
size_t num_signals);
virtual int _write(struct Sample *smps[], unsigned int cnt);
public:
ModbusNode(const uuid_t &id = {}, const std::string &name = "");
virtual ~ModbusNode();
virtual int prepare();
virtual int parse(json_t *json);
virtual int check();
virtual int start();
virtual int stop();
virtual std::vector<int> getPollFDs();
virtual std::vector<int> getNetemFDs();
virtual const std::string &getDetails();
};
} // namespace modbus
} // namespace node
} // namespace villas