Initial release commit

This commit is contained in:
TraYali 2024-03-20 19:08:13 +01:00
commit 0e25af7ef8
15 changed files with 12511 additions and 0 deletions

20
CMakeLists.txt Normal file
View file

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.6.0)
project(hoymilesClient VERSION 0.1.0 LANGUAGES C CXX)
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
include_directories(inc inc/libmodbus inc/hoymiles inc/hoymiles/portParameters)
file(GLOB SOURCES src/*.cpp src/libmodbus/*.c src/hoymiles/*.cpp src/hoymiles/portParameters/*.cpp)
add_executable(hoymilesClient_exec ${SOURCES})
# configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_SOURCE_DIR}/inc/libmodbus/config.h)
if(WIN32)
# find_library(ws2_32_library NAMES libws2_32.a HINTS /usr/x86_64-w64-mingw32/lib)
# target_link_libraries(hoymilesClient_exec "/usr/x86_64-w64-mingw32/lib/libws2_32.a")
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -static -static-libgcc -static-libstdc++")
set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -static-libgcc -static-libstdc++ -lwsock32 -lws2_32")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive")
endif(WIN32)

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# Jenkins
Link for jenkins builds with artifacts:
- [jenkins.trabus322.eu#freeBsd](https://jenkins.trabus322.eu/job/hoymilesClient/)
- [Executable](https://jenkins.trabus322.eu/job/hoymilesClient/lastSuccesfulBuild/artifact/build/hoymilesClient_exec)
- [jenkins.trabus322.eu#linux](https://jenkins.trabus322.eu/job/hoymilesClient_linux/)
- [Executable](https://jenkins.trabus322.eu/job/hoymilesClient_linux/lastSuccesfulBuild/artifact/build/hoymilesClient_exec)
- [jenkins.trabus322.eu#windows](https://jenkins.trabus322.eu/job/hoymilesClient_windows/)
- [Executable](https://jenkins.trabus322.eu/job/hoymilesClient_windows/lastSuccessfulBuild/artifact/build/hoymilesClient_exec.exe)

10966
inc/CLI11.hpp Normal file

File diff suppressed because it is too large Load diff

39
inc/hoymiles/dtu.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef DTU_H
#define DTU_H
#include <vector>
#include <memory>
#include <string>
#include "microinverter.h"
#include "modbus.h"
class Dtu {
private:
std::shared_ptr<class modbus> modbus;
std::vector<Microinverter> microinverters;
bool connected;
void populateMicroinverters();
std::pair<bool, Microinverter*> getMicroinverterBySerialNumber(long long serialNumber);
public:
Dtu(const char *ip_address, int port);
bool isConnected();
// void updateMicroinverters();
void updateMicroinverters(std::vector<std::string> &parametersToGet, bool allParameters, std::vector<long long> &microinvertersToGet);
// void printMicroinverters();
void printMicroinverters(std::vector<std::string> &parametersToGet, bool allParameters, std::vector<long long> &microinvertersToGet);
~Dtu();
};
#endif

View file

@ -0,0 +1,46 @@
#ifndef MICROINVERTER_H
#define MICROINVERTER_H
#include <vector>
#include <memory>
#include <string>
// #include <mutex>
#include "port.h"
#include "modbus.h"
// struct _modbus;
// typedef _modbus modbus_t;
class Microinverter {
private:
// std::shared_ptr<modbus_t*> modbus_context;
std::shared_ptr<class modbus> modbus;
// std::mutex *modbus_context_mutex;
public:
Microinverter(
std::shared_ptr<class modbus> modbus, long long serialNumber);
long long serialNumber;
std::vector<Port> ports;
// void updatePorts();
void updatePorts(std::vector<std::string> &parametersToGet, bool allParameters);
void updatePort(int i);
Port getPort(int i);
// void printPorts();
void printPorts(std::vector<std::string> &parametersToGet, bool allParameters);
};
#endif

38
inc/hoymiles/port.h Normal file
View file

@ -0,0 +1,38 @@
#ifndef PORT_H
#define PORT_H
#include <stdint.h>
#include <string>
#include <vector>
#include "portParametersGeneric.h"
#include "modbus.h"
class Port {
private:
std::shared_ptr<class modbus> modbus;
uint16_t portStartAddress;
void populateParameters();
std::pair<std::shared_ptr<PortParameter>, bool> getParameterByName(std::string name);
void fixCurrent();
bool currentFixed;
public:
Port(std::shared_ptr<class modbus> modbus, uint16_t portStartAddress);
std::vector<std::shared_ptr<PortParameter>> parameters;
// void updateParameters();
void updateParameters(std::vector<std::string> &parametersToGet, bool allParameters);
// void printParameters();
void printParameters(std::vector<std::string> &parametersToGet, bool allParameters);
};
#endif

View file

@ -0,0 +1,87 @@
#ifndef PORTPARAMETERS_H
#define PORTPARAMETERS_H
#include "portParametersGeneric.h"
class PortParameterMicroinverterSerialNumber : public PortParameterInt {
protected:
void setValueFromRegisters(uint16_t *readArray, int registerCount);
public:
PortParameterMicroinverterSerialNumber();
};
class PortParameterPortNumber : public PortParameterInt {
private:
void setValueFromRegisters(uint16_t *readArray, int registerCount);
public:
PortParameterPortNumber();
};
class PortParameterPvVoltage : public PortParameterFloat {
public:
PortParameterPvVoltage();
};
class PortParameterPvCurrentMi : public PortParameterFloat {
public:
PortParameterPvCurrentMi();
};
class PortParameterPvCurrentHm : public PortParameterFloat {
public:
PortParameterPvCurrentHm();
};
class PortParameterGridVoltage : public PortParameterFloat {
public:
PortParameterGridVoltage();
};
class PortParameterGridFrequency : public PortParameterFloat {
public:
PortParameterGridFrequency();
};
class PortParameterPvPower : public PortParameterFloat {
public:
PortParameterPvPower();
};
class PortParameterTodayProduction : public PortParameterInt {
public:
PortParameterTodayProduction();
};
class PortParameterTotalProduction : public PortParameterInt {
public:
PortParameterTotalProduction();
};
class PortParameterTemperature : public PortParameterFloat {
public:
PortParameterTemperature();
};
class PortParameterOperatingStatus : public PortParameterInt {
public:
PortParameterOperatingStatus();
};
class PortParameterAlarmCode : public PortParameterInt {
public:
PortParameterAlarmCode();
};
class PortParameterAlarmCount : public PortParameterInt {
public:
PortParameterAlarmCount();
};
class PortParameterLinkStatus : public PortParameterInt {
public:
PortParameterLinkStatus();
};
#endif

View file

@ -0,0 +1,68 @@
#ifndef PORTPARAMETERSGENERIC_H
#define PORTPARAMETERSGENERIC_H
#include <stdint.h>
#include <string>
#include <memory>
// #include <mutex>
struct _modbus;
typedef _modbus modbus_t;
class PortParameter {
protected:
uint16_t parameterAddressOffset;
int registerSize;
virtual void setValueFromRegisters(uint16_t *readArray, int registerCount);
public:
PortParameter(std::string name, uint16_t parameterAddressOffset, int registerSize);
virtual ~PortParameter();
enum PortParameterValueType { Int, Float };
union PortParameterValue {
long long i;
float f;
};
protected:
PortParameterValueType valueType;
PortParameterValue value;
public:
std::string name;
int age;
std::pair<PortParameterValue, PortParameterValueType> getValue();
virtual std::string getOutputValue();
void updateValue(std::shared_ptr<class modbus> modubs, uint16_t portStartAddress);
};
class PortParameterFloat : public PortParameter {
protected:
int decimalPlaces;
virtual void setValueFromRegisters(uint16_t *readArray, int registerCount);
public:
PortParameterFloat(std::string name, int decimalPlaces, uint16_t parameterAddressOffset, int registerSize);
std::string getOutputValue();
};
class PortParameterInt : public PortParameter {
protected:
virtual void setValueFromRegisters(uint16_t *readArray, int registerCount);
public:
PortParameterInt(std::string name, uint16_t parameterAddressOffset, int registerSize);
std::string getOutputValue();
};
#endif

717
inc/modbus.h Normal file
View file

@ -0,0 +1,717 @@
/* modbus.h
*
* Copyright (C) 20017-2021 Fanzhe Lyu <lvfanzhe@hotmail.com>, all rights reserved.
*
* modbuspp is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MODBUSPP_MODBUS_H
#define MODBUSPP_MODBUS_H
#include <cstring>
#include <stdint.h>
#include <string>
#ifdef ENABLE_MODBUSPP_LOGGING
#include <cstdio>
#define LOG(fmt, ...) printf("[ modbuspp ]" fmt, ##__VA_ARGS__)
#else
#define LOG(...) (void)0
#endif
#ifdef _WIN32
// WINDOWS socket
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <winsock2.h>
#pragma comment(lib, "Ws2_32.lib")
using X_SOCKET = SOCKET;
// using ssize_t = int;
#define X_ISVALIDSOCKET(s) ((s) != INVALID_SOCKET)
#define X_CLOSE_SOCKET(s) closesocket(s)
#define X_ISCONNECTSUCCEED(s) ((s) != SOCKET_ERROR)
#else
// Berkeley socket
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using X_SOCKET = int;
#define X_ISVALIDSOCKET(s) ((s) >= 0)
#define X_CLOSE_SOCKET(s) close(s)
#define X_ISCONNECTSUCCEED(s) ((s) >= 0)
#endif
using SOCKADDR = struct sockaddr;
using SOCKADDR_IN = struct sockaddr_in;
#define MAX_MSG_LENGTH 260
///Function Code
#define READ_COILS 0x01
#define READ_INPUT_BITS 0x02
#define READ_REGS 0x03
#define READ_INPUT_REGS 0x04
#define WRITE_COIL 0x05
#define WRITE_REG 0x06
#define WRITE_COILS 0x0F
#define WRITE_REGS 0x10
///Exception Codes
#define EX_ILLEGAL_FUNCTION 0x01 // Function Code not Supported
#define EX_ILLEGAL_ADDRESS 0x02 // Output Address not exists
#define EX_ILLEGAL_VALUE 0x03 // Output Value not in Range
#define EX_SERVER_FAILURE 0x04 // Slave Deive Fails to process request
#define EX_ACKNOWLEDGE 0x05 // Service Need Long Time to Execute
#define EX_SERVER_BUSY 0x06 // Server Was Unable to Accept MB Request PDU
#define EX_NEGATIVE_ACK 0x07
#define EX_MEM_PARITY_PROB 0x08
#define EX_GATEWAY_PROBLEMP 0x0A // Gateway Path not Available
#define EX_GATEWAY_PROBLEMF 0x0B // Target Device Failed to Response
#define EX_BAD_DATA 0XFF // Bad Data lenght or Address
#define BAD_CON -1
/// Modbus Operator Class
/**
* Modbus Operator Class
* Providing networking support and mobus operation support.
*/
class modbus
{
public:
bool err{};
int err_no{};
std::string error_msg;
modbus(std::string host, uint16_t port);
~modbus();
bool modbus_connect();
void modbus_close() const;
bool is_connected() const { return _connected; }
void modbus_set_slave_id(int id);
int modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer);
int modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer);
int modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer);
int modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer);
int modbus_write_coil(uint16_t address, const bool &to_write);
int modbus_write_register(uint16_t address, const uint16_t &value);
int modbus_write_coils(uint16_t address, uint16_t amount, const bool *value);
int modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value);
private:
bool _connected{};
uint16_t PORT{};
uint32_t _msg_id{};
int _slaveid{};
std::string HOST;
X_SOCKET _socket{};
SOCKADDR_IN _server{};
#ifdef _WIN32
WSADATA wsadata;
#endif
void modbus_build_request(uint8_t *to_send, uint16_t address, int func) const;
int modbus_read(uint16_t address, uint16_t amount, int func);
int modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value);
ssize_t modbus_send(uint8_t *to_send, size_t length);
ssize_t modbus_receive(uint8_t *buffer) const;
void modbuserror_handle(const uint8_t *msg, int func);
void set_bad_con();
void set_bad_input();
};
/**
* Main Constructor of Modbus Connector Object
* @param host IP Address of Host
* @param port Port for the TCP Connection
* @return A Modbus Connector Object
*/
inline modbus::modbus(std::string host, uint16_t port = 502)
{
HOST = host;
PORT = port;
_slaveid = 1;
_msg_id = 1;
_connected = false;
err = false;
err_no = 0;
error_msg = "";
}
/**
* Destructor of Modbus Connector Object
*/
inline modbus::~modbus(void) = default;
/**
* Modbus Slave ID Setter
* @param id ID of the Modbus Server Slave
*/
inline void modbus::modbus_set_slave_id(int id)
{
_slaveid = id;
}
/**
* Build up a Modbus/TCP Connection
* @return If A Connection Is Successfully Built
*/
inline bool modbus::modbus_connect()
{
if (HOST.empty() || PORT == 0)
{
LOG("Missing Host and Port");
return false;
}
else
{
LOG("Found Proper Host %s and Port %d", HOST.c_str(), PORT);
}
#ifdef _WIN32
if (WSAStartup(0x0202, &wsadata))
{
return false;
}
#endif
_socket = socket(AF_INET, SOCK_STREAM, 0);
if (!X_ISVALIDSOCKET(_socket))
{
LOG("Error Opening Socket");
#ifdef _WIN32
WSACleanup();
#endif
return false;
}
else
{
LOG("Socket Opened Successfully");
}
#ifdef WIN32
const DWORD timeout = 20000;
#else
struct timeval timeout
{
};
timeout.tv_sec = 20; // after 20 seconds connect() will timeout
timeout.tv_usec = 0;
#endif
setsockopt(_socket, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(timeout));
setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout));
_server.sin_family = AF_INET;
_server.sin_addr.s_addr = inet_addr(HOST.c_str());
_server.sin_port = htons(PORT);
if (!X_ISCONNECTSUCCEED(connect(_socket, (SOCKADDR *)&_server, sizeof(_server))))
{
LOG("Connection Error");
#ifdef _WIN32
WSACleanup();
#endif
return false;
}
LOG("Connected");
_connected = true;
return true;
}
/**
* Close the Modbus/TCP Connection
*/
inline void modbus::modbus_close() const
{
X_CLOSE_SOCKET(_socket);
#ifdef _WIN32
WSACleanup();
#endif
LOG("Socket Closed");
}
/**
* Modbus Request Builder
* @param to_send Message Buffer to Be Sent
* @param address Reference Address
* @param func Modbus Functional Code
*/
inline void modbus::modbus_build_request(uint8_t *to_send, uint16_t address, int func) const
{
to_send[0] = (uint8_t)(_msg_id >> 8u);
to_send[1] = (uint8_t)(_msg_id & 0x00FFu);
to_send[2] = 0;
to_send[3] = 0;
to_send[4] = 0;
to_send[6] = (uint8_t)_slaveid;
to_send[7] = (uint8_t)func;
to_send[8] = (uint8_t)(address >> 8u);
to_send[9] = (uint8_t)(address & 0x00FFu);
}
/**
* Write Request Builder and Sender
* @param address Reference Address
* @param amount Amount of data to be Written
* @param func Modbus Functional Code
* @param value Data to Be Written
*/
inline int modbus::modbus_write(uint16_t address, uint16_t amount, int func, const uint16_t *value)
{
int status = 0;
uint8_t *to_send;
if (func == WRITE_COIL || func == WRITE_REG)
{
to_send = new uint8_t[12];
modbus_build_request(to_send, address, func);
to_send[5] = 6;
to_send[10] = (uint8_t)(value[0] >> 8u);
to_send[11] = (uint8_t)(value[0] & 0x00FFu);
status = modbus_send(to_send, 12);
}
else if (func == WRITE_REGS)
{
to_send = new uint8_t[13 + 2 * amount];
modbus_build_request(to_send, address, func);
to_send[5] = (uint8_t)(7 + 2 * amount);
to_send[10] = (uint8_t)(amount >> 8u);
to_send[11] = (uint8_t)(amount & 0x00FFu);
to_send[12] = (uint8_t)(2 * amount);
for (int i = 0; i < amount; i++)
{
to_send[13 + 2 * i] = (uint8_t)(value[i] >> 8u);
to_send[14 + 2 * i] = (uint8_t)(value[i] & 0x00FFu);
}
status = modbus_send(to_send, 13 + 2 * amount);
}
else if (func == WRITE_COILS)
{
to_send = new uint8_t[14 + (amount - 1) / 8];
modbus_build_request(to_send, address, func);
to_send[5] = (uint8_t)(7 + (amount + 7) / 8);
to_send[10] = (uint8_t)(amount >> 8u);
to_send[11] = (uint8_t)(amount & 0x00FFu);
to_send[12] = (uint8_t)((amount + 7) / 8);
for (int i = 0; i < (amount + 7) / 8; i++)
to_send[13 + i] = 0; // init needed before summing!
for (int i = 0; i < amount; i++)
{
to_send[13 + i / 8] += (uint8_t)(value[i] << (i % 8u));
}
status = modbus_send(to_send, 14 + (amount - 1) / 8);
}
delete[] to_send;
return status;
}
/**
* Read Request Builder and Sender
* @param address Reference Address
* @param amount Amount of Data to Read
* @param func Modbus Functional Code
*/
inline int modbus::modbus_read(uint16_t address, uint16_t amount, int func)
{
uint8_t to_send[12];
modbus_build_request(to_send, address, func);
to_send[5] = 6;
to_send[10] = (uint8_t)(amount >> 8u);
to_send[11] = (uint8_t)(amount & 0x00FFu);
return modbus_send(to_send, 12);
}
/**
* Read Holding Registers
* MODBUS FUNCTION 0x03
* @param address Reference Address
* @param amount Amount of Registers to Read
* @param buffer Buffer to Store Data Read from Registers
*/
inline int modbus::modbus_read_holding_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
{
if (_connected)
{
modbus_read(address, amount, READ_REGS);
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
modbuserror_handle(to_rec, READ_REGS);
if (err)
return err_no;
for (auto i = 0; i < amount; i++)
{
buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;
buffer[i] += (uint16_t)to_rec[10u + 2u * i];
}
return 0;
}
else
{
set_bad_con();
return BAD_CON;
}
}
/**
* Read Input Registers
* MODBUS FUNCTION 0x04
* @param address Reference Address
* @param amount Amount of Registers to Read
* @param buffer Buffer to Store Data Read from Registers
*/
inline int modbus::modbus_read_input_registers(uint16_t address, uint16_t amount, uint16_t *buffer)
{
if (_connected)
{
modbus_read(address, amount, READ_INPUT_REGS);
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
modbuserror_handle(to_rec, READ_INPUT_REGS);
if (err)
return err_no;
for (auto i = 0; i < amount; i++)
{
buffer[i] = ((uint16_t)to_rec[9u + 2u * i]) << 8u;
buffer[i] += (uint16_t)to_rec[10u + 2u * i];
}
return 0;
}
else
{
set_bad_con();
return BAD_CON;
}
}
/**
* Read Coils
* MODBUS FUNCTION 0x01
* @param address Reference Address
* @param amount Amount of Coils to Read
* @param buffer Buffer to Store Data Read from Coils
*/
inline int modbus::modbus_read_coils(uint16_t address, uint16_t amount, bool *buffer)
{
if (_connected)
{
if (amount > 2040)
{
set_bad_input();
return EX_BAD_DATA;
}
modbus_read(address, amount, READ_COILS);
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
modbuserror_handle(to_rec, READ_COILS);
if (err)
return err_no;
for (auto i = 0; i < amount; i++)
{
buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
}
return 0;
}
else
{
set_bad_con();
return BAD_CON;
}
}
/**
* Read Input Bits(Discrete Data)
* MODBUS FUNCITON 0x02
* @param address Reference Address
* @param amount Amount of Bits to Read
* @param buffer Buffer to store Data Read from Input Bits
*/
inline int modbus::modbus_read_input_bits(uint16_t address, uint16_t amount, bool *buffer)
{
if (_connected)
{
if (amount > 2040)
{
set_bad_input();
return EX_BAD_DATA;
}
modbus_read(address, amount, READ_INPUT_BITS);
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
if (err)
return err_no;
for (auto i = 0; i < amount; i++)
{
buffer[i] = (bool)((to_rec[9u + i / 8u] >> (i % 8u)) & 1u);
}
modbuserror_handle(to_rec, READ_INPUT_BITS);
return 0;
}
else
{
return BAD_CON;
}
}
/**
* Write Single Coils
* MODBUS FUNCTION 0x05
* @param address Reference Address
* @param to_write Value to be Written to Coil
*/
inline int modbus::modbus_write_coil(uint16_t address, const bool &to_write)
{
if (_connected)
{
int value = to_write * 0xFF00;
modbus_write(address, 1, WRITE_COIL, (uint16_t *)&value);
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
modbuserror_handle(to_rec, WRITE_COIL);
if (err)
return err_no;
return 0;
}
else
{
set_bad_con();
return BAD_CON;
}
}
/**
* Write Single Register
* FUCTION 0x06
* @param address Reference Address
* @param value Value to Be Written to Register
*/
inline int modbus::modbus_write_register(uint16_t address, const uint16_t &value)
{
if (_connected)
{
modbus_write(address, 1, WRITE_REG, &value);
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
modbuserror_handle(to_rec, WRITE_COIL);
if (err)
return err_no;
return 0;
}
else
{
set_bad_con();
return BAD_CON;
}
}
/**
* Write Multiple Coils
* MODBUS FUNCTION 0x0F
* @param address Reference Address
* @param amount Amount of Coils to Write
* @param value Values to Be Written to Coils
*/
inline int modbus::modbus_write_coils(uint16_t address, uint16_t amount, const bool *value)
{
if (_connected)
{
uint16_t *temp = new uint16_t[amount];
for (int i = 0; i < amount; i++)
{
temp[i] = (uint16_t)value[i];
}
modbus_write(address, amount, WRITE_COILS, temp);
delete[] temp;
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
modbuserror_handle(to_rec, WRITE_COILS);
if (err)
return err_no;
return 0;
}
else
{
set_bad_con();
return BAD_CON;
}
}
/**
* Write Multiple Registers
* MODBUS FUNCION 0x10
* @param address Reference Address
* @param amount Amount of Value to Write
* @param value Values to Be Written to the Registers
*/
inline int modbus::modbus_write_registers(uint16_t address, uint16_t amount, const uint16_t *value)
{
if (_connected)
{
modbus_write(address, amount, WRITE_REGS, value);
uint8_t to_rec[MAX_MSG_LENGTH];
ssize_t k = modbus_receive(to_rec);
if (k == -1)
{
set_bad_con();
return BAD_CON;
}
modbuserror_handle(to_rec, WRITE_REGS);
if (err)
return err_no;
return 0;
}
else
{
set_bad_con();
return BAD_CON;
}
}
/**
* Data Sender
* @param to_send Request to Be Sent to Server
* @param length Length of the Request
* @return Size of the request
*/
inline ssize_t modbus::modbus_send(uint8_t *to_send, size_t length)
{
_msg_id++;
return send(_socket, (const char *)to_send, (size_t)length, 0);
}
/**
* Data Receiver
* @param buffer Buffer to Store the Data Retrieved
* @return Size of Incoming Data
*/
inline ssize_t modbus::modbus_receive(uint8_t *buffer) const
{
return recv(_socket, (char *)buffer, MAX_MSG_LENGTH, 0);
}
inline void modbus::set_bad_con()
{
err = true;
error_msg = "BAD CONNECTION";
}
inline void modbus::set_bad_input()
{
err = true;
error_msg = "BAD FUNCTION INPUT";
}
/**
* Error Code Handler
* @param msg Message Received from the Server
* @param func Modbus Functional Code
*/
inline void modbus::modbuserror_handle(const uint8_t *msg, int func)
{
err = false;
error_msg = "NO ERR";
if (msg[7] == func + 0x80)
{
err = true;
switch (msg[8])
{
case EX_ILLEGAL_FUNCTION:
error_msg = "1 Illegal Function";
break;
case EX_ILLEGAL_ADDRESS:
error_msg = "2 Illegal Address";
break;
case EX_ILLEGAL_VALUE:
error_msg = "3 Illegal Value";
break;
case EX_SERVER_FAILURE:
error_msg = "4 Server Failure";
break;
case EX_ACKNOWLEDGE:
error_msg = "5 Acknowledge";
break;
case EX_SERVER_BUSY:
error_msg = "6 Server Busy";
break;
case EX_NEGATIVE_ACK:
error_msg = "7 Negative Acknowledge";
break;
case EX_MEM_PARITY_PROB:
error_msg = "8 Memory Parity Problem";
break;
case EX_GATEWAY_PROBLEMP:
error_msg = "10 Gateway Path Unavailable";
break;
case EX_GATEWAY_PROBLEMF:
error_msg = "11 Gateway Target Device Failed to Respond";
break;
default:
error_msg = "UNK";
break;
}
}
}
#endif //MODBUSPP_MODBUS_H

116
src/hoymiles/dtu.cpp Normal file
View file

@ -0,0 +1,116 @@
#include <vector>
#include <iostream>
#include <string>
#include "modbus.h"
#include "dtu.h"
#include "microinverter.h"
#include "portParameters.h"
Dtu::Dtu(const char *ip_address, int port) {
class modbus modbus{ip_address, (uint16_t) port};
this->modbus = std::make_shared<class modbus>(modbus);
if (!this->modbus.get()->modbus_connect()) {
std::cerr << "conn_error" << std::endl;
this->connected = false;
}
else {
this->connected = true;
}
if(this->connected) {
this->populateMicroinverters();
}
}
bool Dtu::isConnected() {
return this->connected;
}
Dtu::~Dtu() {
this->modbus.get()->modbus_close();
}
void Dtu::populateMicroinverters() {
uint16_t portStartAddress = 0x1000;
uint16_t readArray[1];
int registerCount;
registerCount = this->modbus.get()->modbus_read_holding_registers(portStartAddress + 0x0021, 1, readArray);
while(registerCount != -1 && readArray[0] == 0x700) {
Port port{ this->modbus, portStartAddress };
PortParameterMicroinverterSerialNumber portParameterMicroinverterSerialNumber{};
portParameterMicroinverterSerialNumber.updateValue(this->modbus, portStartAddress);
long long serialNumber = portParameterMicroinverterSerialNumber.getValue().first.i;
std::pair<bool, Microinverter*> getMicroinverterBySerialNumber = this->getMicroinverterBySerialNumber(serialNumber);
if(getMicroinverterBySerialNumber.first) {
getMicroinverterBySerialNumber.second->ports.push_back(port);
}
else {
Microinverter microinverter{ this->modbus, serialNumber };
this->microinverters.push_back(microinverter);
this->microinverters.back().ports.push_back(port);
}
portStartAddress += 0x0028;
registerCount = this->modbus.get()->modbus_read_holding_registers(portStartAddress + 0x0021, 1, readArray);
}
}
std::pair<bool, Microinverter*> Dtu::getMicroinverterBySerialNumber(long long serialNumber) {
std::vector<Microinverter>::iterator microinvertersIterator = this->microinverters.begin();
while(microinvertersIterator != this->microinverters.end()) {
if(microinvertersIterator->serialNumber == serialNumber) {
return std::pair<bool, Microinverter*>(true, &*microinvertersIterator);
}
else{
microinvertersIterator++;
}
}
return std::pair<bool, Microinverter*>(false, &*microinvertersIterator);
}
void Dtu::updateMicroinverters(std::vector<std::string> &parametersToGet, bool allParameters, std::vector<long long> &microinvertersToGet) {
if(microinvertersToGet.empty()) {
std::vector<Microinverter>::iterator microinvertersIterator = this->microinverters.begin();
while(microinvertersIterator != this->microinverters.end()) {
microinvertersToGet.push_back(microinvertersIterator->serialNumber);
microinvertersIterator++;
}
}
std::vector<long long>::iterator microinvertersToGetIterator = microinvertersToGet.begin();
while(microinvertersToGetIterator != microinvertersToGet.end()) {
std::pair<bool, Microinverter*> microinverterPair = this->getMicroinverterBySerialNumber(*microinvertersToGetIterator);
if(microinverterPair.first) {
microinverterPair.second->updatePorts(parametersToGet, allParameters);
}
microinvertersToGetIterator++;
}
}
void Dtu::printMicroinverters(std::vector<std::string> &parametersToGet, bool allParameters, std::vector<long long> &microinvertersToGet) {
if(microinvertersToGet.empty()) {
std::vector<Microinverter>::iterator microinvertersIterator = this->microinverters.begin();
while(microinvertersIterator != this->microinverters.end()) {
microinvertersToGet.push_back(microinvertersIterator->serialNumber);
microinvertersIterator++;
}
}
std::vector<long long>::iterator microinvertersToGetIterator = microinvertersToGet.begin();
while(microinvertersToGetIterator != microinvertersToGet.end()) {
std::pair<bool, Microinverter*> microinverterPair = this->getMicroinverterBySerialNumber(*microinvertersToGetIterator);
if(microinverterPair.first) {
microinverterPair.second->printPorts(parametersToGet, allParameters);
}
microinvertersToGetIterator++;
}
}

View file

@ -0,0 +1,35 @@
// #include <thread>
#include <iostream>
#include <memory>
#include <string>
#include "modbus.h"
#include "microinverter.h"
#include "port.h"
Microinverter::Microinverter(std::shared_ptr<class modbus> modbus, long long serialNumber) {
this->modbus = modbus;
// this->modbus_context_mutex = modbus_context_mutex;
this->serialNumber = serialNumber;
}
void Microinverter::updatePorts(std::vector<std::string> &parametersToGet, bool allParameters) {
std::vector<Port>::iterator portsIterator = this->ports.begin();
while(portsIterator != this->ports.end()) {
portsIterator->updateParameters(parametersToGet, allParameters);
portsIterator++;
}
}
void Microinverter::printPorts(std::vector<std::string> &parametersToGet, bool allParameters) {
std::cout << "Microinverter: " << this->serialNumber << std::endl;
std::vector<Port>::iterator portsIterator = this->ports.begin();
while(portsIterator != this->ports.end()) {
portsIterator->printParameters(parametersToGet, allParameters);
std::cout << std::endl;
portsIterator++;
}
}

144
src/hoymiles/port.cpp Normal file
View file

@ -0,0 +1,144 @@
#include <algorithm>
#include <cmath>
#include <iostream>
#include <string>
#include <vector>
#include "modbus.h"
#include "port.h"
#include "portParameters.h"
Port::Port(std::shared_ptr<class modbus> modbus, uint16_t portStartAddress) {
this->modbus = modbus;
this->portStartAddress = portStartAddress;
this->currentFixed = false;
this->populateParameters();
}
void Port::populateParameters() {
this->parameters.push_back(std::make_shared<PortParameterMicroinverterSerialNumber>());
this->parameters.push_back(std::make_shared<PortParameterPortNumber>());
this->parameters.push_back(std::make_shared<PortParameterPvVoltage>());
this->parameters.push_back(std::make_shared<PortParameterPvCurrentMi>());
this->parameters.push_back(std::make_shared<PortParameterPvCurrentHm>());
this->parameters.push_back(std::make_shared<PortParameterGridVoltage>());
this->parameters.push_back(std::make_shared<PortParameterGridFrequency>());
this->parameters.push_back(std::make_shared<PortParameterPvPower>());
this->parameters.push_back(std::make_shared<PortParameterTodayProduction>());
this->parameters.push_back(std::make_shared<PortParameterTotalProduction>());
this->parameters.push_back(std::make_shared<PortParameterTemperature>());
this->parameters.push_back(std::make_shared<PortParameterOperatingStatus>());
this->parameters.push_back(std::make_shared<PortParameterAlarmCode>());
this->parameters.push_back(std::make_shared<PortParameterAlarmCount>());
this->parameters.push_back(std::make_shared<PortParameterLinkStatus>());
}
std::pair<std::shared_ptr<PortParameter>, bool> Port::getParameterByName(std::string name) {
std::pair<std::shared_ptr<PortParameter>, bool> result;
result.second = false;
std::vector<std::shared_ptr<PortParameter>>::iterator parametersIterator = this->parameters.begin();
while (parametersIterator != this->parameters.end() && !result.second) {
if (parametersIterator->get()->name == name) {
result.first = *parametersIterator;
result.second = true;
}
parametersIterator++;
}
return result;
}
void Port::fixCurrent() {
if (this->currentFixed) {
return;
}
if (!this->getParameterByName("pvCurrentMI").second != !this->getParameterByName("pvCurrentHM").second) {
this->currentFixed = true;
return;
}
if (this->getParameterByName("pvVoltage").second && this->getParameterByName("pvPower").second) {
if (this->getParameterByName("pvCurrentMI").second && this->getParameterByName("pvCurrentHM").second) {
if(this->getParameterByName("pvPower").first->getValue().first.f > this->getParameterByName("pvVoltage").first->getValue().first.f * this->getParameterByName("pvCurrentMI").first->getValue().first.f) {
this->parameters.erase(std::find(this->parameters.begin(), this->parameters.end(), this->getParameterByName("pvCurrentHM").first));
}
else {
this->parameters.erase(std::find(this->parameters.begin(), this->parameters.end(), this->getParameterByName("pvCurrentM").first));
}
this->currentFixed = true;
}
}
}
void Port::updateParameters(std::vector<std::string> &parametersToGet, bool allParameters) {
if (allParameters && parametersToGet.size() < this->parameters.size()) {
std::vector<std::shared_ptr<PortParameter>>::iterator parametersIterator = this->parameters.begin();
while (parametersIterator != this->parameters.end()) {
if (std::find(parametersToGet.begin(), parametersToGet.end(), parametersIterator->get()->name) == parametersToGet.end()) {
parametersToGet.push_back(parametersIterator->get()->name);
}
parametersIterator++;
}
}
std::vector<std::string>::iterator parametersToGetIterator = parametersToGet.begin();
while (parametersToGetIterator != parametersToGet.end()) {
std::pair<std::shared_ptr<PortParameter>, bool> parameterPair;
parameterPair = this->getParameterByName(*parametersToGetIterator);
if (parameterPair.second) {
parameterPair.first->updateValue(this->modbus, this->portStartAddress);
}
this->fixCurrent();
parametersToGetIterator++;
}
}
void Port::printParameters(std::vector<std::string> &parametersToGet, bool allParameters) {
if (allParameters && parametersToGet.size() < this->parameters.size()) {
std::vector<std::shared_ptr<PortParameter>>::iterator parametersIterator = this->parameters.begin();
while (parametersIterator != this->parameters.end()) {
if (std::find(parametersToGet.begin(), parametersToGet.end(), parametersIterator->get()->name) != parametersToGet.end()) {
parametersToGet.push_back(parametersIterator->get()->name);
}
parametersIterator++;
}
}
std::vector<std::string>::iterator parametersToGetIterator = parametersToGet.begin();
if (parametersToGetIterator != parametersToGet.end()) {
std::cout << "|";
}
while (parametersToGetIterator != parametersToGet.end()) {
std::pair<std::shared_ptr<PortParameter>, bool> parameterPair;
parameterPair = this->getParameterByName(*parametersToGetIterator);
if (parameterPair.second) {
std::cout << " " << parameterPair.first->name << ": " << parameterPair.first->getOutputValue() << " |";
}
parametersToGetIterator++;
}
}

View file

@ -0,0 +1,58 @@
#include <cmath>
#include <string>
#include <iomanip>
#include <sstream>
#include "portParameters.h"
PortParameterMicroinverterSerialNumber::PortParameterMicroinverterSerialNumber() : PortParameterInt("microinverterSerialNumber", 0x0001, 6) {}
void PortParameterMicroinverterSerialNumber::setValueFromRegisters(uint16_t *readArray, int registerCount) {
uint16_t readValue;
std::string readValueString = "";
registerCount = std::ceil(registerCount/2);
for (int i{0}; i < registerCount; i++) {
readValue = readArray[i];
std::stringstream readValueStringStream;
readValueStringStream << std::hex << readValue;
readValueString.append(readValueStringStream.str());
}
this->value.i = std::stoll(readValueString);
}
PortParameterPortNumber::PortParameterPortNumber() : PortParameterInt("portNumber", 0x0007, 1) {}
void PortParameterPortNumber::setValueFromRegisters(uint16_t *readArray, int registerCount) {
if (registerCount > 0) {
this->value.i = readArray[0];
std::stringstream valueStringStream;
valueStringStream << std::hex << this->value.i;
this->value.i = valueStringStream.str().at(0) - '0';
}
}
PortParameterPvVoltage::PortParameterPvVoltage() : PortParameterFloat("pvVoltage", 1, 0x0008, 2) {}
PortParameterPvCurrentMi::PortParameterPvCurrentMi() : PortParameterFloat("pvCurrentMI", 1, 0x000a, 2) {}
PortParameterPvCurrentHm::PortParameterPvCurrentHm() : PortParameterFloat("pvCurrentHM", 2, 0x000a, 2) {}
PortParameterGridVoltage::PortParameterGridVoltage() : PortParameterFloat("gridVoltage", 1, 0x000c, 2) {}
PortParameterGridFrequency::PortParameterGridFrequency() : PortParameterFloat("gridFrequency", 2, 0x000e, 2) {}
PortParameterPvPower::PortParameterPvPower() : PortParameterFloat("pvPower", 1, 0x0010, 2) {}
PortParameterTodayProduction::PortParameterTodayProduction() : PortParameterInt("todayProduction", 0x0012, 2) {}
PortParameterTotalProduction::PortParameterTotalProduction() : PortParameterInt("totalProduction", 0x0014, 4) {}
PortParameterTemperature::PortParameterTemperature() : PortParameterFloat("temperature", 1, 0x0018, 2) {}
PortParameterOperatingStatus::PortParameterOperatingStatus() : PortParameterInt("operatingStatus", 0x001a, 2) {}
PortParameterAlarmCode::PortParameterAlarmCode() : PortParameterInt("alarmCode", 0x001c, 2) {}
PortParameterAlarmCount::PortParameterAlarmCount() : PortParameterInt("alarmCount", 0x001e, 2) {}
PortParameterLinkStatus::PortParameterLinkStatus() : PortParameterInt("linkStatus", 0x020, 2) {}

View file

@ -0,0 +1,91 @@
#include <cmath>
#include <memory>
#include <mutex>
#include <sstream>
#include <iomanip>
#include "modbus.h"
#include "portParametersGeneric.h"
PortParameter::PortParameter(std::string name, uint16_t parameterAddressOffset, int registerSize) {
this->name = name;
this->parameterAddressOffset = parameterAddressOffset;
this->registerSize = registerSize;
this->age = 0;
}
PortParameter::~PortParameter() {}
void PortParameter::setValueFromRegisters(uint16_t *readArray, int registerCount) {}
std::pair<PortParameter::PortParameterValue, PortParameter::PortParameterValueType> PortParameter::getValue() {
return std::pair<PortParameter::PortParameterValue, PortParameter::PortParameterValueType>(this->value, this->valueType);
}
std::string PortParameter::getOutputValue() {
return "yeet";
}
void PortParameter::updateValue(std::shared_ptr<class modbus> modbus, uint16_t portStartAddress) {
uint16_t readArray[this->registerSize];
int registerCount;
// modbus_context_mutex->lock();
registerCount = modbus.get()->modbus_read_holding_registers(portStartAddress + this->parameterAddressOffset, this->registerSize, readArray);
// modbus_context_mutex->unlock();
if(registerCount != 0){
this->age++;
}
else{
registerCount = this->registerSize;
this->setValueFromRegisters(readArray, registerCount);
this->age = 0;
}
}
PortParameterFloat::PortParameterFloat(std::string name, int decimalPlaces, uint16_t parameterAddressOffset, int registerSize) : PortParameter(name, parameterAddressOffset, registerSize) {
this->decimalPlaces = decimalPlaces;
this->valueType = Float;
this->value.f = 0;
}
void PortParameterFloat::setValueFromRegisters(uint16_t *readArray, int registerCount) {
float temp = readArray[0];
temp = temp / std::pow(10, this->decimalPlaces);
this->value.f = temp;
}
std::string PortParameterFloat::getOutputValue() {
std::string separator{"_age"};
std::stringstream valueStringStream;
valueStringStream << std::fixed << std::setprecision(this->decimalPlaces) << this->value.f;
return valueStringStream.str().append(separator.append(std::to_string(this->age)));
}
PortParameterInt::PortParameterInt(std::string name, uint16_t parameterAddressOffset, int registerSize) : PortParameter(name, parameterAddressOffset, registerSize) {
this->valueType = Int;
this->value.i = 0;
}
void PortParameterInt::setValueFromRegisters(uint16_t *readArray, int registerCount) {
uint16_t readValue;
std::string readValueString = "";
registerCount = std::ceil(registerCount/2);
for (int i{0}; i < registerCount; i++) {
readValue = readArray[i];
readValueString.append(std::to_string(readValue));
}
this->value.i = std::stoll(readValueString);
}
std::string PortParameterInt::getOutputValue() {
std::string separator{"_age"};
return std::to_string(this->value.i).append(separator.append(std::to_string(this->age)));
}

78
src/main.cpp Normal file
View file

@ -0,0 +1,78 @@
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include "CLI11.hpp"
#include "modbus.h"
#include "dtu.h"
void sigHandler(int signal);
int main(int argc, char **argv) {
signal(SIGINT, sigHandler);
// signal(SIGBREAK, &handler);
signal(SIGTERM, sigHandler);
signal(SIGABRT, sigHandler);
// signal(SIGQUIT, sigHandler);
CLI::App hoymilesClient{"Client for DTU-Pro/DTU-ProS"};
std::string ipAddress{"127.0.0.1"};
std::string ipAddressHelp{"ipv4 address of DTU {default: " + ipAddress + "}"};
hoymilesClient.add_option<std::string>("-i,--ip_address", ipAddress, ipAddressHelp)->required();
int port{502};
std::string portHelp{"Port of DTU {default: " + std::to_string(port) + "}"};
hoymilesClient.add_option<int>("-p,--port", port, portHelp);
std::vector<std::string> parametersToGet{};
std::string parametersToGetHelp{"List of parameters to fetch, delimited by ',', example[par1,par2,par3]"};
hoymilesClient.add_option<std::vector<std::string>>("-P,--parameters", parametersToGet, parametersToGetHelp)->delimiter(',');
bool allParameters = false;
std::string allParametersHelp{"Fetch all parameters"};
hoymilesClient.add_flag<bool>("-a,--all_parameters", allParameters, allParametersHelp);
bool ignoreNotConnected = false;
std::string ignoreNotConnectedHelp{"Ignore connection errors"};
hoymilesClient.add_flag<bool>("-I,--ignore_conn_error", ignoreNotConnected, ignoreNotConnectedHelp);
std::vector<long long> microinvertersToGet{};
std::string microinvertersToGetHelp{"List of microinverters to fetch, if omitted all are fetched, delimited by ','"};
hoymilesClient.add_option<std::vector<long long>>("-m,--microinverters", microinvertersToGet, microinvertersToGetHelp)->delimiter(',');
try {
hoymilesClient.parse(argc, argv);
} catch (const CLI::ParseError &e) {
return hoymilesClient.exit(e);
}
std::cout << "Mapping out DTU" << std::endl;
auto startTime = std::chrono::high_resolution_clock::now();
Dtu dtu{ipAddress.c_str(), port};
auto endTime = std::chrono::high_resolution_clock::now();
std::cout << "DTU construction time: " << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() << "ms" << std::endl;
while ((dtu.isConnected() || ignoreNotConnected) && (!parametersToGet.empty() || allParameters)) {
startTime = std::chrono::high_resolution_clock::now();
dtu.updateMicroinverters(parametersToGet, allParameters, microinvertersToGet);
endTime = std::chrono::high_resolution_clock::now();
std::cout << "DTU update time: " << std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count() << "ms" << std::endl;
dtu.printMicroinverters(parametersToGet, allParameters, microinvertersToGet);
std::cout << std::endl;
}
return 0;
}
void sigHandler(int signal) {
printf("Interrupted\n");
exit(0);
}