Skip to content

Commit 8b7382a

Browse files
Merge pull request #86 from TheRealMoeder/modular
Modularize code
2 parents 3daff42 + e4bbeb1 commit 8b7382a

16 files changed

Lines changed: 1616 additions & 1335 deletions

src/config/Configuration.cpp

Lines changed: 377 additions & 0 deletions
Large diffs are not rendered by default.

src/config/Configuration.h

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#ifndef CONFIGURATION_H
2+
#define CONFIGURATION_H
3+
4+
#include <Arduino.h>
5+
#include <Preferences.h>
6+
7+
// Fix WifiManager/WebServer conflicts
8+
#ifndef ESP32
9+
#define WEBSERVER_H "fix WifiManager conflict"
10+
#endif
11+
12+
// Platform-specific includes
13+
#ifdef ESP32
14+
#include <HTTPClient.h>
15+
#include <AsyncTCP.h>
16+
#include <ESPmDNS.h>
17+
#include <WiFi.h>
18+
#else
19+
#include <ESP8266HTTPClient.h>
20+
#include <ESPAsyncTCP.h>
21+
#include <ESP8266mDNS.h>
22+
#endif
23+
24+
// Web & JSON libraries
25+
#include <ArduinoJson.h>
26+
#include <WiFiManager.h>
27+
#include <PubSubClient.h>
28+
#include <WiFiClient.h>
29+
#include <ESPAsyncWebServer.h>
30+
#include <WiFiUdp.h>
31+
#include <ModbusIP_ESP8266.h>
32+
33+
// ============================================================================
34+
// DEBUG & TIMING
35+
// ============================================================================
36+
37+
#define DEBUG true // set to false for no DEBUG output
38+
#define DEBUG_SERIAL if(DEBUG)Serial
39+
40+
extern unsigned long startMillis;
41+
extern unsigned long startMillis_sunspec;
42+
extern unsigned long currentMillis;
43+
44+
// ============================================================================
45+
// CONFIGURATION VARIABLES (stored in Preferences)
46+
// ============================================================================
47+
48+
// Data source and server settings
49+
extern char input_type[40];
50+
extern char mqtt_server[160];
51+
extern char mqtt_port[6];
52+
extern char mqtt_topic[90];
53+
extern char mqtt_user[40];
54+
extern char mqtt_passwd[40];
55+
56+
// JSON path settings
57+
extern char power_path[60];
58+
extern char pwr_export_path[60];
59+
extern char power_l1_path[60];
60+
extern char power_l2_path[60];
61+
extern char power_l3_path[60];
62+
extern char energy_in_path[60];
63+
extern char energy_out_path[60];
64+
65+
// Shelly device settings
66+
extern char shelly_gen[2];
67+
extern char shelly_fw_id[32];
68+
extern char shelly_mac[13];
69+
extern char shelly_name[26];
70+
extern char shelly_port[6];
71+
72+
// Query and protocol settings
73+
extern char query_period[10];
74+
extern char modbus_dev[10];
75+
extern char force_pwr_decimals[6];
76+
extern bool forcePwrDecimals;
77+
extern char sma_id[17];
78+
79+
// LED settings
80+
extern char led_gpio[3];
81+
extern char led_gpio_i[6];
82+
extern unsigned long ledOffTime;
83+
extern uint8_t led;
84+
extern bool led_i;
85+
extern const uint8_t ledblinkduration;
86+
87+
// SMA Multicast IP and Port
88+
extern unsigned int multicastPort; // local port to listen on
89+
extern IPAddress multicastIP;
90+
91+
// MODBUS settings
92+
extern IPAddress modbus_ip;
93+
extern ModbusIP modbus1;
94+
extern int16_t modbus_result[256];
95+
96+
// Default electrical values
97+
extern const uint8_t defaultVoltage;
98+
extern const uint8_t defaultFrequency;
99+
extern const uint8_t defaultPowerFactor;
100+
101+
// RPC and query settings
102+
extern unsigned long period;
103+
extern int rpcId;
104+
extern char rpcUser[20];
105+
106+
// flags for saving/resetting WifiManager data
107+
extern bool shouldSaveConfig;
108+
extern bool shouldResetConfig;
109+
110+
// flags for data sources
111+
extern bool dataMQTT;
112+
extern bool dataSMA;
113+
extern bool dataSHRDZM;
114+
extern bool dataHTTP;
115+
extern bool dataSUNSPEC;
116+
117+
extern Preferences preferences;
118+
119+
120+
// ============================================================================
121+
// NETWORK OBJECTS
122+
// ============================================================================
123+
124+
extern WiFiClient wifi_client;
125+
extern PubSubClient mqtt_client;
126+
extern AsyncWebServer server;
127+
extern AsyncWebSocket webSocket;
128+
extern WiFiUDP Udp;
129+
extern HTTPClient http;
130+
extern WiFiUDP UdpRPC;
131+
132+
// Platform-specific UDP print macro
133+
#ifdef ESP32
134+
#define UDPPRINT print
135+
#else
136+
#define UDPPRINT write
137+
#endif
138+
139+
// ============================================================================
140+
// MDNS RESPONDER HANDLES (ESP8266 only)
141+
// ============================================================================
142+
143+
#ifndef ESP32
144+
extern MDNSResponder::hMDNSService hMDNSService; // handle of the http service in the MDNS responder
145+
extern MDNSResponder::hMDNSService hMDNSService2; // handle of the shelly service in the MDNS responder
146+
#endif
147+
148+
// ============================================================================
149+
// FUNCTION DECLARATIONS
150+
// ============================================================================
151+
152+
void blinkled(int duration);
153+
void handleblinkled();
154+
void saveConfigCallback();
155+
void WifiManagerSetup();
156+
void setupMdns();
157+
158+
#endif // CONFIGURATION_H

src/data/DataProcessing.cpp

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#include "DataStructures.h"
2+
#include "DataProcessing.h"
3+
#include "../config/Configuration.h"
4+
5+
// Global data structures
6+
PowerData PhasePower[3];
7+
EnergyData PhaseEnergy[3];
8+
String serJsonResponse;
9+
10+
double round2(double value) {
11+
int ivalue = (int)(value * 100.0 + (value > 0.0 ? 0.5 : -0.5));
12+
13+
// fix Marstek bug: make sure to have decimal numbers
14+
if(forcePwrDecimals && (ivalue % 100 == 0)) ivalue++;
15+
16+
return ivalue / 100.0;
17+
}
18+
19+
bool isValidIPAddress(const char* ipString) {
20+
IPAddress ip;
21+
return ip.fromString(ipString);
22+
}
23+
24+
JsonVariant resolveJsonPath(JsonVariant variant, const char *path) {
25+
for (size_t n = 0; path[n]; n++) {
26+
// Not a full array support, but works for Shelly 3EM emeters array!
27+
if (path[n] == '[') {
28+
variant = variant[JsonString(path, n)][atoi(&path[n+1])];
29+
path += n + 4;
30+
n = 0;
31+
}
32+
if (path[n] == '.') {
33+
variant = variant[JsonString(path, n)];
34+
path += n + 1;
35+
n = 0;
36+
}
37+
}
38+
return variant[path];
39+
}
40+
41+
void setPowerData(double totalPower) {
42+
for (int i = 0; i <= 2; i++) {
43+
PhasePower[i].power = round2(totalPower * 0.3333);
44+
PhasePower[i].voltage = round2(defaultVoltage);
45+
PhasePower[i].current = round2(PhasePower[i].power / PhasePower[i].voltage);
46+
PhasePower[i].apparentPower = round2(PhasePower[i].power);
47+
PhasePower[i].powerFactor = round2(defaultPowerFactor);
48+
PhasePower[i].frequency = defaultFrequency;
49+
}
50+
DEBUG_SERIAL.print("Current total power: ");
51+
DEBUG_SERIAL.println(totalPower);
52+
}
53+
54+
void setPowerData(double phase1Power, double phase2Power, double phase3Power) {
55+
PhasePower[0].power = round2(phase1Power);
56+
PhasePower[1].power = round2(phase2Power);
57+
PhasePower[2].power = round2(phase3Power);
58+
for (int i = 0; i <= 2; i++) {
59+
PhasePower[i].voltage = round2(defaultVoltage);
60+
PhasePower[i].current = round2(PhasePower[i].power / PhasePower[i].voltage);
61+
PhasePower[i].apparentPower = round2(PhasePower[i].power);
62+
PhasePower[i].powerFactor = round2(defaultPowerFactor);
63+
PhasePower[i].frequency = defaultFrequency;
64+
}
65+
DEBUG_SERIAL.print("Current power L1: ");
66+
DEBUG_SERIAL.print(phase1Power);
67+
DEBUG_SERIAL.print(" - L2: ");
68+
DEBUG_SERIAL.print(phase2Power);
69+
DEBUG_SERIAL.print(" - L3: ");
70+
DEBUG_SERIAL.println(phase3Power);
71+
}
72+
73+
void setEnergyData(double totalEnergyGridSupply, double totalEnergyGridFeedIn) {
74+
for (int i = 0; i <= 2; i++) {
75+
PhaseEnergy[i].consumption = round2(totalEnergyGridSupply * 0.3333);
76+
PhaseEnergy[i].gridfeedin = round2(totalEnergyGridFeedIn * 0.3333);
77+
}
78+
DEBUG_SERIAL.print("Total consumption: ");
79+
DEBUG_SERIAL.print(totalEnergyGridSupply);
80+
DEBUG_SERIAL.print(" - Total Grid Feed-In: ");
81+
DEBUG_SERIAL.println(totalEnergyGridFeedIn);
82+
}
83+
84+
void setJsonPathPower(JsonDocument json) {
85+
// If the incoming JSON already uses Shelly 3EM field names, parse directly
86+
if (json["a_current"].is<JsonVariant>() || json["a_act_power"].is<JsonVariant>()) {
87+
DEBUG_SERIAL.println("Parsing direct Shelly 3EM payload");
88+
PhasePower[0].current = round2((double)json["a_current"].as<double>());
89+
PhasePower[0].voltage = round2((double)json["a_voltage"].as<double>());
90+
PhasePower[0].power = round2((double)json["a_act_power"].as<double>());
91+
PhasePower[0].apparentPower = round2((double)json["a_aprt_power"].as<double>());
92+
PhasePower[0].powerFactor = round2((double)json["a_pf"].as<double>());
93+
PhasePower[0].frequency = json["a_freq"].as<int>();
94+
95+
PhasePower[1].current = round2((double)json["b_current"].as<double>());
96+
PhasePower[1].voltage = round2((double)json["b_voltage"].as<double>());
97+
PhasePower[1].power = round2((double)json["b_act_power"].as<double>());
98+
PhasePower[1].apparentPower = round2((double)json["b_aprt_power"].as<double>());
99+
PhasePower[1].powerFactor = round2((double)json["b_pf"].as<double>());
100+
PhasePower[1].frequency = json["b_freq"].as<int>();
101+
102+
PhasePower[2].current = round2((double)json["c_current"].as<double>());
103+
PhasePower[2].voltage = round2((double)json["c_voltage"].as<double>());
104+
PhasePower[2].power = round2((double)json["c_act_power"].as<double>());
105+
PhasePower[2].apparentPower = round2((double)json["c_aprt_power"].as<double>());
106+
PhasePower[2].powerFactor = round2((double)json["c_pf"].as<double>());
107+
PhasePower[2].frequency = json["c_freq"].as<int>();
108+
109+
// Optionally use total fields if present
110+
if (json["total_act_power"].is<JsonVariant>()) {
111+
double total = json["total_act_power"].as<double>();
112+
// distribute if individual phases missing or for logging
113+
DEBUG_SERIAL.print("Total power from payload: ");
114+
DEBUG_SERIAL.println(total);
115+
}
116+
return;
117+
}
118+
if (strcmp(power_path, "TRIPHASE") == 0) {
119+
DEBUG_SERIAL.println("resolving triphase");
120+
double power1 = resolveJsonPath(json, power_l1_path);
121+
double power2 = resolveJsonPath(json, power_l2_path);
122+
double power3 = resolveJsonPath(json, power_l3_path);
123+
DEBUG_SERIAL.println(power1);
124+
setPowerData(power1, power2, power3);
125+
} else {
126+
// Check if BOTH paths (Import = power_path, Export = pwr_export_path) are defined
127+
if ((strcmp(power_path, "") != 0) && (strcmp(pwr_export_path, "") != 0)) {
128+
DEBUG_SERIAL.println("Resolving net power (import - export)");
129+
double importPower = resolveJsonPath(json, power_path).as<double>();
130+
double exportPower = resolveJsonPath(json, pwr_export_path).as<double>();
131+
double netPower = importPower - exportPower;
132+
setPowerData(netPower);
133+
}
134+
// (FALLBACK): Only the normal power_path (import path) is defined (old logic)
135+
else if (strcmp(power_path, "") != 0) {
136+
DEBUG_SERIAL.println("Resolving monophase (single path only)");
137+
double power = resolveJsonPath(json, power_path).as<double>();
138+
setPowerData(power);
139+
}
140+
}
141+
if ((strcmp(energy_in_path, "") != 0) && (strcmp(energy_out_path, "") != 0)) {
142+
double energyIn = resolveJsonPath(json, energy_in_path);
143+
double energyOut = resolveJsonPath(json, energy_out_path);
144+
setEnergyData(energyIn, energyOut);
145+
}
146+
}
147+
148+
// Helper: parse a raw Shelly JSON string and forward to setJsonPathPower
149+
void parseShellyString(const char *jsonStr) {
150+
JsonDocument doc;
151+
DeserializationError err = deserializeJson(doc, jsonStr);
152+
if (err) {
153+
DEBUG_SERIAL.print("deserializeJson failed: ");
154+
DEBUG_SERIAL.println(err.c_str());
155+
return;
156+
}
157+
setJsonPathPower(doc);
158+
}
159+
160+
void parseShellyString(const String &s) {
161+
parseShellyString(s.c_str());
162+
}

src/data/DataProcessing.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#ifndef DATA_PROCESSING_H
2+
#define DATA_PROCESSING_H
3+
4+
#include <Arduino.h>
5+
#include <ArduinoJson.h>
6+
7+
// Utility functions
8+
double round2(double value);
9+
bool isValidIPAddress(const char *ipString);
10+
JsonVariant resolveJsonPath(JsonVariant variant, const char *path);
11+
12+
// Power setters - distribute to all three phases
13+
void setPowerData(double totalPower);
14+
void setPowerData(double phase1Power, double phase2Power, double phase3Power);
15+
16+
// Energy setter - distribute to all three phases
17+
void setEnergyData(double totalEnergyGridSupply, double totalEnergyGridFeedIn);
18+
19+
// JSON path processing
20+
void setJsonPathPower(JsonDocument json);
21+
void parseShellyString(const char *jsonStr);
22+
void parseShellyString(const String &s);
23+
24+
#endif // DATA_PROCESSING_H

src/data/DataStructures.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef DATA_STRUCTURES_H
2+
#define DATA_STRUCTURES_H
3+
4+
#include <Arduino.h>
5+
6+
// Core data structures for power and energy measurements
7+
struct PowerData {
8+
double current;
9+
double voltage;
10+
double power;
11+
double apparentPower;
12+
double powerFactor;
13+
double frequency;
14+
};
15+
16+
struct EnergyData {
17+
double gridfeedin;
18+
double consumption;
19+
};
20+
21+
// Global power data for three phases
22+
extern PowerData PhasePower[3];
23+
24+
// Global energy data for three phases
25+
extern EnergyData PhaseEnergy[3];
26+
27+
// Global JSON response buffer
28+
extern String serJsonResponse;
29+
30+
#endif // DATA_STRUCTURES_H

0 commit comments

Comments
 (0)