@alteriom/painlessmesh 1.9.19 → 1.10.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.
- package/CHANGELOG.md +168 -0
- package/README.md +102 -63
- package/RELEASE_GUIDE.md +147 -8
- package/examples/alteriom/README.md +4 -4
- package/examples/alteriom/alteriom_custom_package_template.hpp +320 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +1 -1
- package/examples/alteriom/mppt_example/alteriom_mppt_example.ino +208 -0
- package/examples/bridge_failover/bridge_failover.ino +17 -0
- package/examples/sendToInternet/CMakeLists.txt +54 -0
- package/examples/sendToInternet/PC_NODE_README.md +517 -0
- package/examples/sendToInternet/README.md +39 -1
- package/examples/sendToInternet/build.sh +153 -0
- package/examples/sendToInternet/mock_server_test.ino +361 -0
- package/examples/sendToInternet/pc_mesh_node.cpp +361 -0
- package/examples/tcpRetryConfig/README.md +110 -0
- package/examples/tcpRetryConfig/platformio.ini +26 -0
- package/examples/tcpRetryConfig/tcpRetryConfig.ino +154 -0
- package/keywords.txt +3 -0
- package/library.json +4 -1
- package/library.properties +1 -1
- package/package.json +3 -3
- package/src/AlteriomPainlessMesh.h +6 -14
- package/src/arduino/wifi.hpp +352 -114
- package/src/connection.cpp +10 -0
- package/src/painlessMesh.h +2 -15
- package/src/painlessTaskOptions.h +9 -0
- package/src/painlessmesh/buffer.hpp +4 -1
- package/src/painlessmesh/configuration.hpp +13 -2
- package/src/painlessmesh/connection.hpp +36 -21
- package/src/painlessmesh/gateway.hpp +0 -1061
- package/src/painlessmesh/mesh.hpp +102 -107
- package/src/painlessmesh/message_queue.hpp +25 -15
- package/src/painlessmesh/metrics.hpp +2 -262
- package/src/painlessmesh/plugin.hpp +27 -5
- package/src/painlessmesh/tcp.hpp +158 -29
- package/src/painlessmesh/validation.hpp +0 -143
- package/docs/README.md +0 -132
- package/docs/alteriom/overview.md +0 -531
- package/docs/api/core-api.md +0 -607
- package/docs/api/shared-gateway.md +0 -1207
- package/docs/architecture/mesh-architecture.md +0 -399
- package/docs/architecture/plugin-system.md +0 -517
- package/docs/getting-started/arduino-manual-install.md +0 -313
- package/docs/getting-started/first-mesh.md +0 -410
- package/docs/getting-started/installation.md +0 -275
- package/docs/getting-started/quickstart.md +0 -158
- package/docs/troubleshooting/common-issues.md +0 -679
- package/docs/troubleshooting/debugging.md +0 -455
- package/docs/troubleshooting/external-device-connection.md +0 -283
- package/docs/troubleshooting/faq.md +0 -574
- package/docs/tutorials/basic-examples.md +0 -718
|
@@ -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
|
|
@@ -520,7 +520,7 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
520
520
|
*
|
|
521
521
|
* This is an extended version of StatusPackage that includes additional
|
|
522
522
|
* mesh statistics, performance metrics, and alerting capabilities.
|
|
523
|
-
* Type ID
|
|
523
|
+
* Type ID 604 is used to distinguish from the basic StatusPackage (202).
|
|
524
524
|
*/
|
|
525
525
|
class EnhancedStatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
526
526
|
public:
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
//************************************************************
|
|
2
|
+
// AlteriomPainlessMesh – MPPT Charge Controller Example
|
|
3
|
+
//
|
|
4
|
+
// This sketch shows how to use a custom package (MpptPackage)
|
|
5
|
+
// to broadcast solar charge-controller telemetry over a
|
|
6
|
+
// painlessMesh network.
|
|
7
|
+
//
|
|
8
|
+
// Hardware assumptions:
|
|
9
|
+
// - An MPPT charge controller connected via Serial / RS-485
|
|
10
|
+
// (e.g. Renegy, Epever, Victron BlueSolar, etc.)
|
|
11
|
+
// - ESP8266 or ESP32 running this firmware
|
|
12
|
+
//
|
|
13
|
+
// How it works:
|
|
14
|
+
// 1. Every 10 seconds the node reads the charge controller
|
|
15
|
+
// and broadcasts an MpptPackage to all mesh nodes.
|
|
16
|
+
// 2. Any node that receives an MpptPackage prints the values
|
|
17
|
+
// to Serial (bridge nodes can forward them to MQTT/HTTP).
|
|
18
|
+
// 3. A CommandPackage handler is included so a bridge node can
|
|
19
|
+
// request an immediate reading (command code 10).
|
|
20
|
+
//
|
|
21
|
+
// See alteriom_custom_package_template.hpp for a step-by-step
|
|
22
|
+
// guide to creating your own custom packages.
|
|
23
|
+
//************************************************************
|
|
24
|
+
|
|
25
|
+
#include "AlteriomPainlessMesh.h"
|
|
26
|
+
#include "alteriom_custom_package_template.hpp"
|
|
27
|
+
#include "alteriom_sensor_package.hpp" // for CommandPackage
|
|
28
|
+
|
|
29
|
+
#define MESH_PREFIX "AlteriomMesh"
|
|
30
|
+
#define MESH_PASSWORD "somethingSneaky"
|
|
31
|
+
#define MESH_PORT 5555
|
|
32
|
+
|
|
33
|
+
// How often to broadcast MPPT data (milliseconds)
|
|
34
|
+
#define MPPT_SEND_INTERVAL 10000
|
|
35
|
+
|
|
36
|
+
Scheduler userScheduler;
|
|
37
|
+
painlessMesh mesh;
|
|
38
|
+
|
|
39
|
+
using namespace alteriom;
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Forward declarations
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
void sendMpptData();
|
|
45
|
+
void handleIncomingPackage(uint32_t from, String& msg);
|
|
46
|
+
void handleCommandPackage(CommandPackage& cmd);
|
|
47
|
+
void newConnectionCallback(uint32_t nodeId);
|
|
48
|
+
void changedConnectionCallback();
|
|
49
|
+
void nodeTimeAdjustedCallback(int32_t offset);
|
|
50
|
+
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Scheduled tasks
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
Task taskSendMppt(MPPT_SEND_INTERVAL, TASK_FOREVER, &sendMpptData);
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Stub: read data from your MPPT controller
|
|
58
|
+
//
|
|
59
|
+
// Replace this function body with real hardware reads, e.g.:
|
|
60
|
+
// - Modbus RTU over RS-485 (using ModbusMaster library)
|
|
61
|
+
// - Vendor protocol over SoftwareSerial / HardwareSerial
|
|
62
|
+
// - I²C registers for controllers that support it
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
MpptPackage readMpptController() {
|
|
65
|
+
MpptPackage data;
|
|
66
|
+
data.from = mesh.getNodeId();
|
|
67
|
+
data.deviceId = mesh.getNodeId();
|
|
68
|
+
data.timestamp = mesh.getNodeTime() / 1000; // convert µs → s
|
|
69
|
+
|
|
70
|
+
// --- Replace these lines with real hardware reads ---
|
|
71
|
+
data.solarVoltage = 18.5f; // V (example: 18.5 V PV panel)
|
|
72
|
+
data.solarCurrent = 5.2f; // A
|
|
73
|
+
data.solarPower = 96; // W (= solarVoltage * solarCurrent)
|
|
74
|
+
data.batteryVoltage = 12.6f; // V (12 V lead-acid, ~80 % SOC)
|
|
75
|
+
data.batterySOC = 80; // %
|
|
76
|
+
data.loadVoltage = 12.4f; // V
|
|
77
|
+
data.loadCurrent = 2.1f; // A
|
|
78
|
+
data.chargeState = CHARGE_MPPT;
|
|
79
|
+
data.controllerTemp = 32; // °C
|
|
80
|
+
// ----------------------------------------------------
|
|
81
|
+
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// Setup
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
void setup() {
|
|
89
|
+
Serial.begin(115200);
|
|
90
|
+
|
|
91
|
+
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
|
|
92
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
93
|
+
mesh.onReceive(&handleIncomingPackage);
|
|
94
|
+
mesh.onNewConnection(&newConnectionCallback);
|
|
95
|
+
mesh.onChangedConnections(&changedConnectionCallback);
|
|
96
|
+
mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
|
|
97
|
+
|
|
98
|
+
userScheduler.addTask(taskSendMppt);
|
|
99
|
+
taskSendMppt.enable();
|
|
100
|
+
|
|
101
|
+
Serial.println("MPPT node initialised");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Loop
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
void loop() {
|
|
108
|
+
mesh.update();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Broadcast MPPT telemetry
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
void sendMpptData() {
|
|
115
|
+
MpptPackage data = readMpptController();
|
|
116
|
+
|
|
117
|
+
// Serialise with ArduinoJson v7 API
|
|
118
|
+
JsonDocument doc;
|
|
119
|
+
JsonObject obj = doc.to<JsonObject>();
|
|
120
|
+
data.addTo(std::move(obj));
|
|
121
|
+
|
|
122
|
+
String msg;
|
|
123
|
+
serializeJson(doc, msg);
|
|
124
|
+
|
|
125
|
+
if (mesh.sendBroadcast(msg)) {
|
|
126
|
+
Serial.printf(
|
|
127
|
+
"MPPT sent: PV=%.1fV/%.1fA/%dW Bat=%.1fV/%d%% "
|
|
128
|
+
"Load=%.1fV/%.1fA State=%d Temp=%d°C\n",
|
|
129
|
+
data.solarVoltage, data.solarCurrent, data.solarPower,
|
|
130
|
+
data.batteryVoltage, data.batterySOC,
|
|
131
|
+
data.loadVoltage, data.loadCurrent,
|
|
132
|
+
data.chargeState, data.controllerTemp);
|
|
133
|
+
} else {
|
|
134
|
+
Serial.println("sendBroadcast failed – check mesh connectivity");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Receive handler
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
void handleIncomingPackage(uint32_t from, String& msg) {
|
|
142
|
+
JsonDocument doc;
|
|
143
|
+
if (deserializeJson(doc, msg) != DeserializationError::Ok) {
|
|
144
|
+
Serial.printf("JSON parse error from %u\n", from);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
JsonObject obj = doc.as<JsonObject>();
|
|
149
|
+
uint16_t msgType = obj["type"];
|
|
150
|
+
|
|
151
|
+
switch (msgType) {
|
|
152
|
+
case 205: { // MpptPackage
|
|
153
|
+
MpptPackage received(obj);
|
|
154
|
+
Serial.printf(
|
|
155
|
+
"MPPT from %u: PV=%.1fV/%.1fA/%dW Bat=%.1fV/%d%% "
|
|
156
|
+
"Load=%.1fV/%.1fA State=%d Temp=%d°C\n",
|
|
157
|
+
received.from,
|
|
158
|
+
received.solarVoltage, received.solarCurrent, received.solarPower,
|
|
159
|
+
received.batteryVoltage, received.batterySOC,
|
|
160
|
+
received.loadVoltage, received.loadCurrent,
|
|
161
|
+
received.chargeState, received.controllerTemp);
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case 400: { // CommandPackage
|
|
166
|
+
CommandPackage cmd(obj);
|
|
167
|
+
if (cmd.dest == mesh.getNodeId()) {
|
|
168
|
+
handleCommandPackage(cmd);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
default:
|
|
174
|
+
// Silently ignore unknown types
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Command handler
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
void handleCommandPackage(CommandPackage& cmd) {
|
|
183
|
+
switch (cmd.command) {
|
|
184
|
+
case 10: // Immediate MPPT data request
|
|
185
|
+
Serial.printf("Immediate data request from command %u\n", cmd.commandId);
|
|
186
|
+
sendMpptData();
|
|
187
|
+
break;
|
|
188
|
+
|
|
189
|
+
default:
|
|
190
|
+
Serial.printf("Unknown command %d\n", cmd.command);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Mesh callbacks
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
void newConnectionCallback(uint32_t nodeId) {
|
|
199
|
+
Serial.printf("New connection: nodeId = %u\n", nodeId);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
void changedConnectionCallback() {
|
|
203
|
+
Serial.println("Connections changed");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
void nodeTimeAdjustedCallback(int32_t offset) {
|
|
207
|
+
Serial.printf("Time adjusted: offset = %d µs\n", offset);
|
|
208
|
+
}
|
|
@@ -238,6 +238,23 @@ void setup() {
|
|
|
238
238
|
mesh.onBridgeStatusChanged(&bridgeStatusCallback);
|
|
239
239
|
mesh.onBridgeRoleChanged(&bridgeRoleCallback);
|
|
240
240
|
|
|
241
|
+
// Monitor bridge coordination (fires every ~30s per bridge)
|
|
242
|
+
mesh.onBridgeCoordination(
|
|
243
|
+
[](const painlessmesh::plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode) {
|
|
244
|
+
Serial.printf("Bridge %u: priority=%d, load=%d%%\n",
|
|
245
|
+
fromNode, pkg.priority, pkg.load);
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// Get notified when bridge state changes (new/updated/lost)
|
|
250
|
+
mesh.onBridgeCoordinationChanged(
|
|
251
|
+
[](const painlessmesh::plugin::BridgeCoordinationPackage& pkg, uint32_t fromNode,
|
|
252
|
+
TSTRING changeType) {
|
|
253
|
+
Serial.printf("Bridge %s: %u (role=%s)\n",
|
|
254
|
+
changeType.c_str(), fromNode, pkg.role.c_str());
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
|
|
241
258
|
// Configure logging
|
|
242
259
|
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
|
|
243
260
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.10)
|
|
2
|
+
project(PCMeshNode)
|
|
3
|
+
|
|
4
|
+
set(CMAKE_CXX_STANDARD 14)
|
|
5
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
6
|
+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
|
|
7
|
+
|
|
8
|
+
# Find Boost (compatible with different CMake versions and platforms)
|
|
9
|
+
FIND_PACKAGE(Boost 1.66 COMPONENTS system)
|
|
10
|
+
|
|
11
|
+
IF(Boost_FOUND)
|
|
12
|
+
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS})
|
|
13
|
+
LINK_DIRECTORIES(${Boost_LIBRARY_DIRS})
|
|
14
|
+
message(STATUS "Boost found: ${Boost_VERSION}")
|
|
15
|
+
message(STATUS "Boost include dirs: ${Boost_INCLUDE_DIRS}")
|
|
16
|
+
ELSE()
|
|
17
|
+
message(FATAL_ERROR "Boost not found! Please install Boost development libraries:\n"
|
|
18
|
+
" Ubuntu/Debian: sudo apt-get install libboost-dev libboost-system-dev\n"
|
|
19
|
+
" macOS: brew install boost\n"
|
|
20
|
+
" Windows: See https://www.boost.org/")
|
|
21
|
+
ENDIF()
|
|
22
|
+
|
|
23
|
+
# Set compiler flags
|
|
24
|
+
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
|
25
|
+
|
|
26
|
+
# Add executable
|
|
27
|
+
add_executable(pc_mesh_node
|
|
28
|
+
pc_mesh_node.cpp
|
|
29
|
+
../../test/catch/fake_serial.cpp
|
|
30
|
+
../../src/scheduler.cpp
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Include directories
|
|
34
|
+
target_include_directories(pc_mesh_node PUBLIC
|
|
35
|
+
../../test/include/
|
|
36
|
+
../../test/boost/
|
|
37
|
+
../../test/ArduinoJson/src/
|
|
38
|
+
../../test/TaskScheduler/src/
|
|
39
|
+
../../src/
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Link libraries
|
|
43
|
+
TARGET_LINK_LIBRARIES(pc_mesh_node ${Boost_LIBRARIES})
|
|
44
|
+
|
|
45
|
+
# Print instructions
|
|
46
|
+
message(STATUS "")
|
|
47
|
+
message(STATUS "===========================================")
|
|
48
|
+
message(STATUS "PC Mesh Node - Build Configuration")
|
|
49
|
+
message(STATUS "===========================================")
|
|
50
|
+
message(STATUS "Build with: cmake . && make")
|
|
51
|
+
message(STATUS "Run with: ./pc_mesh_node <bridge_ip> <bridge_port>")
|
|
52
|
+
message(STATUS "Example: ./pc_mesh_node 192.168.1.100 5555")
|
|
53
|
+
message(STATUS "===========================================")
|
|
54
|
+
message(STATUS "")
|