Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Modbus #1893

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions include/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ struct CONFIG_T {
double Latitude;
uint8_t SunsetType;
} Ntp;
struct {
bool TCPEnabled;
uint32_t Port;
uint32_t Clients;
uint32_t IDDTUPro;
uint32_t IDTotal;
uint32_t IDMeter;
} Modbus;

struct {
bool Enabled;
Expand Down
79 changes: 79 additions & 0 deletions include/ModbusDtu.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <vector>

#include <TaskSchedulerDeclarations.h>

// eModbus
#include "ModbusMessage.h"
#include "ModbusServerTCPasync.h"

class ModbusDTUMessage : public ModbusMessage {
private:
// Value cache, mostly for conversion
union Value {
float val_float;
int16_t val_i16;
uint16_t val_u16;
int32_t val_i32;
uint32_t val_u32;
uint64_t val_u64;
uint32_t val_ip;
} value;

// Conversion cache
union Conversion {
// fixed point converted to u32
uint32_t fixed_point_u32;
// fixed point converted to u16
uint16_t fixed_point_u16;
// uint64 converted to hex string
char u64_hex_str[sizeof(uint64_t) * 8 + 1];
// ip address converted to String
char ip_str[12];
} conv;

public:
// Default empty message Constructor - optionally takes expected size of MM_data
explicit ModbusDTUMessage(uint16_t dataLen);

// Special message Constructor - takes a std::vector<uint8_t>
explicit ModbusDTUMessage(std::vector<uint8_t> s);

// Add float to Modbus register
uint16_t addFloat32(const float_t &val, const size_t reg_offset);

// Add float as 32 bit decimal fixed point to Modbus register
uint16_t addFloatAsDecimalFixedPoint32(const float_t &val, const float &precision, const size_t reg_offset);

// Add float as 16 bit decimal fixed point to Modbus register
uint16_t addFloatAsDecimalFixedPoint16(const float_t &val, const float &precision);

// Add string to Modbus register
uint16_t addString(const char * const str, const size_t length, const size_t reg_offset);

// Add string to Modbus register
uint16_t addString(const String &str, const size_t reg_offset);

// Add uint16 to Modbus register
uint16_t addUInt16(const uint16_t val);

// Add uint32 to Modbus register
uint16_t addUInt32(const uint32_t val, const size_t reg_offset);

// Add uint64 to Modbus register
uint16_t addUInt64(const uint64_t val, const size_t reg_offset);

// Convert uint64 to hex string and add to Modbus register
uint16_t addUInt64AsHexString(const uint64_t val, const size_t reg_offset);

// Convert IP address to string and add to Modbus register
uint16_t addIPAddressAsString(const IPAddress val, const size_t reg_offset);
};

ModbusMessage DTUPro(ModbusMessage request);
ModbusMessage OpenDTUTotal(ModbusMessage request);
ModbusMessage OpenDTUMeter(ModbusMessage request);

extern ModbusServerTCPasync ModbusTCPServer;
17 changes: 17 additions & 0 deletions include/ModbusSettings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

class ModbusSettingsClass {
public:
ModbusSettingsClass();
void init();

void performConfig();

private:
void startTCP();

void stopTCP();
};

extern ModbusSettingsClass ModbusSettings;
2 changes: 2 additions & 0 deletions include/WebApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "WebApi_inverter.h"
#include "WebApi_limit.h"
#include "WebApi_maintenance.h"
#include "WebApi_modbus.h"
#include "WebApi_mqtt.h"
#include "WebApi_network.h"
#include "WebApi_ntp.h"
Expand Down Expand Up @@ -55,6 +56,7 @@ class WebApiClass {
WebApiInverterClass _webApiInverter;
WebApiLimitClass _webApiLimit;
WebApiMaintenanceClass _webApiMaintenance;
WebApiModbusClass _webApiModbus;
WebApiMqttClass _webApiMqtt;
WebApiNetworkClass _webApiNetwork;
WebApiNtpClass _webApiNtp;
Expand Down
15 changes: 15 additions & 0 deletions include/WebApi_modbus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include <ESPAsyncWebServer.h>
#include <TaskSchedulerDeclarations.h>

class WebApiModbusClass {
public:
void init(AsyncWebServer& server, Scheduler& scheduler);

private:
void onModbusStatus(AsyncWebServerRequest* request);
void onModbusAdminGet(AsyncWebServerRequest* request);
void onModbusAdminPost(AsyncWebServerRequest* request);
};
7 changes: 7 additions & 0 deletions include/defaults.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@
#define NTP_LATITUDE 51.1657f
#define NTP_SUNSETTYPE 1U

#define MODBUS_TCP_ENABLED false
#define MODBUS_PORT 502
#define MODBUS_CLIENTS 1
#define MODBUS_ID_DTUPRO 1
#define MODBUS_ID_TOTAL 125
#define MODBUS_ID_METER 243

#define MQTT_ENABLED false
#define MQTT_HOST ""
#define MQTT_PORT 1883U
Expand Down
7 changes: 7 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ build_flags =
build_unflags =
-std=gnu++11

; Ignore dependencies of eModbus as they are fulfilled by other library variants
lib_ignore =
AsyncTCP
ESPAsyncTCP
custom-Ethernet

lib_deps =
mathieucarbou/ESP Async WebServer @ 2.10.8
bblanchon/ArduinoJson @ 7.0.4
Expand All @@ -45,6 +51,7 @@ lib_deps =
olikraus/U8g2 @ 2.35.19
buelowp/sunset @ 1.1.7
https://github.com/arkhipenko/TaskScheduler#testing
https://github.com/eModbus/eModbus.git

extra_scripts =
pre:pio-scripts/auto_firmware_version.py
Expand Down
16 changes: 16 additions & 0 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ bool ConfigurationClass::write()
ntp["longitude"] = config.Ntp.Longitude;
ntp["sunsettype"] = config.Ntp.SunsetType;

JsonObject modbus = doc["modbus"].to<JsonObject>();
modbus["tcp_enabled"] = config.Modbus.TCPEnabled;
modbus["port"] = config.Modbus.Port;
modbus["clients"] = config.Modbus.Clients;
modbus["id_dtupro"] = config.Modbus.IDDTUPro;
modbus["id_total"] = config.Modbus.IDTotal;
modbus["id_meter"] = config.Modbus.IDMeter;

JsonObject mqtt = doc["mqtt"].to<JsonObject>();
mqtt["enabled"] = config.Mqtt.Enabled;
mqtt["hostname"] = config.Mqtt.Hostname;
Expand Down Expand Up @@ -230,6 +238,14 @@ bool ConfigurationClass::read()
config.Ntp.Longitude = ntp["longitude"] | NTP_LONGITUDE;
config.Ntp.SunsetType = ntp["sunsettype"] | NTP_SUNSETTYPE;

JsonObject modbus = doc["modbus"];
config.Modbus.TCPEnabled = modbus["tcp_enabled"] | MODBUS_TCP_ENABLED;
config.Modbus.Port = modbus["port"] | MODBUS_PORT;
config.Modbus.Clients = modbus["clients"] | MODBUS_CLIENTS;
config.Modbus.IDDTUPro = modbus["id_dtupro"] | MODBUS_ID_DTUPRO;
config.Modbus.IDTotal = modbus["id_total"] | MODBUS_ID_TOTAL;
config.Modbus.IDMeter = modbus["id_meter"] | MODBUS_ID_METER;

JsonObject mqtt = doc["mqtt"];
config.Mqtt.Enabled = mqtt["enabled"] | MQTT_ENABLED;
strlcpy(config.Mqtt.Hostname, mqtt["hostname"] | MQTT_HOST, sizeof(config.Mqtt.Hostname));
Expand Down
123 changes: 123 additions & 0 deletions src/ModbusDtu.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2024 Bobby Noelte
*/
#include <array>
#include <cstring>
#include <string>

// OpenDTU
#include "ModbusDtu.h"


ModbusDTUMessage::ModbusDTUMessage(uint16_t dataLen = 0) : ModbusMessage(dataLen) {
value.val_float = NAN;
}

ModbusDTUMessage::ModbusDTUMessage(std::vector<uint8_t> s) : ModbusMessage(s) {
value.val_float = NAN;
}

uint16_t ModbusDTUMessage::addFloat32(const float_t &val, const size_t reg_offset) {
// Use union to convert from float to uint32
value.val_float = val;

return addUInt32(value.val_u32, reg_offset);
}

uint16_t ModbusDTUMessage::addFloatAsDecimalFixedPoint32(const float_t &val, const float &precision, const size_t reg_offset) {
// Check if value is already converted to fixed point
if (value.val_float != val) {
// Multiply by 10^precision to shift the decimal point
// Round the scaled value to the nearest integer
// Use union to convert from fixed point to uint32
value.val_i32 = round(val * std::pow(10, precision));
// remember converted value
conv.fixed_point_u32 = value.val_u32;
// mark conversion
value.val_float = val;
}

return addUInt32(conv.fixed_point_u32, reg_offset);
}

uint16_t ModbusDTUMessage::addFloatAsDecimalFixedPoint16(const float_t &val, const float &precision) {
// Multiply by 10^precision to shift the decimal point
// Round the scaled value to the nearest integer
// Use union to convert from fixed point to uint16
value.val_i16 = round(val * std::pow(10, precision));

add(value.val_u16);
return value.val_u16;
}

uint16_t ModbusDTUMessage::addString(const char * const str, const size_t length, const size_t reg_offset) {
// Check if the position is within the bounds of the string
size_t offset = reg_offset * sizeof(uint16_t);
if (offset + sizeof(uint16_t) <= length) {
// Reinterpret the memory at position 'offset' as uint16_t
std::memcpy(&value.val_u16, str + offset, sizeof(uint16_t));
} else {
value.val_u16 = 0;
}

add(value.val_u16);
return value.val_u16;
}

uint16_t ModbusDTUMessage::addString(const String &str, const size_t reg_offset) {
return addString(str.c_str(), str.length(), reg_offset);
}

uint16_t ModbusDTUMessage::addUInt16(const uint16_t val) {
add(val);
return val;
}

uint16_t ModbusDTUMessage::addUInt32(const uint32_t val, const size_t reg_offset) {
if (reg_offset <= 1) {
value.val_u16 = val >> (16 * (1 - reg_offset));
} else {
value.val_u16 = 0;
}
add(value.val_u16);
return value.val_u16;
}

uint16_t ModbusDTUMessage::addUInt64(const uint64_t val, const size_t reg_offset) {
if (reg_offset <= 3) {
value.val_u16 = val >> (16 * (3 - reg_offset));
} else {
value.val_u16 = 0;
}
add(value.val_u16);
return value.val_u16;
}

uint16_t ModbusDTUMessage::addUInt64AsHexString(const uint64_t val, const size_t reg_offset) {
// Check if value is already converted to hex string
if (val != value.val_u64) {
snprintf(&conv.u64_hex_str[0], sizeof(conv.u64_hex_str), "%0x%08x",
((uint32_t)((val >> 32) & 0xFFFFFFFFUL)),
((uint32_t)(val & 0xFFFFFFFFUL)));
// mark conversion
value.val_u64 = val;
}

return addString(&conv.u64_hex_str[0], sizeof(conv.u64_hex_str), reg_offset);
}

uint16_t ModbusDTUMessage::addIPAddressAsString(const IPAddress val, const size_t reg_offset) {
// Check if value is already converted to hex string
if (val != value.val_ip) {
String str(val.toString());
std::memcpy(&conv.ip_str, str.c_str(), std::min(sizeof(conv.ip_str), str.length()));
// mark conversion
value.val_ip = val;
}

return addString(&conv.ip_str[0], sizeof(conv.ip_str), reg_offset);
}

// Create server(s)
ModbusServerTCPasync ModbusTCPServer;
Loading