@alteriom/painlessmesh 1.10.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/BRIDGE_TO_INTERNET.md +167 -29
  2. package/CHANGELOG.md +483 -0
  3. package/CONTRIBUTING.md +56 -53
  4. package/README.md +100 -95
  5. package/RELEASE_GUIDE.md +81 -780
  6. package/examples/alteriom/README.md +8 -10
  7. package/examples/alteriom/alteriom.ino +2 -2
  8. package/examples/alteriom/alteriom_sensor_package.hpp +17 -11
  9. package/examples/alteriom/mppt_example/alteriom_custom_package_template.hpp +320 -0
  10. package/examples/alteriom/mppt_example/alteriom_sensor_package.hpp +1389 -0
  11. package/examples/alteriom/mppt_example/{alteriom_mppt_example.ino → mppt_example.ino} +4 -0
  12. package/examples/basic/test/simulator/README.md +3 -3
  13. package/examples/bridge_failover/README.md +51 -14
  14. package/examples/commandControl/commandControl.ino +86 -0
  15. package/examples/commandControl/platformio.ini +26 -0
  16. package/examples/mqttBridge/mqttBridge.ino +4 -0
  17. package/examples/mqttBridge/platformio.ini +1 -1
  18. package/examples/otaSender/otaSender.ino +5 -1
  19. package/examples/priority/README.md +1 -1
  20. package/examples/priority/{priority_basic_example.ino → priority_basic_example/priority_basic_example.ino} +4 -4
  21. package/examples/priority/{priority_with_queue.ino → priority_with_queue/priority_with_queue.ino} +20 -2
  22. package/examples/reliableSensorLogging/platformio.ini +26 -0
  23. package/examples/reliableSensorLogging/reliableSensorLogging.ino +151 -0
  24. package/examples/sendToInternet/README.md +12 -5
  25. package/examples/sendToInternet/{CMakeLists.txt → pc_node/CMakeLists.txt} +7 -7
  26. package/examples/sendToInternet/{PC_NODE_README.md → pc_node/PC_NODE_README.md} +15 -15
  27. package/examples/sendToInternet/{build.sh → pc_node/build.sh} +5 -5
  28. package/examples/sendToInternet/{pc_mesh_node.cpp → pc_node/pc_mesh_node.cpp} +12 -1
  29. package/examples/sharedGateway/README.md +1 -2
  30. package/keywords.txt +50 -1
  31. package/library.json +8 -6
  32. package/library.properties +2 -2
  33. package/package.json +3 -3
  34. package/src/AlteriomPainlessMesh.h +3 -3
  35. package/src/arduino/wifi.hpp +556 -126
  36. package/src/painlessMesh.h +2 -2
  37. package/src/painlessMeshSTA.cpp +607 -87
  38. package/src/painlessMeshSTA.h +135 -3
  39. package/src/painlessmesh/ack.hpp +283 -0
  40. package/src/painlessmesh/buffer.hpp +70 -8
  41. package/src/painlessmesh/callback.hpp +38 -5
  42. package/src/painlessmesh/configuration.hpp +69 -1
  43. package/src/painlessmesh/connection.hpp +12 -5
  44. package/src/painlessmesh/gateway.hpp +270 -5
  45. package/src/painlessmesh/layout.hpp +70 -2
  46. package/src/painlessmesh/logger.hpp +15 -0
  47. package/src/painlessmesh/mesh.hpp +552 -48
  48. package/src/painlessmesh/ntp.hpp +2 -4
  49. package/src/painlessmesh/plugin.hpp +30 -6
  50. package/src/painlessmesh/protocol.hpp +55 -2
  51. package/src/painlessmesh/router.hpp +192 -77
  52. package/src/painlessmesh/tcp.hpp +10 -0
  53. package/src/painlessmesh/message_tracker.hpp +0 -311
  54. /package/examples/sendToInternet/{mock_server_test.ino → mock_server_test/mock_server_test.ino} +0 -0
@@ -44,7 +44,7 @@ Extended status package with comprehensive health metrics (18 fields).
44
44
  - `avgLatency`, `packetLossRate`, `throughput` - Performance metrics
45
45
  - `alertFlags`, `lastError` - Alert system
46
46
 
47
- ### MetricsPackage (Type 204) - NEW in v1.7.7
47
+ ### MetricsPackage (Type 204)
48
48
  Comprehensive performance metrics for detailed monitoring and dashboards.
49
49
 
50
50
  **Key Fields:**
@@ -61,7 +61,7 @@ Comprehensive performance metrics for detailed monitoring and dashboards.
61
61
  - Network throughput analysis
62
62
  - Latency monitoring
63
63
 
64
- ### HealthCheckPackage (Type 605) - NEW in v1.7.7
64
+ ### HealthCheckPackage (Type 605)
65
65
  Proactive health monitoring with problem detection and recommendations.
66
66
 
67
67
  **Key Fields:**
@@ -93,7 +93,7 @@ Proactive health monitoring with problem detection and recommendations.
93
93
  - Automated alerting
94
94
  - Memory leak detection
95
95
 
96
- ### MeshNodeListPackage (Type 600) - NEW in v1.7.7
96
+ ### MeshNodeListPackage (Type 600)
97
97
  List of all nodes in the mesh network with their status.
98
98
 
99
99
  **Fields:**
@@ -110,7 +110,7 @@ List of all nodes in the mesh network with their status.
110
110
  - Network health visualization
111
111
  - Device inventory management
112
112
 
113
- ### MeshTopologyPackage (Type 601) - NEW in v1.7.7
113
+ ### MeshTopologyPackage (Type 601)
114
114
  Mesh network topology with all connections between nodes.
115
115
 
116
116
  **Fields:**
@@ -128,7 +128,7 @@ Mesh network topology with all connections between nodes.
128
128
  - Network optimization
129
129
  - Path analysis
130
130
 
131
- ### MeshAlertPackage (Type 602) - NEW in v1.7.7
131
+ ### MeshAlertPackage (Type 602)
132
132
  Mesh network alerts for critical events and warnings.
133
133
 
134
134
  **Fields:**
@@ -147,7 +147,7 @@ Mesh network alerts for critical events and warnings.
147
147
  - Problem notification
148
148
  - Event logging
149
149
 
150
- ### MeshBridgePackage (Type 603) - NEW in v1.7.7
150
+ ### MeshBridgePackage (Type 603)
151
151
  Bridge package for encapsulating native mesh protocol messages.
152
152
 
153
153
  **Fields:**
@@ -184,7 +184,7 @@ OTA enhancement example demonstrating:
184
184
  - Alert system implementation
185
185
  - Usage patterns for advanced features
186
186
 
187
- ### `metrics_health_node.ino` (NEW in v1.7.7)
187
+ ### `metrics_health_node.ino`
188
188
  Comprehensive monitoring node example demonstrating:
189
189
  - MetricsPackage (Type 204) collection and broadcasting
190
190
  - HealthCheckPackage (Type 605) proactive monitoring (MESH_METRICS)
@@ -278,9 +278,7 @@ This validates:
278
278
 
279
279
  For developers adding new configuration fields to Alteriom packages:
280
280
 
281
- - **[API Design Guidelines](../../docs/API_DESIGN_GUIDELINES.md)** - Comprehensive guide on when to use nested vs flat JSON structures
282
281
  - **[Time Field Naming Convention](alteriom_sensor_package.hpp#L10-L55)** - How to handle time-based fields (ms/s variants)
283
- - **[Boolean Naming Convention](../../docs/BOOLEAN_NAMING_CONVENTION.md)** - Consistent patterns for boolean fields (*Set, *Enabled, is*)
284
282
  - **[JSON Structure Guidelines](alteriom_sensor_package.hpp#L57-L121)** - Quick reference for nesting patterns
285
283
 
286
- These guidelines ensure consistency and maintainability across all Alteriom packages.
284
+ These guidelines ensure consistency and maintainability across all Alteriom packages.
@@ -90,7 +90,7 @@ void handleStatusRequest() {
90
90
  status.uptime = millis() / 1000;
91
91
  status.freeMemory = ESP.getFreeHeap() / 1024; // KB
92
92
  status.wifiStrength = 75; // Placeholder
93
- status.firmwareVersion = "1.0.0-alteriom";
93
+ status.firmwareVersion = "2.0.0";
94
94
 
95
95
  // Serialize and send (using ArduinoJson v7 API)
96
96
  JsonDocument doc;
@@ -184,4 +184,4 @@ void changedConnectionCallback() { Serial.printf("Changed connections\n"); }
184
184
 
185
185
  void nodeTimeAdjustedCallback(int32_t offset) {
186
186
  Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(), offset);
187
- }
187
+ }
@@ -1233,7 +1233,7 @@ class BridgeStatusPackage : public painlessmesh::plugin::BroadcastPackage {
1233
1233
  *
1234
1234
  * Election process:
1235
1235
  * 1. Bridge failure detected (no heartbeat for 60+ seconds)
1236
- * 2. Nodes broadcast BridgeElectionPackage with their router RSSI
1236
+ * 2. Nodes broadcast BridgeElectionPackage with router RSSI and channel
1237
1237
  * 3. 5-second collection window for all candidates
1238
1238
  * 4. Each node evaluates all candidates locally (deterministic)
1239
1239
  * 5. Winner promotes itself to bridge, others remain as regular nodes
@@ -1242,11 +1242,12 @@ class BridgeStatusPackage : public painlessmesh::plugin::BroadcastPackage {
1242
1242
  */
1243
1243
  class BridgeElectionPackage : public painlessmesh::plugin::BroadcastPackage {
1244
1244
  public:
1245
- int8_t routerRSSI = 0; // Router WiFi signal strength in dBm (-127 to 0)
1246
- uint32_t uptime = 0; // Node uptime in milliseconds
1247
- uint32_t freeMemory = 0; // Free memory in bytes
1248
- uint32_t timestamp = 0; // Election timestamp
1249
- TSTRING routerSSID = ""; // Router SSID (for verification)
1245
+ int8_t routerRSSI = 0; // Router WiFi signal strength in dBm (-127 to 0)
1246
+ uint8_t routerChannel = 0; // Channel the candidate would use as bridge
1247
+ uint32_t uptime = 0; // Node uptime in milliseconds
1248
+ uint32_t freeMemory = 0; // Free memory in bytes
1249
+ uint32_t timestamp = 0; // Election timestamp
1250
+ TSTRING routerSSID = ""; // Router SSID (for verification)
1250
1251
 
1251
1252
  // MQTT Schema v0.7.3+ message_type
1252
1253
  uint16_t messageType = 611; // BRIDGE_ELECTION
@@ -1255,6 +1256,7 @@ class BridgeElectionPackage : public painlessmesh::plugin::BroadcastPackage {
1255
1256
 
1256
1257
  BridgeElectionPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
1257
1258
  routerRSSI = jsonObj["routerRSSI"] | 0;
1259
+ routerChannel = jsonObj["routerChannel"] | 0;
1258
1260
  uptime = jsonObj["uptime"] | 0;
1259
1261
  freeMemory = jsonObj["freeMemory"] | 0;
1260
1262
  timestamp = jsonObj["timestamp"] | 0;
@@ -1265,6 +1267,7 @@ class BridgeElectionPackage : public painlessmesh::plugin::BroadcastPackage {
1265
1267
  JsonObject addTo(JsonObject&& jsonObj) const {
1266
1268
  jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
1267
1269
  jsonObj["routerRSSI"] = routerRSSI;
1270
+ jsonObj["routerChannel"] = routerChannel;
1268
1271
  jsonObj["uptime"] = uptime;
1269
1272
  jsonObj["freeMemory"] = freeMemory;
1270
1273
  jsonObj["timestamp"] = timestamp;
@@ -1275,7 +1278,7 @@ class BridgeElectionPackage : public painlessmesh::plugin::BroadcastPackage {
1275
1278
 
1276
1279
  #if ARDUINOJSON_VERSION_MAJOR < 7
1277
1280
  size_t jsonObjectSize() const {
1278
- return JSON_OBJECT_SIZE(noJsonFields + 6) + routerSSID.length();
1281
+ return JSON_OBJECT_SIZE(noJsonFields + 7) + routerSSID.length();
1279
1282
  }
1280
1283
  #endif
1281
1284
  };
@@ -1294,8 +1297,9 @@ class BridgeTakeoverPackage : public painlessmesh::plugin::BroadcastPackage {
1294
1297
  uint32_t previousBridge = 0; // Previous bridge node ID (0 if none)
1295
1298
  TSTRING reason =
1296
1299
  ""; // Reason for takeover (e.g., "Election winner - best router signal")
1297
- int8_t routerRSSI = 0; // New bridge's router signal strength
1298
- uint32_t timestamp = 0; // Takeover timestamp
1300
+ int8_t routerRSSI = 0; // New bridge's router signal strength
1301
+ uint8_t routerChannel = 0; // Channel peers follow during takeover
1302
+ uint32_t timestamp = 0; // Takeover timestamp
1299
1303
 
1300
1304
  // MQTT Schema v0.7.3+ message_type
1301
1305
  uint16_t messageType = 612; // BRIDGE_TAKEOVER
@@ -1306,6 +1310,7 @@ class BridgeTakeoverPackage : public painlessmesh::plugin::BroadcastPackage {
1306
1310
  previousBridge = jsonObj["previousBridge"] | 0;
1307
1311
  reason = jsonObj["reason"].as<TSTRING>();
1308
1312
  routerRSSI = jsonObj["routerRSSI"] | 0;
1313
+ routerChannel = jsonObj["routerChannel"] | 0;
1309
1314
  timestamp = jsonObj["timestamp"] | 0;
1310
1315
  messageType = jsonObj["message_type"] | 612;
1311
1316
  }
@@ -1315,6 +1320,7 @@ class BridgeTakeoverPackage : public painlessmesh::plugin::BroadcastPackage {
1315
1320
  jsonObj["previousBridge"] = previousBridge;
1316
1321
  jsonObj["reason"] = reason;
1317
1322
  jsonObj["routerRSSI"] = routerRSSI;
1323
+ jsonObj["routerChannel"] = routerChannel;
1318
1324
  jsonObj["timestamp"] = timestamp;
1319
1325
  jsonObj["message_type"] = messageType;
1320
1326
  return jsonObj;
@@ -1322,7 +1328,7 @@ class BridgeTakeoverPackage : public painlessmesh::plugin::BroadcastPackage {
1322
1328
 
1323
1329
  #if ARDUINOJSON_VERSION_MAJOR < 7
1324
1330
  size_t jsonObjectSize() const {
1325
- return JSON_OBJECT_SIZE(noJsonFields + 5) + reason.length();
1331
+ return JSON_OBJECT_SIZE(noJsonFields + 6) + reason.length();
1326
1332
  }
1327
1333
  #endif
1328
1334
  };
@@ -1380,4 +1386,4 @@ class NTPTimeSyncPackage : public painlessmesh::plugin::BroadcastPackage {
1380
1386
 
1381
1387
  } // namespace alteriom
1382
1388
 
1383
- #endif // ALTERIOM_SENSOR_PACKAGE_HPP
1389
+ #endif // ALTERIOM_SENSOR_PACKAGE_HPP
@@ -0,0 +1,320 @@
1
+ #ifndef ALTERIOM_CUSTOM_PACKAGE_TEMPLATE_HPP
2
+ #define ALTERIOM_CUSTOM_PACKAGE_TEMPLATE_HPP
3
+
4
+ #include "painlessmesh/plugin.hpp"
5
+
6
+ /**
7
+ * @file alteriom_custom_package_template.hpp
8
+ * @brief Template and example for creating custom Alteriom packages
9
+ *
10
+ * HOW TO CREATE A CUSTOM PACKAGE
11
+ * ===============================
12
+ *
13
+ * This file serves two purposes:
14
+ * 1. A step-by-step guide to creating custom painlessMesh packages
15
+ * 2. A concrete example: MpptPackage for MPPT solar charge controllers
16
+ *
17
+ * QUICK START
18
+ * -----------
19
+ * To create your own custom package:
20
+ * 1. Pick an unused Type ID from the table below (use 206+ range)
21
+ * 2. Choose a base class: BroadcastPackage (all nodes) or SinglePackage (one
22
+ * node)
23
+ * 3. Add your data fields with appropriate types
24
+ * 4. Implement the JSON constructor and addTo() method
25
+ * 5. Add a test in test/catch/catch_custom_package.cpp
26
+ *
27
+ * RESERVED TYPE IDS
28
+ * -----------------
29
+ * The following IDs are already used; do NOT reuse them:
30
+ *
31
+ * 200 : SensorPackage (environmental sensors: temp, humidity, pressure)
32
+ * 202 : StatusPackage (device health and configuration)
33
+ * 204 : MetricsPackage (network performance metrics)
34
+ * 205 : MpptPackage (MPPT solar charge controller data) <-- this file
35
+ * 400 : CommandPackage (device control commands)
36
+ * 600 : MeshNodeListPackage
37
+ * 601 : MeshTopologyPackage
38
+ * 602 : MeshAlertPackage
39
+ * 603 : MeshBridgePackage
40
+ * 604 : EnhancedStatusPackage
41
+ * 605 : HealthCheckPackage
42
+ * 610 : BridgeStatusPackage
43
+ * 611 : BridgeElectionPackage
44
+ * 612 : BridgeTakeoverPackage
45
+ * 614 : NTPTimeSyncPackage
46
+ *
47
+ * Available ranges: 206-399 (add your package here and update this table).
48
+ *
49
+ *
50
+ * CHOOSING BASE CLASS
51
+ * -------------------
52
+ *
53
+ * BroadcastPackage – sent to every node in the mesh.
54
+ * Use for: sensor readings, status updates, telemetry data.
55
+ * Base fields: from, routing (BROADCAST), type (noJsonFields = 3)
56
+ *
57
+ * SinglePackage – sent to one specific destination node.
58
+ * Use for: commands, acknowledgements, targeted responses.
59
+ * Base fields: from, dest, routing (SINGLE), type (noJsonFields = 4)
60
+ *
61
+ *
62
+ * FIELD TYPE GUIDELINES
63
+ * ---------------------
64
+ *
65
+ * Choose types appropriate for your platform:
66
+ *
67
+ * uint8_t – flags, states, small counts (0-255)
68
+ * uint16_t – larger counts, port numbers, voltages in mV (0-65535)
69
+ * uint32_t – device IDs, Unix timestamps, large counters
70
+ * int8_t – signed small values, e.g. temperature in °C (-128 to +127)
71
+ * float – measured values requiring decimals (4 bytes; fine on both
72
+ * ESP8266 and ESP32)
73
+ * double – high-precision measurements (8 bytes; prefer float on ESP8266)
74
+ * TSTRING – text strings (always use TSTRING, NOT Arduino String)
75
+ * bool – boolean flags; see BOOLEAN NAMING CONVENTION below
76
+ *
77
+ * BOOLEAN NAMING CONVENTION
78
+ * -------------------------
79
+ * *Set suffix – configuration data has been provided
80
+ * e.g., serverAddressSet = true
81
+ * *Enabled suffix – feature is currently active/on
82
+ * e.g., loggingEnabled = true
83
+ * is* prefix – current runtime state
84
+ * e.g., isCharging = true
85
+ *
86
+ *
87
+ * JSON FIELD NAMING
88
+ * -----------------
89
+ *
90
+ * Use SHORT keys to minimise over-the-air message sizes:
91
+ *
92
+ * batteryVoltage -> "bv"
93
+ * solarCurrent -> "sc"
94
+ * chargeState -> "cs"
95
+ * deviceId -> "did"
96
+ * timestamp -> "ts"
97
+ *
98
+ * Always document the mapping in a comment near the field declaration.
99
+ *
100
+ *
101
+ * TIME FIELDS
102
+ * -----------
103
+ *
104
+ * For interval / duration fields, follow the Alteriom time convention:
105
+ * - Store internally in milliseconds (uint32_t)
106
+ * - Serialise both a _ms and a _s variant in JSON
107
+ * - Deserialise from the _ms variant only
108
+ *
109
+ * Timestamp fields (Unix epoch seconds) are an exception: single field, no
110
+ * dual-unit serialisation needed.
111
+ *
112
+ *
113
+ * ARDUINOJSON COMPATIBILITY
114
+ * -------------------------
115
+ *
116
+ * Always wrap the jsonObjectSize() method in an
117
+ * #if ARDUINOJSON_VERSION_MAJOR < 7 guard. ArduinoJson v7 computes document
118
+ * sizes automatically; v6 requires an explicit capacity hint.
119
+ *
120
+ * The formula is:
121
+ * JSON_OBJECT_SIZE(noJsonFields + <number of your own fields>)
122
+ * + <total length of all TSTRING fields>
123
+ *
124
+ *
125
+ * MINIMAL PACKAGE TEMPLATE
126
+ * ========================
127
+ *
128
+ * Copy this skeleton and replace the placeholder names / IDs:
129
+ *
130
+ * @code
131
+ * namespace alteriom {
132
+ *
133
+ * class MyCustomPackage : public painlessmesh::plugin::BroadcastPackage {
134
+ * public:
135
+ * // --- Your data fields ---
136
+ * uint32_t myId = 0;
137
+ * float myValue = 0.0f;
138
+ * TSTRING myText = "";
139
+ *
140
+ * // MQTT message_type (set to your chosen type ID)
141
+ * uint16_t messageType = 206;
142
+ *
143
+ * MyCustomPackage() : BroadcastPackage(206) {}
144
+ *
145
+ * MyCustomPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
146
+ * myId = jsonObj["id"];
147
+ * myValue = jsonObj["val"];
148
+ * myText = jsonObj["txt"].as<TSTRING>();
149
+ * messageType = jsonObj["message_type"] | 206;
150
+ * }
151
+ *
152
+ * JsonObject addTo(JsonObject&& jsonObj) const {
153
+ * jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
154
+ * jsonObj["id"] = myId;
155
+ * jsonObj["val"] = myValue;
156
+ * jsonObj["txt"] = myText;
157
+ * jsonObj["message_type"] = messageType;
158
+ * return jsonObj;
159
+ * }
160
+ *
161
+ * #if ARDUINOJSON_VERSION_MAJOR < 7
162
+ * size_t jsonObjectSize() const {
163
+ * // noJsonFields covers base-class fields; 3 = number of YOUR fields
164
+ * return JSON_OBJECT_SIZE(noJsonFields + 3) + myText.length();
165
+ * }
166
+ * #endif
167
+ * };
168
+ *
169
+ * } // namespace alteriom
170
+ * @endcode
171
+ *
172
+ *
173
+ * CONCRETE EXAMPLE: MpptPackage
174
+ * ==============================
175
+ *
176
+ * The MpptPackage (Type 205) transmits real-time telemetry from an MPPT solar
177
+ * charge controller (e.g. Renegy, Epever, Victron). It is a BroadcastPackage
178
+ * so every node in the mesh receives the data automatically.
179
+ *
180
+ * Fields at a glance:
181
+ *
182
+ * solarVoltage (float, V) – PV panel open-circuit / input voltage
183
+ * solarCurrent (float, A) – PV panel current
184
+ * solarPower (uint16_t, W) – PV panel instantaneous power
185
+ * batteryVoltage (float, V) – Battery terminal voltage
186
+ * batterySOC (uint8_t, %) – State of charge 0–100
187
+ * loadVoltage (float, V) – Load output voltage
188
+ * loadCurrent (float, A) – Load output current
189
+ * chargeState (uint8_t) – Controller state (see ChargeState enum)
190
+ * controllerTemp (int8_t, °C) – Internal controller temperature
191
+ * deviceId (uint32_t) – Unique hardware identifier
192
+ * timestamp (uint32_t) – Unix timestamp of the reading
193
+ */
194
+
195
+ namespace alteriom {
196
+
197
+ /**
198
+ * @brief Charge state values for MpptPackage::chargeState
199
+ */
200
+ enum ChargeState : uint8_t {
201
+ CHARGE_OFF = 0, ///< Charging disabled
202
+ CHARGE_NORMAL = 1, ///< Normal PWM charging
203
+ CHARGE_MPPT = 2, ///< Maximum Power Point Tracking active
204
+ CHARGE_EQUALIZE = 3, ///< Equalisation charge (battery maintenance)
205
+ CHARGE_BOOST = 4, ///< Boost / bulk charge stage
206
+ CHARGE_FLOAT = 5, ///< Float / maintenance stage
207
+ CHARGE_LIMITED = 6 ///< Current-limited charging
208
+ };
209
+
210
+ /**
211
+ * @brief Real-time telemetry from an MPPT solar charge controller
212
+ *
213
+ * Broadcasts voltage, current, power and status from an MPPT charge controller
214
+ * to all nodes in the mesh (e.g. for logging, display, or load management).
215
+ *
216
+ * Adapting for your controller
217
+ * ----------------------------
218
+ * Most MPPT controllers expose data over RS-232/RS-485 or I²C. Read the raw
219
+ * values from your hardware, assign them to the struct fields, then call
220
+ * sendBroadcast() as shown in alteriom_mppt_example.ino.
221
+ *
222
+ * Type ID: 205
223
+ */
224
+ class MpptPackage : public painlessmesh::plugin::BroadcastPackage {
225
+ public:
226
+ // JSON key : "sv" – PV panel voltage in Volts
227
+ float solarVoltage = 0.0f;
228
+ // JSON key : "sc" – PV panel current in Amperes
229
+ float solarCurrent = 0.0f;
230
+ // JSON key : "sp" – PV panel power in Watts
231
+ uint16_t solarPower = 0;
232
+ // JSON key : "bv" – Battery terminal voltage in Volts
233
+ float batteryVoltage = 0.0f;
234
+ // JSON key : "bsoc" – Battery state of charge, 0–100 %
235
+ uint8_t batterySOC = 0;
236
+ // JSON key : "lv" – Load output voltage in Volts
237
+ float loadVoltage = 0.0f;
238
+ // JSON key : "lc" – Load output current in Amperes
239
+ float loadCurrent = 0.0f;
240
+ // JSON key : "cs" – Charge controller state (see ChargeState enum)
241
+ uint8_t chargeState = CHARGE_OFF;
242
+ // JSON key : "ct" – Controller internal temperature in °C (signed)
243
+ int8_t controllerTemp = 0;
244
+ // JSON key : "did" – Unique hardware / node identifier
245
+ uint32_t deviceId = 0;
246
+ // JSON key : "ts" – Unix timestamp of measurement (seconds since epoch)
247
+ uint32_t timestamp = 0;
248
+
249
+ // MQTT Schema message_type for fast classification at the bridge
250
+ uint16_t messageType = 205; // MPPT_DATA
251
+
252
+ // -------------------------------------------------------------------------
253
+ // Constructors
254
+ // -------------------------------------------------------------------------
255
+
256
+ MpptPackage() : BroadcastPackage(205) {}
257
+
258
+ /**
259
+ * @brief Deserialise from a JSON object received over the mesh
260
+ *
261
+ * @param jsonObj Parsed JSON object (ArduinoJson JsonObject)
262
+ */
263
+ MpptPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
264
+ solarVoltage = jsonObj["sv"];
265
+ solarCurrent = jsonObj["sc"];
266
+ solarPower = jsonObj["sp"];
267
+ batteryVoltage = jsonObj["bv"];
268
+ batterySOC = jsonObj["bsoc"];
269
+ loadVoltage = jsonObj["lv"];
270
+ loadCurrent = jsonObj["lc"];
271
+ chargeState = jsonObj["cs"];
272
+ controllerTemp = jsonObj["ct"];
273
+ deviceId = jsonObj["did"];
274
+ timestamp = jsonObj["ts"];
275
+ messageType = jsonObj["message_type"] | 205;
276
+ }
277
+
278
+ // -------------------------------------------------------------------------
279
+ // Serialisation
280
+ // -------------------------------------------------------------------------
281
+
282
+ /**
283
+ * @brief Serialise this package into the provided JSON object
284
+ *
285
+ * Call addTo() on a freshly created JsonObject, then serialise with
286
+ * ArduinoJson's serializeJson() before passing the result to
287
+ * mesh.sendBroadcast().
288
+ */
289
+ JsonObject addTo(JsonObject&& jsonObj) const {
290
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
291
+ jsonObj["sv"] = solarVoltage;
292
+ jsonObj["sc"] = solarCurrent;
293
+ jsonObj["sp"] = solarPower;
294
+ jsonObj["bv"] = batteryVoltage;
295
+ jsonObj["bsoc"] = batterySOC;
296
+ jsonObj["lv"] = loadVoltage;
297
+ jsonObj["lc"] = loadCurrent;
298
+ jsonObj["cs"] = chargeState;
299
+ jsonObj["ct"] = controllerTemp;
300
+ jsonObj["did"] = deviceId;
301
+ jsonObj["ts"] = timestamp;
302
+ jsonObj["message_type"] = messageType;
303
+ return jsonObj;
304
+ }
305
+
306
+ #if ARDUINOJSON_VERSION_MAJOR < 7
307
+ /**
308
+ * @brief Required capacity hint for ArduinoJson v6
309
+ *
310
+ * noJsonFields covers the 3 base-class fields (from, routing, type).
311
+ * The +12 accounts for the 12 fields declared in this class.
312
+ * No TSTRING fields, so no extra string length term.
313
+ */
314
+ size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 12); }
315
+ #endif
316
+ };
317
+
318
+ } // namespace alteriom
319
+
320
+ #endif // ALTERIOM_CUSTOM_PACKAGE_TEMPLATE_HPP