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+ }
0 commit comments