@alteriom/painlessmesh 1.6.1 → 1.7.2

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 (129) hide show
  1. package/CHANGELOG.md +380 -143
  2. package/LICENSE +674 -674
  3. package/README.md +477 -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 +77 -70
  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/getting-started/first-mesh.md +409 -409
  25. package/docs/getting-started/installation.md +274 -274
  26. package/docs/getting-started/quickstart.md +157 -157
  27. package/docs/improvements/FEATURE_PROPOSALS.md +337 -0
  28. package/docs/improvements/PHASE1_IMPLEMENTATION.md +325 -0
  29. package/docs/improvements/PHASE2_IMPLEMENTATION.md +567 -0
  30. package/docs/improvements/README.md +86 -68
  31. package/docs/improvements/ota-and-status-enhancements.md +911 -0
  32. package/docs/improvements/ota-status-architecture-diagrams.md +658 -0
  33. package/docs/improvements/ota-status-quick-reference.md +284 -0
  34. package/docs/platformio-publishing.md +255 -0
  35. package/docs/platformio-setup-summary.md +121 -0
  36. package/docs/troubleshooting/common-issues.md +520 -520
  37. package/docs/troubleshooting/faq.md +472 -472
  38. package/docs/tutorials/basic-examples.md +717 -717
  39. package/docs/wiki/API-Reference.md +245 -245
  40. package/docs/wiki/Complete-Documentation.md +122 -122
  41. package/examples/alteriom/README.md +139 -81
  42. package/examples/alteriom/alteriom.ino +186 -185
  43. package/examples/alteriom/alteriom_sensor_package.hpp +240 -127
  44. package/examples/alteriom/platformio.ini +24 -24
  45. package/examples/alteriomImproved/alteriom_sensor_package.hpp +224 -0
  46. package/examples/{alteriom → alteriomImproved}/improved_sensor_node.ino +245 -245
  47. package/examples/alteriomImproved/platformio.ini +25 -0
  48. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +224 -0
  49. package/examples/alteriomPhase1/phase1_features.ino +242 -0
  50. package/examples/alteriomPhase1/platformio.ini +25 -0
  51. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +224 -0
  52. package/examples/alteriomPhase2/phase2_features.ino +186 -0
  53. package/examples/alteriomPhase2/platformio.ini +25 -0
  54. package/examples/{alteriom → alteriomSensorNode}/alteriom_sensor_node.ino +183 -183
  55. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +224 -0
  56. package/examples/alteriomSensorNode/platformio.ini +25 -0
  57. package/examples/basic/basic.ino +66 -66
  58. package/examples/basic/platformio.ini +25 -25
  59. package/examples/bridge/bridge.ino +51 -51
  60. package/examples/bridge/mesh_event_publisher.hpp +253 -0
  61. package/examples/bridge/mesh_topology_reporter.hpp +303 -0
  62. package/examples/bridge/mqtt_command_bridge.hpp +459 -0
  63. package/examples/bridge/mqtt_status_bridge.hpp +519 -0
  64. package/examples/bridge/platformio.ini +25 -25
  65. package/examples/echoNode/echoNode.ino +33 -33
  66. package/examples/echoNode/platformio.ini +25 -25
  67. package/examples/logClient/logClient.ino +109 -109
  68. package/examples/logClient/platformio.ini +25 -25
  69. package/examples/logServer/logServer.ino +81 -81
  70. package/examples/logServer/platformio.ini +25 -25
  71. package/examples/meshCommandNode/alteriom_sensor_package.hpp +235 -0
  72. package/examples/meshCommandNode/meshCommandNode.ino +263 -0
  73. package/examples/meshCommandNode/platformio.ini +25 -0
  74. package/examples/mqttBridge/mqttBridge.ino +118 -118
  75. package/examples/mqttBridge/platformio.ini +26 -26
  76. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +235 -0
  77. package/examples/mqttCommandBridge/mesh_event_publisher.hpp +253 -0
  78. package/examples/mqttCommandBridge/mesh_topology_reporter.hpp +303 -0
  79. package/examples/mqttCommandBridge/mqttCommandBridge.ino +252 -0
  80. package/examples/mqttCommandBridge/mqtt_command_bridge.hpp +459 -0
  81. package/examples/mqttCommandBridge/platformio.ini +26 -0
  82. package/examples/mqttStatusBridge/mqttStatusBridge.ino +216 -0
  83. package/examples/mqttStatusBridge/mqtt_status_bridge.hpp +522 -0
  84. package/examples/mqttStatusBridge/platformio.ini +26 -0
  85. package/examples/mqttTopologyTest/README.md +467 -0
  86. package/examples/mqttTopologyTest/mqttTopologyTest.ino +748 -0
  87. package/examples/mqttTopologyTest/platformio.ini +26 -0
  88. package/examples/namedMesh/namedMesh.ino +97 -97
  89. package/examples/namedMesh/platformio.ini +25 -25
  90. package/examples/otaReceiver/otaReceiver.ino +79 -79
  91. package/examples/otaReceiver/platformio.ini +25 -25
  92. package/examples/otaSender/otaSender.ino +160 -151
  93. package/examples/otaSender/platformio.ini +25 -25
  94. package/examples/startHere/platformio.ini +25 -25
  95. package/examples/startHere/startHere.ino +159 -159
  96. package/examples/webServer/platformio.ini +27 -27
  97. package/examples/webServer/webServer.ino +89 -89
  98. package/keywords.txt +48 -48
  99. package/library.json +55 -34
  100. package/library.properties +10 -10
  101. package/package.json +86 -78
  102. package/src/AlteriomPainlessMesh.h +97 -97
  103. package/src/arduino/wifi.hpp +365 -365
  104. package/src/boost/asynctcp.hpp +279 -279
  105. package/src/painlessMesh.h +70 -70
  106. package/src/painlessMeshSTA.cpp +236 -236
  107. package/src/painlessMeshSTA.h +58 -58
  108. package/src/painlessTaskOptions.h +4 -4
  109. package/src/painlessmesh/base64.hpp +111 -111
  110. package/src/painlessmesh/buffer.hpp +229 -229
  111. package/src/painlessmesh/callback.hpp +91 -91
  112. package/src/painlessmesh/configuration.hpp +77 -77
  113. package/src/painlessmesh/connection.hpp +192 -192
  114. package/src/painlessmesh/layout.hpp +188 -188
  115. package/src/painlessmesh/logger.hpp +158 -158
  116. package/src/painlessmesh/memory.hpp +119 -119
  117. package/src/painlessmesh/mesh.hpp +761 -560
  118. package/src/painlessmesh/metrics.hpp +322 -322
  119. package/src/painlessmesh/ntp.hpp +263 -263
  120. package/src/painlessmesh/ota.hpp +582 -553
  121. package/src/painlessmesh/plugin.hpp +188 -188
  122. package/src/painlessmesh/protocol.hpp +813 -813
  123. package/src/painlessmesh/router.hpp +322 -322
  124. package/src/painlessmesh/tcp.hpp +71 -71
  125. package/src/painlessmesh/validation.hpp +238 -238
  126. package/src/plugin/performance.hpp +214 -214
  127. package/src/plugin/remote.hpp +64 -64
  128. package/src/scheduler.cpp +10 -10
  129. 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