@alteriom/painlessmesh 1.6.1 → 1.7.3

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 (152) hide show
  1. package/CHANGELOG.md +435 -144
  2. package/LICENSE +674 -674
  3. package/README.md +491 -434
  4. package/RELEASE_GUIDE.md +504 -418
  5. package/docs/DOCUMENTATION_MIGRATION_PLAN.md +175 -175
  6. package/docs/IMPLEMENTATION_PLAN_MESH_TOPOLOGY.md +1062 -0
  7. package/docs/MESH_TOPOLOGY_GUIDE.md +992 -0
  8. package/docs/MESH_TOPOLOGY_PROGRESS.md +422 -0
  9. package/docs/MQTT_BRIDGE_COMMANDS.md +894 -0
  10. package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +324 -0
  11. package/docs/MQTT_COMMAND_SCHEMA_PROPOSAL.md +576 -0
  12. package/docs/MQTT_SCHEMA_COMPLIANCE.md +285 -0
  13. package/docs/MQTT_SCHEMA_PROPOSALS.md +446 -0
  14. package/docs/MQTT_SCHEMA_REVIEW.md +690 -0
  15. package/docs/OTA_COMMANDS_REFERENCE.md +554 -0
  16. package/docs/PHASE1_GUIDE.md +349 -0
  17. package/docs/PHASE2_GUIDE.md +543 -0
  18. package/docs/README.md +130 -71
  19. package/docs/SCHEMA_VALIDATION_CHECKLIST.md +222 -0
  20. package/docs/alteriom/overview.md +507 -507
  21. package/docs/api/core-api.md +606 -606
  22. package/docs/architecture/mesh-architecture.md +378 -378
  23. package/docs/architecture/plugin-system.md +516 -516
  24. package/docs/archive/DOCUSAURUS_DEPLOYMENT.md +166 -0
  25. package/docs/archive/FEATURE_PROPOSALS.md +337 -0
  26. package/docs/archive/LIBRARY_JSON_FIX.md +98 -0
  27. package/docs/archive/LIBRARY_STRUCTURE_FIX.md +215 -0
  28. package/docs/archive/PHASE1_IMPLEMENTATION.md +325 -0
  29. package/docs/archive/PHASE2_IMPLEMENTATION.md +567 -0
  30. package/docs/archive/RELEASE_SUMMARY.md +173 -0
  31. package/docs/archive/SCONS_BUILD_FIX.md +313 -0
  32. package/docs/archive/TRIGGER_RELEASE.md +280 -0
  33. package/docs/archive/VECTOR_INCLUDE_FIX.md +129 -0
  34. package/docs/archive/ota-and-status-enhancements.md +911 -0
  35. package/docs/archive/ota-status-architecture-diagrams.md +658 -0
  36. package/docs/archive/ota-status-quick-reference.md +284 -0
  37. package/docs/development/ARDUINO_COMPLIANCE_SUMMARY.md +71 -0
  38. package/docs/development/CODE_REFACTORING_RECOMMENDATIONS.md +1011 -0
  39. package/docs/development/DOCKER_TESTING.md +196 -0
  40. package/docs/development/PLATFORMIO_USAGE.md +180 -0
  41. package/docs/development/TESTING_SUMMARY.md +126 -0
  42. package/docs/development/contributing.md +301 -0
  43. package/docs/development/documentation.md +583 -0
  44. package/docs/getting-started/first-mesh.md +409 -409
  45. package/docs/getting-started/installation.md +274 -274
  46. package/docs/getting-started/quickstart.md +157 -157
  47. package/docs/improvements/FUTURE_PROPOSALS.md +1016 -0
  48. package/docs/improvements/IMPLEMENTATION_HISTORY.md +1091 -0
  49. package/docs/improvements/OTA_STATUS_ENHANCEMENTS.md +709 -0
  50. package/docs/improvements/README.md +212 -69
  51. package/docs/platformio-publishing.md +255 -0
  52. package/docs/platformio-setup-summary.md +121 -0
  53. package/docs/releases/FEATURE_HISTORY.md +543 -0
  54. package/docs/releases/PATCH_v1.7.3.md +262 -0
  55. package/docs/releases/PHASE1_SUMMARY.md +246 -0
  56. package/docs/releases/PHASE2_SUMMARY.md +499 -0
  57. package/docs/releases/RELEASE_NOTES_1.7.0.md +539 -0
  58. package/docs/troubleshooting/common-issues.md +520 -520
  59. package/docs/troubleshooting/debugging.md +455 -0
  60. package/docs/troubleshooting/faq.md +472 -472
  61. package/docs/tutorials/basic-examples.md +717 -717
  62. package/docs/wiki/API-Reference.md +245 -245
  63. package/docs/wiki/Complete-Documentation.md +122 -122
  64. package/examples/alteriom/README.md +139 -81
  65. package/examples/alteriom/alteriom.ino +186 -185
  66. package/examples/alteriom/alteriom_sensor_package.hpp +240 -127
  67. package/examples/alteriom/platformio.ini +24 -24
  68. package/examples/alteriomImproved/alteriom_sensor_package.hpp +224 -0
  69. package/examples/{alteriom → alteriomImproved}/improved_sensor_node.ino +245 -245
  70. package/examples/alteriomImproved/platformio.ini +25 -0
  71. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +224 -0
  72. package/examples/alteriomPhase1/phase1_features.ino +242 -0
  73. package/examples/alteriomPhase1/platformio.ini +25 -0
  74. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +224 -0
  75. package/examples/alteriomPhase2/phase2_features.ino +186 -0
  76. package/examples/alteriomPhase2/platformio.ini +25 -0
  77. package/examples/{alteriom → alteriomSensorNode}/alteriom_sensor_node.ino +183 -183
  78. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +224 -0
  79. package/examples/alteriomSensorNode/platformio.ini +25 -0
  80. package/examples/basic/basic.ino +66 -66
  81. package/examples/basic/platformio.ini +25 -25
  82. package/examples/bridge/bridge.ino +51 -51
  83. package/examples/bridge/mesh_event_publisher.hpp +253 -0
  84. package/examples/bridge/mesh_topology_reporter.hpp +303 -0
  85. package/examples/bridge/mqtt_command_bridge.hpp +459 -0
  86. package/examples/bridge/mqtt_status_bridge.hpp +519 -0
  87. package/examples/bridge/platformio.ini +25 -25
  88. package/examples/echoNode/echoNode.ino +33 -33
  89. package/examples/echoNode/platformio.ini +25 -25
  90. package/examples/logClient/logClient.ino +109 -109
  91. package/examples/logClient/platformio.ini +25 -25
  92. package/examples/logServer/logServer.ino +81 -81
  93. package/examples/logServer/platformio.ini +25 -25
  94. package/examples/meshCommandNode/alteriom_sensor_package.hpp +235 -0
  95. package/examples/meshCommandNode/meshCommandNode.ino +263 -0
  96. package/examples/meshCommandNode/platformio.ini +25 -0
  97. package/examples/mqttBridge/mqttBridge.ino +118 -118
  98. package/examples/mqttBridge/platformio.ini +26 -26
  99. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +235 -0
  100. package/examples/mqttCommandBridge/mesh_event_publisher.hpp +253 -0
  101. package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +303 -0
  102. package/examples/mqttCommandBridge/mqttCommandBridge.ino +252 -0
  103. package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +459 -0
  104. package/examples/mqttCommandBridge/platformio.ini +26 -0
  105. package/examples/mqttStatusBridge/mqttStatusBridge.ino +216 -0
  106. package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +522 -0
  107. package/examples/mqttStatusBridge/platformio.ini +26 -0
  108. package/examples/mqttTopologyTest/README.md +467 -0
  109. package/examples/mqttTopologyTest/mqttTopologyTest.ino +748 -0
  110. package/examples/mqttTopologyTest/platformio.ini +26 -0
  111. package/examples/namedMesh/namedMesh.ino +97 -97
  112. package/examples/namedMesh/platformio.ini +25 -25
  113. package/examples/otaReceiver/otaReceiver.ino +79 -79
  114. package/examples/otaReceiver/platformio.ini +25 -25
  115. package/examples/otaSender/otaSender.ino +160 -151
  116. package/examples/otaSender/platformio.ini +25 -25
  117. package/examples/startHere/platformio.ini +25 -25
  118. package/examples/startHere/startHere.ino +159 -159
  119. package/examples/webServer/platformio.ini +27 -27
  120. package/examples/webServer/webServer.ino +89 -89
  121. package/keywords.txt +48 -48
  122. package/library.json +55 -34
  123. package/library.properties +10 -10
  124. package/package.json +86 -78
  125. package/src/AlteriomPainlessMesh.h +97 -97
  126. package/src/arduino/wifi.hpp +365 -365
  127. package/src/boost/asynctcp.hpp +279 -279
  128. package/src/painlessMesh.h +70 -70
  129. package/src/painlessMeshSTA.cpp +236 -236
  130. package/src/painlessMeshSTA.h +58 -58
  131. package/src/painlessTaskOptions.h +4 -4
  132. package/src/painlessmesh/base64.hpp +111 -111
  133. package/src/painlessmesh/buffer.hpp +229 -229
  134. package/src/painlessmesh/callback.hpp +91 -91
  135. package/src/painlessmesh/configuration.hpp +77 -77
  136. package/src/painlessmesh/connection.hpp +192 -192
  137. package/src/painlessmesh/layout.hpp +188 -188
  138. package/src/painlessmesh/logger.hpp +158 -158
  139. package/src/painlessmesh/memory.hpp +119 -119
  140. package/src/painlessmesh/mesh.hpp +761 -560
  141. package/src/painlessmesh/metrics.hpp +322 -322
  142. package/src/painlessmesh/ntp.hpp +263 -263
  143. package/src/painlessmesh/ota.hpp +582 -553
  144. package/src/painlessmesh/plugin.hpp +188 -188
  145. package/src/painlessmesh/protocol.hpp +813 -813
  146. package/src/painlessmesh/router.hpp +338 -322
  147. package/src/painlessmesh/tcp.hpp +71 -71
  148. package/src/painlessmesh/validation.hpp +238 -238
  149. package/src/plugin/performance.hpp +214 -214
  150. package/src/plugin/remote.hpp +64 -64
  151. package/src/scheduler.cpp +10 -10
  152. package/src/wifi.cpp +2 -2
@@ -1,66 +1,66 @@
1
- //************************************************************
2
- // this is a simple example that uses the painlessMesh library
3
- //
4
- // 1. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
5
- // 2. prints anything it receives to Serial.print
6
- //
7
- //
8
- //************************************************************
9
- #include "painlessMesh.h"
10
-
11
- #define MESH_PREFIX "whateverYouLike"
12
- #define MESH_PASSWORD "somethingSneaky"
13
- #define MESH_PORT 5555
14
-
15
- Scheduler userScheduler; // to control your personal task
16
- painlessMesh mesh;
17
-
18
- // User stub
19
- void sendMessage() ; // Prototype so PlatformIO doesn't complain
20
-
21
- Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );
22
-
23
- void sendMessage() {
24
- String msg = "Hello from node ";
25
- msg += mesh.getNodeId();
26
- mesh.sendBroadcast( msg );
27
- taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
28
- }
29
-
30
- // Needed for painless library
31
- void receivedCallback( uint32_t from, String &msg ) {
32
- Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
33
- }
34
-
35
- void newConnectionCallback(uint32_t nodeId) {
36
- Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
37
- }
38
-
39
- void changedConnectionCallback() {
40
- Serial.printf("Changed connections\n");
41
- }
42
-
43
- void nodeTimeAdjustedCallback(int32_t offset) {
44
- Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
45
- }
46
-
47
- void setup() {
48
- Serial.begin(115200);
49
-
50
- //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
51
- mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
52
-
53
- mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
54
- mesh.onReceive(&receivedCallback);
55
- mesh.onNewConnection(&newConnectionCallback);
56
- mesh.onChangedConnections(&changedConnectionCallback);
57
- mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
58
-
59
- userScheduler.addTask( taskSendMessage );
60
- taskSendMessage.enable();
61
- }
62
-
63
- void loop() {
64
- // it will run the user scheduler as well
65
- mesh.update();
66
- }
1
+ //************************************************************
2
+ // this is a simple example that uses the painlessMesh library
3
+ //
4
+ // 1. sends a silly message to every node on the mesh at a random time between 1 and 5 seconds
5
+ // 2. prints anything it receives to Serial.print
6
+ //
7
+ //
8
+ //************************************************************
9
+ #include "painlessMesh.h"
10
+
11
+ #define MESH_PREFIX "whateverYouLike"
12
+ #define MESH_PASSWORD "somethingSneaky"
13
+ #define MESH_PORT 5555
14
+
15
+ Scheduler userScheduler; // to control your personal task
16
+ painlessMesh mesh;
17
+
18
+ // User stub
19
+ void sendMessage() ; // Prototype so PlatformIO doesn't complain
20
+
21
+ Task taskSendMessage( TASK_SECOND * 1 , TASK_FOREVER, &sendMessage );
22
+
23
+ void sendMessage() {
24
+ String msg = "Hello from node ";
25
+ msg += mesh.getNodeId();
26
+ mesh.sendBroadcast( msg );
27
+ taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
28
+ }
29
+
30
+ // Needed for painless library
31
+ void receivedCallback( uint32_t from, String &msg ) {
32
+ Serial.printf("startHere: Received from %u msg=%s\n", from, msg.c_str());
33
+ }
34
+
35
+ void newConnectionCallback(uint32_t nodeId) {
36
+ Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
37
+ }
38
+
39
+ void changedConnectionCallback() {
40
+ Serial.printf("Changed connections\n");
41
+ }
42
+
43
+ void nodeTimeAdjustedCallback(int32_t offset) {
44
+ Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
45
+ }
46
+
47
+ void setup() {
48
+ Serial.begin(115200);
49
+
50
+ //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
51
+ mesh.setDebugMsgTypes( ERROR | STARTUP ); // set before init() so that you can see startup messages
52
+
53
+ mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
54
+ mesh.onReceive(&receivedCallback);
55
+ mesh.onNewConnection(&newConnectionCallback);
56
+ mesh.onChangedConnections(&changedConnectionCallback);
57
+ mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
58
+
59
+ userScheduler.addTask( taskSendMessage );
60
+ taskSendMessage.enable();
61
+ }
62
+
63
+ void loop() {
64
+ // it will run the user scheduler as well
65
+ mesh.update();
66
+ }
@@ -1,25 +1,25 @@
1
- [platformio]
2
- src_dir = .
3
-
4
- [env]
5
- lib_deps =
6
- bblanchon/ArduinoJson
7
- arkhipenko/TaskScheduler
8
-
9
- [env:esp8266]
10
- platform = espressif8266
11
- board = nodemcuv2
12
- framework = arduino
13
- lib_extra_dirs = ../../
14
- lib_deps =
15
- ${env.lib_deps} ; Inherit common dependencies
16
- esp32async/ESPAsyncTCP ; Only for ESP8266
17
-
18
- [env:esp32]
19
- platform = espressif32
20
- board = esp32dev
21
- framework = arduino
22
- lib_extra_dirs = ../../
23
- lib_deps =
24
- ${env.lib_deps} ; Inherit common dependencies
25
- esp32async/AsyncTCP
1
+ [platformio]
2
+ src_dir = .
3
+
4
+ [env]
5
+ lib_deps =
6
+ bblanchon/ArduinoJson
7
+ arkhipenko/TaskScheduler
8
+
9
+ [env:esp8266]
10
+ platform = espressif8266
11
+ board = nodemcuv2
12
+ framework = arduino
13
+ lib_extra_dirs = ../../
14
+ lib_deps =
15
+ ${env.lib_deps} ; Inherit common dependencies
16
+ esp32async/ESPAsyncTCP ; Only for ESP8266
17
+
18
+ [env:esp32]
19
+ platform = espressif32
20
+ board = esp32dev
21
+ framework = arduino
22
+ lib_extra_dirs = ../../
23
+ lib_deps =
24
+ ${env.lib_deps} ; Inherit common dependencies
25
+ esp32async/AsyncTCP
@@ -1,51 +1,51 @@
1
- //************************************************************
2
- // this is a simple example that uses the painlessMesh library to
3
- // connect to a node on another network. Please see the WIKI on gitlab
4
- // for more details
5
- // https://gitlab.com/painlessMesh/painlessMesh/wikis/bridge-between-mesh-and-another-network
6
- //************************************************************
7
- #include "painlessMesh.h"
8
-
9
- #define MESH_PREFIX "whateverYouLike"
10
- #define MESH_PASSWORD "somethingSneaky"
11
- #define MESH_PORT 5555
12
-
13
- #define STATION_SSID "MSYSSID"
14
- #define STATION_PASSWORD "SSIDPASSWORD"
15
- #define STATION_PORT 5555
16
- uint8_t station_ip[4] = {192, 168, 1, 3}; // IP of the server
17
-
18
- // prototypes
19
- void receivedCallback(uint32_t from, String &msg);
20
-
21
- painlessMesh mesh;
22
-
23
- void setup() {
24
- Serial.begin(115200);
25
- mesh.setDebugMsgTypes(
26
- ERROR | STARTUP |
27
- CONNECTION); // set before init() so that you can see startup messages
28
-
29
- // Channel set to 6. Make sure to use the same channel for your mesh and for
30
- // you other network (STATION_SSID)
31
- mesh.init(MESH_PREFIX, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 1);
32
- // Setup over the air update support
33
- mesh.initOTAReceive("bridge");
34
-
35
- mesh.stationManual(STATION_SSID, STATION_PASSWORD, STATION_PORT, station_ip);
36
- // Bridge node, should (in most cases) be a root node. See [the
37
- // wiki](https://gitlab.com/painlessMesh/painlessMesh/wikis/Possible-challenges-in-mesh-formation)
38
- // for some background
39
- mesh.setRoot(true);
40
- // This node and all other nodes should ideally know the mesh contains a root,
41
- // so call this on all nodes
42
- mesh.setContainsRoot(true);
43
-
44
- mesh.onReceive(&receivedCallback);
45
- }
46
-
47
- void loop() { mesh.update(); }
48
-
49
- void receivedCallback(uint32_t from, String &msg) {
50
- Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
51
- }
1
+ //************************************************************
2
+ // this is a simple example that uses the painlessMesh library to
3
+ // connect to a node on another network. Please see the WIKI on gitlab
4
+ // for more details
5
+ // https://gitlab.com/painlessMesh/painlessMesh/wikis/bridge-between-mesh-and-another-network
6
+ //************************************************************
7
+ #include "painlessMesh.h"
8
+
9
+ #define MESH_PREFIX "whateverYouLike"
10
+ #define MESH_PASSWORD "somethingSneaky"
11
+ #define MESH_PORT 5555
12
+
13
+ #define STATION_SSID "MSYSSID"
14
+ #define STATION_PASSWORD "SSIDPASSWORD"
15
+ #define STATION_PORT 5555
16
+ uint8_t station_ip[4] = {192, 168, 1, 3}; // IP of the server
17
+
18
+ // prototypes
19
+ void receivedCallback(uint32_t from, String &msg);
20
+
21
+ painlessMesh mesh;
22
+
23
+ void setup() {
24
+ Serial.begin(115200);
25
+ mesh.setDebugMsgTypes(
26
+ ERROR | STARTUP |
27
+ CONNECTION); // set before init() so that you can see startup messages
28
+
29
+ // Channel set to 6. Make sure to use the same channel for your mesh and for
30
+ // you other network (STATION_SSID)
31
+ mesh.init(MESH_PREFIX, MESH_PASSWORD, MESH_PORT, WIFI_AP_STA, 1);
32
+ // Setup over the air update support
33
+ mesh.initOTAReceive("bridge");
34
+
35
+ mesh.stationManual(STATION_SSID, STATION_PASSWORD, STATION_PORT, station_ip);
36
+ // Bridge node, should (in most cases) be a root node. See [the
37
+ // wiki](https://gitlab.com/painlessMesh/painlessMesh/wikis/Possible-challenges-in-mesh-formation)
38
+ // for some background
39
+ mesh.setRoot(true);
40
+ // This node and all other nodes should ideally know the mesh contains a root,
41
+ // so call this on all nodes
42
+ mesh.setContainsRoot(true);
43
+
44
+ mesh.onReceive(&receivedCallback);
45
+ }
46
+
47
+ void loop() { mesh.update(); }
48
+
49
+ void receivedCallback(uint32_t from, String &msg) {
50
+ Serial.printf("bridge: Received from %u msg=%s\n", from, msg.c_str());
51
+ }
@@ -0,0 +1,253 @@
1
+ #ifndef MESH_EVENT_PUBLISHER_HPP
2
+ #define MESH_EVENT_PUBLISHER_HPP
3
+
4
+ #include "painlessMesh.h"
5
+ #include <PubSubClient.h>
6
+ #include <ArduinoJson.h>
7
+
8
+ /**
9
+ * MeshEventPublisher - Publish real-time mesh events
10
+ *
11
+ * Publishes mesh state change events conforming to @alteriom/mqtt-schema v0.5.0
12
+ * mesh_event.schema.json specification.
13
+ *
14
+ * Event Types:
15
+ * - node_join: New node entered mesh
16
+ * - node_leave: Node left mesh (clean disconnect)
17
+ * - connection_lost: Direct connection failed
18
+ * - connection_restored: Connection recovered
19
+ *
20
+ * @author Alteriom Development Team
21
+ * @date October 12, 2025
22
+ */
23
+ class MeshEventPublisher {
24
+ private:
25
+ painlessMesh& mesh;
26
+ PubSubClient& mqttClient;
27
+ String meshId;
28
+ String eventTopic;
29
+ String firmwareVersion;
30
+
31
+ public:
32
+ MeshEventPublisher(painlessMesh& mesh, PubSubClient& mqttClient,
33
+ const String& meshId, const String& topic = "alteriom/mesh/events",
34
+ const String& fwVersion = "GW 2.3.4")
35
+ : mesh(mesh), mqttClient(mqttClient), meshId(meshId),
36
+ eventTopic(topic), firmwareVersion(fwVersion) {}
37
+
38
+ /**
39
+ * Publish node join event
40
+ * Called when a new node connects to the mesh
41
+ */
42
+ void publishNodeJoin(uint32_t nodeId) {
43
+ DynamicJsonDocument doc(1024);
44
+
45
+ // Envelope
46
+ doc["schema_version"] = 1;
47
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
48
+ doc["device_type"] = "gateway";
49
+ doc["timestamp"] = getISO8601Timestamp();
50
+ doc["firmware_version"] = firmwareVersion;
51
+
52
+ // Event
53
+ doc["event"] = "mesh_event";
54
+ doc["event_type"] = "node_join";
55
+ doc["mesh_id"] = meshId;
56
+
57
+ JsonArray affected = doc.createNestedArray("affected_nodes");
58
+ affected.add(generateDeviceId(nodeId));
59
+
60
+ JsonObject details = doc.createNestedObject("details");
61
+ details["total_nodes"] = mesh.getNodeList().size() + 1;
62
+ details["timestamp"] = getISO8601Timestamp();
63
+
64
+ publishEvent(doc, "Node Join");
65
+ }
66
+
67
+ /**
68
+ * Publish node leave event
69
+ * Called when a node disconnects from the mesh
70
+ */
71
+ void publishNodeLeave(uint32_t nodeId) {
72
+ DynamicJsonDocument doc(1024);
73
+
74
+ // Envelope
75
+ doc["schema_version"] = 1;
76
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
77
+ doc["device_type"] = "gateway";
78
+ doc["timestamp"] = getISO8601Timestamp();
79
+ doc["firmware_version"] = firmwareVersion;
80
+
81
+ // Event
82
+ doc["event"] = "mesh_event";
83
+ doc["event_type"] = "node_leave";
84
+ doc["mesh_id"] = meshId;
85
+
86
+ JsonArray affected = doc.createNestedArray("affected_nodes");
87
+ affected.add(generateDeviceId(nodeId));
88
+
89
+ JsonObject details = doc.createNestedObject("details");
90
+ details["reason"] = "connection_lost";
91
+ details["last_seen"] = getISO8601Timestamp();
92
+ details["total_nodes"] = mesh.getNodeList().size() + 1;
93
+
94
+ publishEvent(doc, "Node Leave");
95
+ }
96
+
97
+ /**
98
+ * Publish connection lost event
99
+ * Called when a direct connection fails
100
+ */
101
+ void publishConnectionLost(uint32_t nodeId) {
102
+ DynamicJsonDocument doc(1024);
103
+
104
+ doc["schema_version"] = 1;
105
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
106
+ doc["device_type"] = "gateway";
107
+ doc["timestamp"] = getISO8601Timestamp();
108
+ doc["firmware_version"] = firmwareVersion;
109
+
110
+ doc["event"] = "mesh_event";
111
+ doc["event_type"] = "connection_lost";
112
+ doc["mesh_id"] = meshId;
113
+
114
+ JsonArray affected = doc.createNestedArray("affected_nodes");
115
+ affected.add(generateDeviceId(nodeId));
116
+
117
+ JsonObject details = doc.createNestedObject("details");
118
+ details["reason"] = "timeout";
119
+ details["timestamp"] = getISO8601Timestamp();
120
+
121
+ publishEvent(doc, "Connection Lost");
122
+ }
123
+
124
+ /**
125
+ * Publish connection restored event
126
+ * Called when a connection is re-established
127
+ */
128
+ void publishConnectionRestored(uint32_t nodeId) {
129
+ DynamicJsonDocument doc(1024);
130
+
131
+ doc["schema_version"] = 1;
132
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
133
+ doc["device_type"] = "gateway";
134
+ doc["timestamp"] = getISO8601Timestamp();
135
+ doc["firmware_version"] = firmwareVersion;
136
+
137
+ doc["event"] = "mesh_event";
138
+ doc["event_type"] = "connection_restored";
139
+ doc["mesh_id"] = meshId;
140
+
141
+ JsonArray affected = doc.createNestedArray("affected_nodes");
142
+ affected.add(generateDeviceId(nodeId));
143
+
144
+ JsonObject details = doc.createNestedObject("details");
145
+ details["timestamp"] = getISO8601Timestamp();
146
+
147
+ publishEvent(doc, "Connection Restored");
148
+ }
149
+
150
+ /**
151
+ * Publish network split event
152
+ * Called when mesh partitions into separate segments
153
+ */
154
+ void publishNetworkSplit() {
155
+ DynamicJsonDocument doc(1024);
156
+
157
+ doc["schema_version"] = 1;
158
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
159
+ doc["device_type"] = "gateway";
160
+ doc["timestamp"] = getISO8601Timestamp();
161
+ doc["firmware_version"] = firmwareVersion;
162
+
163
+ doc["event"] = "mesh_event";
164
+ doc["event_type"] = "network_split";
165
+ doc["mesh_id"] = meshId;
166
+
167
+ JsonArray affected = doc.createNestedArray("affected_nodes");
168
+ // TODO: Detect which nodes are in different partitions
169
+
170
+ JsonObject details = doc.createNestedObject("details");
171
+ details["timestamp"] = getISO8601Timestamp();
172
+ details["partition_count"] = 2; // TODO: Calculate actual partitions
173
+
174
+ publishEvent(doc, "Network Split");
175
+ }
176
+
177
+ /**
178
+ * Publish network merged event
179
+ * Called when mesh partitions rejoin
180
+ */
181
+ void publishNetworkMerged() {
182
+ DynamicJsonDocument doc(1024);
183
+
184
+ doc["schema_version"] = 1;
185
+ doc["device_id"] = generateDeviceId(mesh.getNodeId());
186
+ doc["device_type"] = "gateway";
187
+ doc["timestamp"] = getISO8601Timestamp();
188
+ doc["firmware_version"] = firmwareVersion;
189
+
190
+ doc["event"] = "mesh_event";
191
+ doc["event_type"] = "network_merged";
192
+ doc["mesh_id"] = meshId;
193
+
194
+ JsonArray affected = doc.createNestedArray("affected_nodes");
195
+ // TODO: List nodes that rejoined
196
+
197
+ JsonObject details = doc.createNestedObject("details");
198
+ details["timestamp"] = getISO8601Timestamp();
199
+
200
+ publishEvent(doc, "Network Merged");
201
+ }
202
+
203
+ private:
204
+ /**
205
+ * Internal method to publish event to MQTT
206
+ */
207
+ void publishEvent(const DynamicJsonDocument& doc, const char* eventName) {
208
+ if (!mqttClient.connected()) {
209
+ Serial.printf("[Event] ⚠️ MQTT not connected, cannot publish %s\n", eventName);
210
+ return;
211
+ }
212
+
213
+ String payload;
214
+ serializeJson(doc, payload);
215
+
216
+ bool success = mqttClient.publish(eventTopic.c_str(), payload.c_str());
217
+
218
+ if (success) {
219
+ Serial.printf("[Event] ✅ Published: %s (node: %s)\n",
220
+ eventName,
221
+ doc["affected_nodes"][0].as<const char*>());
222
+ } else {
223
+ Serial.printf("[Event] ❌ Failed to publish: %s\n", eventName);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Generate device ID in Alteriom format (ALT-XXXXXXXXXXXX)
229
+ */
230
+ String generateDeviceId(uint32_t nodeId) {
231
+ char deviceId[20];
232
+ snprintf(deviceId, sizeof(deviceId), "ALT-%012X", nodeId);
233
+ return String(deviceId);
234
+ }
235
+
236
+ /**
237
+ * Get current timestamp in ISO 8601 format
238
+ * TODO: Use proper NTP time if available
239
+ */
240
+ String getISO8601Timestamp() {
241
+ // For now, use epoch-based timestamp
242
+ // In production, this should use actual NTP-synced time
243
+ unsigned long ms = millis();
244
+ char timestamp[32];
245
+ snprintf(timestamp, sizeof(timestamp), "2025-10-12T%02lu:%02lu:%02luZ",
246
+ (ms / 3600000) % 24,
247
+ (ms / 60000) % 60,
248
+ (ms / 1000) % 60);
249
+ return String(timestamp);
250
+ }
251
+ };
252
+
253
+ #endif // MESH_EVENT_PUBLISHER_HPP