@alteriom/painlessmesh 1.7.9 → 1.8.1
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 +118 -2
- package/README.md +159 -12
- package/docs/BRIDGE_FAILOVER.md +512 -0
- package/docs/BRIDGE_HEALTH_MONITORING.md +293 -0
- package/docs/CREATE_MISSING_RELEASES.md +321 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.8.md +523 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.9.md +542 -0
- package/examples/alteriom/alteriom_sensor_package.hpp +213 -0
- package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +1014 -11
- package/examples/basic/basic.ino +6 -2
- package/examples/bridge/bridge.ino +44 -23
- package/examples/bridge/bridge_health_monitoring_example.ino +188 -0
- package/examples/bridgeAwareSensorNode/alteriom_sensor_package.hpp +1227 -0
- package/examples/bridgeAwareSensorNode/bridgeAwareSensorNode.ino +343 -0
- package/examples/bridgeAwareSensorNode/platformio.ini +26 -0
- package/examples/bridge_failover/README.md +358 -0
- package/examples/bridge_failover/bridge_failover.ino +180 -0
- package/examples/bridge_failover/platformio.ini +27 -0
- package/examples/diagnosticsExample/diagnosticsExample.ino +171 -0
- package/examples/diagnosticsExample/platformio.ini +26 -0
- package/examples/multi_bridge/README.md +346 -0
- package/examples/multi_bridge/primary_bridge.ino +96 -0
- package/examples/multi_bridge/regular_node.ino +141 -0
- package/examples/multi_bridge/secondary_bridge.ino +111 -0
- package/examples/ntpTimeSyncBridge/alteriom_sensor_package.hpp +1383 -0
- package/examples/ntpTimeSyncBridge/ntpTimeSyncBridge.ino +81 -0
- package/examples/ntpTimeSyncNode/alteriom_sensor_package.hpp +1383 -0
- package/examples/ntpTimeSyncNode/ntpTimeSyncNode.ino +109 -0
- package/examples/queued_alarms/README.md +390 -0
- package/examples/queued_alarms/queued_alarms.ino +265 -0
- package/examples/rtcIntegration/README.md +235 -0
- package/examples/rtcIntegration/rtcIntegration.ino +196 -0
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
- package/src/arduino/wifi.hpp +888 -0
- package/src/painlessMeshSTA.cpp +63 -0
- package/src/painlessMeshSTA.h +3 -0
- package/src/painlessmesh/mesh.hpp +1327 -4
- package/src/painlessmesh/message_queue.hpp +368 -0
- package/src/painlessmesh/plugin.hpp +69 -0
- package/src/painlessmesh/rtc.hpp +203 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
//************************************************************
|
|
2
|
+
// Queued Alarms Example - Message Queueing for Offline Mode
|
|
3
|
+
//
|
|
4
|
+
// Demonstrates priority-based message queueing for critical alarms
|
|
5
|
+
// when Internet connection is unavailable. Perfect for IoT systems
|
|
6
|
+
// that cannot afford to lose critical data.
|
|
7
|
+
//
|
|
8
|
+
// Use Case: Fish farm dissolved oxygen monitoring
|
|
9
|
+
// - CRITICAL alarms (low O2) must never be lost
|
|
10
|
+
// - Queue messages during Internet outages
|
|
11
|
+
// - Automatic delivery when connection restored
|
|
12
|
+
//
|
|
13
|
+
// Hardware: ESP32 or ESP8266
|
|
14
|
+
//************************************************************
|
|
15
|
+
|
|
16
|
+
#include "painlessMesh.h"
|
|
17
|
+
|
|
18
|
+
// Mesh configuration
|
|
19
|
+
#define MESH_PREFIX "FishFarmMesh"
|
|
20
|
+
#define MESH_PASSWORD "somethingSneaky"
|
|
21
|
+
#define MESH_PORT 5555
|
|
22
|
+
|
|
23
|
+
// Router credentials for bridge node
|
|
24
|
+
#define ROUTER_SSID "YourWiFiSSID"
|
|
25
|
+
#define ROUTER_PASSWORD "YourWiFiPassword"
|
|
26
|
+
|
|
27
|
+
// Sensor thresholds (mg/L for dissolved oxygen)
|
|
28
|
+
#define CRITICAL_O2_THRESHOLD 3.0
|
|
29
|
+
#define WARNING_O2_THRESHOLD 5.0
|
|
30
|
+
|
|
31
|
+
// Queue configuration
|
|
32
|
+
#define MAX_QUEUE_SIZE 500
|
|
33
|
+
#define QUEUE_PRUNE_AGE (24 * 60 * 60 * 1000) // 24 hours in ms
|
|
34
|
+
|
|
35
|
+
Scheduler userScheduler;
|
|
36
|
+
painlessMesh mesh;
|
|
37
|
+
|
|
38
|
+
bool offlineMode = false;
|
|
39
|
+
uint32_t lastO2Check = 0;
|
|
40
|
+
uint32_t lastQueuePrune = 0;
|
|
41
|
+
|
|
42
|
+
// Simulated sensor reading (replace with actual sensor code)
|
|
43
|
+
float readDissolvedOxygenSensor() {
|
|
44
|
+
// In real application, read from actual sensor
|
|
45
|
+
// For demo, simulate varying O2 levels
|
|
46
|
+
static float o2Level = 7.0;
|
|
47
|
+
o2Level += random(-20, 20) / 10.0; // +/- 2.0 mg/L variation
|
|
48
|
+
o2Level = constrain(o2Level, 2.0, 10.0);
|
|
49
|
+
return o2Level;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Send critical O2 alarm
|
|
53
|
+
void sendCriticalAlarm(float o2Level) {
|
|
54
|
+
// Create alarm message (in real app, use JSON)
|
|
55
|
+
String payload = String("{\"type\":\"CRITICAL_ALARM\",\"sensor\":\"O2\",\"value\":")
|
|
56
|
+
+ String(o2Level, 2) + ",\"threshold\":"
|
|
57
|
+
+ String(CRITICAL_O2_THRESHOLD, 2) + ",\"tankId\":\"TANK_A\",\"nodeId\":"
|
|
58
|
+
+ mesh.getNodeId() + ",\"timestamp\":" + mesh.getNodeTime() + "}";
|
|
59
|
+
|
|
60
|
+
if (offlineMode || !mesh.hasInternetConnection()) {
|
|
61
|
+
// CRITICAL: Queue for guaranteed delivery
|
|
62
|
+
uint32_t msgId = mesh.queueMessage(
|
|
63
|
+
payload,
|
|
64
|
+
"mqtt://cloud.farm.com/alarms/critical",
|
|
65
|
+
PRIORITY_CRITICAL
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (msgId) {
|
|
69
|
+
Serial.printf("🚨 CRITICAL O2 ALARM: %.2f mg/L - QUEUED #%u\n", o2Level, msgId);
|
|
70
|
+
} else {
|
|
71
|
+
Serial.printf("🚨 CRITICAL O2 ALARM: %.2f mg/L - QUEUE FAILED!\n", o2Level);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
// Send immediately via bridge (in real app, use MQTT client)
|
|
75
|
+
Serial.printf("🚨 CRITICAL O2 ALARM: %.2f mg/L - SENT IMMEDIATELY\n", o2Level);
|
|
76
|
+
// mqttClient.publish("alarms/critical", payload.c_str());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Send warning alarm
|
|
81
|
+
void sendWarningAlarm(float o2Level) {
|
|
82
|
+
String payload = String("{\"type\":\"WARNING\",\"sensor\":\"O2\",\"value\":")
|
|
83
|
+
+ String(o2Level, 2) + ",\"threshold\":"
|
|
84
|
+
+ String(WARNING_O2_THRESHOLD, 2) + ",\"nodeId\":"
|
|
85
|
+
+ mesh.getNodeId() + "}";
|
|
86
|
+
|
|
87
|
+
if (offlineMode) {
|
|
88
|
+
uint32_t msgId = mesh.queueMessage(
|
|
89
|
+
payload,
|
|
90
|
+
"mqtt://cloud.farm.com/alarms/warning",
|
|
91
|
+
PRIORITY_HIGH
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (msgId) {
|
|
95
|
+
Serial.printf("⚠️ WARNING O2: %.2f mg/L - QUEUED #%u\n", o2Level, msgId);
|
|
96
|
+
} else {
|
|
97
|
+
Serial.printf("⚠️ WARNING O2: %.2f mg/L - QUEUE FULL\n", o2Level);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
Serial.printf("⚠️ WARNING O2: %.2f mg/L - SENT\n", o2Level);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Send normal telemetry
|
|
105
|
+
void sendNormalTelemetry(float o2Level) {
|
|
106
|
+
String payload = String("{\"sensor\":\"O2\",\"value\":") + String(o2Level, 2)
|
|
107
|
+
+ ",\"nodeId\":" + mesh.getNodeId() + "}";
|
|
108
|
+
|
|
109
|
+
if (offlineMode) {
|
|
110
|
+
// Low priority - queue only if space available
|
|
111
|
+
uint32_t msgId = mesh.queueMessage(
|
|
112
|
+
payload,
|
|
113
|
+
"mqtt://cloud.farm.com/telemetry",
|
|
114
|
+
PRIORITY_LOW
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
if (msgId) {
|
|
118
|
+
Serial.printf("📊 Telemetry: %.2f mg/L - queued #%u\n", o2Level, msgId);
|
|
119
|
+
} else {
|
|
120
|
+
Serial.printf("📊 Telemetry: %.2f mg/L - dropped (queue full)\n", o2Level);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
Serial.printf("📊 Telemetry: %.2f mg/L\n", o2Level);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check O2 sensor and send appropriate message
|
|
128
|
+
void checkO2Sensor() {
|
|
129
|
+
float o2Level = readDissolvedOxygenSensor();
|
|
130
|
+
|
|
131
|
+
if (o2Level < CRITICAL_O2_THRESHOLD) {
|
|
132
|
+
sendCriticalAlarm(o2Level);
|
|
133
|
+
} else if (o2Level < WARNING_O2_THRESHOLD) {
|
|
134
|
+
sendWarningAlarm(o2Level);
|
|
135
|
+
} else {
|
|
136
|
+
sendNormalTelemetry(o2Level);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Bridge status callback - Internet connectivity changed
|
|
141
|
+
void bridgeStatusCallback(uint32_t bridgeNodeId, bool hasInternet) {
|
|
142
|
+
if (!hasInternet) {
|
|
143
|
+
offlineMode = true;
|
|
144
|
+
Serial.println("\n⚠️ OFFLINE MODE ACTIVATED");
|
|
145
|
+
Serial.printf(" Bridge node %u lost Internet\n", bridgeNodeId);
|
|
146
|
+
Serial.printf(" Queue size: %u messages\n", mesh.getQueuedMessageCount());
|
|
147
|
+
|
|
148
|
+
uint32_t critical = mesh.getQueuedMessageCount(PRIORITY_CRITICAL);
|
|
149
|
+
if (critical > 0) {
|
|
150
|
+
Serial.printf(" ⚠️ %u CRITICAL messages queued!\n", critical);
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
offlineMode = false;
|
|
154
|
+
Serial.println("\n✅ ONLINE MODE - Internet restored");
|
|
155
|
+
Serial.printf(" Bridge node %u has Internet\n", bridgeNodeId);
|
|
156
|
+
|
|
157
|
+
// Flush queued messages
|
|
158
|
+
uint32_t queuedCount = mesh.getQueuedMessageCount();
|
|
159
|
+
if (queuedCount > 0) {
|
|
160
|
+
Serial.printf(" Flushing %u queued messages...\n", queuedCount);
|
|
161
|
+
|
|
162
|
+
auto messages = mesh.flushMessageQueue();
|
|
163
|
+
for (auto& msg : messages) {
|
|
164
|
+
// In real application, send via MQTT or HTTP
|
|
165
|
+
Serial.printf(" Sending queued message #%u (priority=%d, attempts=%u)\n",
|
|
166
|
+
msg.id, msg.priority, msg.attempts);
|
|
167
|
+
|
|
168
|
+
// Simulate sending (in real app, check if send succeeded)
|
|
169
|
+
bool sent = true; // Replace with: mqttClient.publish(...)
|
|
170
|
+
|
|
171
|
+
if (sent) {
|
|
172
|
+
mesh.removeQueuedMessage(msg.id);
|
|
173
|
+
} else {
|
|
174
|
+
// Increment attempt counter
|
|
175
|
+
mesh.incrementQueuedMessageAttempts(msg.id);
|
|
176
|
+
|
|
177
|
+
// Remove if too many attempts
|
|
178
|
+
if (msg.attempts >= 3) {
|
|
179
|
+
Serial.printf(" ❌ Message #%u failed after 3 attempts, removing\n", msg.id);
|
|
180
|
+
mesh.removeQueuedMessage(msg.id);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
Serial.printf(" ✅ Queue flushed (%u messages sent)\n", queuedCount);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Queue state callback - Monitor queue health
|
|
191
|
+
void queueStateCallback(QueueState state, uint32_t messageCount) {
|
|
192
|
+
switch (state) {
|
|
193
|
+
case QUEUE_EMPTY:
|
|
194
|
+
Serial.println("ℹ️ Queue empty");
|
|
195
|
+
break;
|
|
196
|
+
case QUEUE_NORMAL:
|
|
197
|
+
Serial.printf("ℹ️ Queue normal (%u messages)\n", messageCount);
|
|
198
|
+
break;
|
|
199
|
+
case QUEUE_75_PERCENT:
|
|
200
|
+
Serial.printf("⚠️ Queue 75%% full (%u messages)\n", messageCount);
|
|
201
|
+
break;
|
|
202
|
+
case QUEUE_FULL:
|
|
203
|
+
Serial.printf("🚨 Queue FULL (%u messages) - dropping LOW priority\n", messageCount);
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
void setup() {
|
|
209
|
+
Serial.begin(115200);
|
|
210
|
+
Serial.println("\n\n=== Fish Farm O2 Monitoring with Message Queue ===\n");
|
|
211
|
+
|
|
212
|
+
// Initialize mesh
|
|
213
|
+
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
|
|
214
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
215
|
+
|
|
216
|
+
// For bridge node: set router credentials
|
|
217
|
+
// Uncomment if this is the bridge node
|
|
218
|
+
// mesh.stationManual(ROUTER_SSID, ROUTER_PASSWORD);
|
|
219
|
+
// mesh.setHostname("FishFarmBridge");
|
|
220
|
+
|
|
221
|
+
// Enable message queue
|
|
222
|
+
mesh.enableMessageQueue(true, MAX_QUEUE_SIZE);
|
|
223
|
+
Serial.printf("Message queue enabled (capacity: %u)\n", MAX_QUEUE_SIZE);
|
|
224
|
+
|
|
225
|
+
// Set callbacks
|
|
226
|
+
mesh.onBridgeStatusChanged(&bridgeStatusCallback);
|
|
227
|
+
mesh.onQueueStateChanged(&queueStateCallback);
|
|
228
|
+
|
|
229
|
+
Serial.println("Setup complete. Monitoring O2 levels...\n");
|
|
230
|
+
|
|
231
|
+
// Initialize random for sensor simulation
|
|
232
|
+
randomSeed(analogRead(0));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
void loop() {
|
|
236
|
+
mesh.update();
|
|
237
|
+
|
|
238
|
+
// Check O2 sensor every 10 seconds
|
|
239
|
+
if (millis() - lastO2Check > 10000) {
|
|
240
|
+
lastO2Check = millis();
|
|
241
|
+
checkO2Sensor();
|
|
242
|
+
|
|
243
|
+
// Print queue status
|
|
244
|
+
uint32_t queueSize = mesh.getQueuedMessageCount();
|
|
245
|
+
if (queueSize > 0) {
|
|
246
|
+
Serial.printf(" [Queue: %u messages", queueSize);
|
|
247
|
+
|
|
248
|
+
uint32_t critical = mesh.getQueuedMessageCount(PRIORITY_CRITICAL);
|
|
249
|
+
if (critical > 0) {
|
|
250
|
+
Serial.printf(" (%u CRITICAL)", critical);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
Serial.println("]");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Prune old messages every hour
|
|
258
|
+
if (millis() - lastQueuePrune > 3600000) {
|
|
259
|
+
lastQueuePrune = millis();
|
|
260
|
+
uint32_t pruned = mesh.pruneQueue(QUEUE_PRUNE_AGE);
|
|
261
|
+
if (pruned > 0) {
|
|
262
|
+
Serial.printf("ℹ️ Pruned %u old messages from queue\n", pruned);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# RTC Integration Example
|
|
2
|
+
|
|
3
|
+
This example demonstrates how to integrate Real-Time Clock (RTC) modules with painlessMesh for accurate offline timekeeping.
|
|
4
|
+
|
|
5
|
+
## Problem Statement
|
|
6
|
+
|
|
7
|
+
When Internet or bridge connectivity is unavailable, mesh nodes lose access to accurate time synchronization. This is problematic for applications that require valid timestamps, such as:
|
|
8
|
+
|
|
9
|
+
- Fish farm alarm systems (regulatory compliance)
|
|
10
|
+
- Environmental monitoring with offline data logging
|
|
11
|
+
- Industrial IoT with intermittent connectivity
|
|
12
|
+
- Remote sensor networks
|
|
13
|
+
|
|
14
|
+
## Solution
|
|
15
|
+
|
|
16
|
+
painlessMesh RTC integration provides:
|
|
17
|
+
|
|
18
|
+
✅ **Accurate timestamps during offline periods**
|
|
19
|
+
✅ **Automatic NTP sync when Internet available**
|
|
20
|
+
✅ **Graceful fallback to mesh time**
|
|
21
|
+
✅ **Support for common RTC modules**
|
|
22
|
+
|
|
23
|
+
## Supported RTC Modules
|
|
24
|
+
|
|
25
|
+
- **DS3231** - High accuracy I2C RTC with temperature compensation
|
|
26
|
+
- **DS1307** - Basic I2C RTC
|
|
27
|
+
- **PCF8523** - Low power I2C RTC
|
|
28
|
+
- **PCF8563** - Ultra-low power I2C RTC
|
|
29
|
+
- **ESP32 Internal RTC** - Built-in ESP32 RTC (requires external battery backup)
|
|
30
|
+
|
|
31
|
+
## Hardware Requirements
|
|
32
|
+
|
|
33
|
+
### For DS3231 (used in this example)
|
|
34
|
+
- ESP32 or ESP8266 board
|
|
35
|
+
- DS3231 RTC module
|
|
36
|
+
- Connections:
|
|
37
|
+
- SDA → GPIO 21 (ESP32) or GPIO 4 (ESP8266)
|
|
38
|
+
- SCL → GPIO 22 (ESP32) or GPIO 5 (ESP8266)
|
|
39
|
+
- VCC → 3.3V
|
|
40
|
+
- GND → GND
|
|
41
|
+
|
|
42
|
+
### Library Requirements
|
|
43
|
+
```
|
|
44
|
+
painlessMesh
|
|
45
|
+
RTClib (Adafruit)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Install via Arduino Library Manager:
|
|
49
|
+
- Tools → Manage Libraries
|
|
50
|
+
- Search for "RTClib" by Adafruit
|
|
51
|
+
- Click Install
|
|
52
|
+
|
|
53
|
+
## How It Works
|
|
54
|
+
|
|
55
|
+
### 1. RTC Interface Implementation
|
|
56
|
+
|
|
57
|
+
You implement the `RTCInterface` for your specific RTC hardware:
|
|
58
|
+
|
|
59
|
+
```cpp
|
|
60
|
+
class DS3231Interface : public painlessmesh::rtc::RTCInterface {
|
|
61
|
+
// Implement begin(), isAvailable(), getUnixTime(), setUnixTime(), getType()
|
|
62
|
+
};
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. Enable RTC
|
|
66
|
+
|
|
67
|
+
```cpp
|
|
68
|
+
DS3231Interface rtcInterface(&rtc);
|
|
69
|
+
mesh.enableRTC(&rtcInterface);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3. Automatic Time Management
|
|
73
|
+
|
|
74
|
+
```cpp
|
|
75
|
+
mesh.onBridgeStatusChanged([](uint32_t bridgeNodeId, bool hasInternet) {
|
|
76
|
+
if (hasInternet) {
|
|
77
|
+
// Sync RTC from NTP when Internet available
|
|
78
|
+
uint32_t ntpTime = getNTPTime();
|
|
79
|
+
mesh.syncRTCFromNTP(ntpTime);
|
|
80
|
+
}
|
|
81
|
+
// Offline: RTC maintains accurate time
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Get Accurate Time
|
|
86
|
+
|
|
87
|
+
```cpp
|
|
88
|
+
// Prefers RTC, falls back to mesh time
|
|
89
|
+
uint32_t timestamp = mesh.getAccurateTime();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API Reference
|
|
93
|
+
|
|
94
|
+
### Mesh Methods
|
|
95
|
+
|
|
96
|
+
#### `bool enableRTC(rtc::RTCInterface* rtcInterface)`
|
|
97
|
+
Enable RTC integration with a user-provided interface.
|
|
98
|
+
|
|
99
|
+
**Returns:** `true` if RTC initialized successfully, `false` otherwise
|
|
100
|
+
|
|
101
|
+
#### `void disableRTC()`
|
|
102
|
+
Disable RTC integration.
|
|
103
|
+
|
|
104
|
+
#### `bool syncRTCFromNTP(uint32_t ntpTimestamp)`
|
|
105
|
+
Sync RTC from NTP/Internet time source.
|
|
106
|
+
|
|
107
|
+
**Parameters:**
|
|
108
|
+
- `ntpTimestamp` - Unix timestamp from NTP
|
|
109
|
+
|
|
110
|
+
**Returns:** `true` if sync successful, `false` otherwise
|
|
111
|
+
|
|
112
|
+
#### `uint32_t getAccurateTime()`
|
|
113
|
+
Get accurate time with RTC fallback.
|
|
114
|
+
|
|
115
|
+
**Returns:** Unix timestamp (seconds) from RTC, or mesh time (microseconds) if RTC unavailable
|
|
116
|
+
|
|
117
|
+
#### `bool hasRTC()`
|
|
118
|
+
Check if RTC is enabled and available.
|
|
119
|
+
|
|
120
|
+
**Returns:** `true` if RTC can be used, `false` otherwise
|
|
121
|
+
|
|
122
|
+
#### `rtc::RTCType getRTCType()`
|
|
123
|
+
Get RTC module type.
|
|
124
|
+
|
|
125
|
+
**Returns:** `RTCType` enum value
|
|
126
|
+
|
|
127
|
+
#### `uint32_t getTimeSinceRTCSync()`
|
|
128
|
+
Get time since last RTC sync.
|
|
129
|
+
|
|
130
|
+
**Returns:** Milliseconds since last sync, or 0 if never synced
|
|
131
|
+
|
|
132
|
+
#### `void onRTCSyncComplete(rtcSyncCompleteCallback_t callback)`
|
|
133
|
+
Set callback for RTC sync completion.
|
|
134
|
+
|
|
135
|
+
### RTC Types
|
|
136
|
+
|
|
137
|
+
```cpp
|
|
138
|
+
enum RTCType {
|
|
139
|
+
RTC_NONE = 0,
|
|
140
|
+
RTC_DS3231 = 1,
|
|
141
|
+
RTC_DS1307 = 2,
|
|
142
|
+
RTC_PCF8523 = 3,
|
|
143
|
+
RTC_PCF8563 = 4,
|
|
144
|
+
RTC_ESP32_INTERNAL = 5
|
|
145
|
+
};
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Usage Example
|
|
149
|
+
|
|
150
|
+
```cpp
|
|
151
|
+
#include "painlessMesh.h"
|
|
152
|
+
#include <RTClib.h>
|
|
153
|
+
|
|
154
|
+
painlessMesh mesh;
|
|
155
|
+
RTC_DS3231 rtc;
|
|
156
|
+
DS3231Interface rtcInterface(&rtc);
|
|
157
|
+
|
|
158
|
+
void setup() {
|
|
159
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
160
|
+
mesh.onBridgeStatusChanged(&bridgeStatusCallback);
|
|
161
|
+
mesh.onRTCSyncComplete(&rtcSyncCompleteCallback);
|
|
162
|
+
|
|
163
|
+
if (mesh.enableRTC(&rtcInterface)) {
|
|
164
|
+
Serial.println("RTC enabled!");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
void bridgeStatusCallback(uint32_t bridgeNodeId, bool hasInternet) {
|
|
169
|
+
if (hasInternet && mesh.hasRTC()) {
|
|
170
|
+
uint32_t ntpTime = getNTPTime(); // Your implementation
|
|
171
|
+
mesh.syncRTCFromNTP(ntpTime);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
void rtcSyncCompleteCallback(uint32_t timestamp) {
|
|
176
|
+
Serial.printf("RTC synced to: %u\n", timestamp);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Testing
|
|
181
|
+
|
|
182
|
+
### Verify RTC Time Persistence
|
|
183
|
+
1. Upload sketch to node
|
|
184
|
+
2. Disconnect Internet/bridge
|
|
185
|
+
3. Power cycle the node
|
|
186
|
+
4. Verify timestamps remain accurate (±2 seconds)
|
|
187
|
+
|
|
188
|
+
### Verify NTP Sync
|
|
189
|
+
1. Connect bridge to Internet
|
|
190
|
+
2. Monitor serial output for sync messages
|
|
191
|
+
3. Verify RTC time updated correctly
|
|
192
|
+
|
|
193
|
+
### Verify Offline Operation
|
|
194
|
+
1. Disconnect Internet
|
|
195
|
+
2. Wait several hours
|
|
196
|
+
3. Verify timestamps continue to increment accurately
|
|
197
|
+
|
|
198
|
+
## Troubleshooting
|
|
199
|
+
|
|
200
|
+
### "Couldn't find RTC"
|
|
201
|
+
- Check I2C connections (SDA/SCL)
|
|
202
|
+
- Verify RTC module has power
|
|
203
|
+
- Try I2C scanner sketch to detect device
|
|
204
|
+
|
|
205
|
+
### "RTC lost power"
|
|
206
|
+
- RTC battery needs replacement
|
|
207
|
+
- Time will be synced from NTP when available
|
|
208
|
+
|
|
209
|
+
### Time Not Syncing
|
|
210
|
+
- Verify bridge has Internet connectivity
|
|
211
|
+
- Check NTP server is accessible
|
|
212
|
+
- Ensure `syncRTCFromNTP()` called with valid timestamp
|
|
213
|
+
|
|
214
|
+
## Best Practices
|
|
215
|
+
|
|
216
|
+
1. **Always check RTC availability** before relying on timestamps
|
|
217
|
+
2. **Sync regularly** when Internet available (recommended: every 24 hours)
|
|
218
|
+
3. **Monitor battery** on RTC modules for continuous operation
|
|
219
|
+
4. **Implement fallback** to mesh time if RTC fails
|
|
220
|
+
5. **Log sync events** for debugging and maintenance
|
|
221
|
+
|
|
222
|
+
## Regulatory Compliance
|
|
223
|
+
|
|
224
|
+
For systems requiring timestamp accuracy (e.g., fish farm alarms):
|
|
225
|
+
|
|
226
|
+
- RTC provides ±2 second accuracy during offline periods
|
|
227
|
+
- Timestamps remain valid for alarm reporting
|
|
228
|
+
- Sync logs provide audit trail
|
|
229
|
+
- Battery backup ensures continuous operation
|
|
230
|
+
|
|
231
|
+
## See Also
|
|
232
|
+
|
|
233
|
+
- [Bridge Status Feature](../../BRIDGE_STATUS_FEATURE.md) - Internet connectivity detection
|
|
234
|
+
- [Bridge Architecture](../../BRIDGE_ARCHITECTURE_IMPLEMENTATION.md) - Mesh bridge setup
|
|
235
|
+
- [painlessMesh Documentation](https://gitlab.com/painlessMesh/painlessMesh) - Main library docs
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
//************************************************************
|
|
2
|
+
// RTC Integration Example
|
|
3
|
+
//
|
|
4
|
+
// This example demonstrates how to use RTC (Real-Time Clock)
|
|
5
|
+
// modules with painlessMesh for accurate offline timekeeping.
|
|
6
|
+
//
|
|
7
|
+
// When Internet/bridge is unavailable, nodes can still maintain
|
|
8
|
+
// accurate timestamps using a local RTC module.
|
|
9
|
+
//
|
|
10
|
+
// Supported RTC modules:
|
|
11
|
+
// - DS3231, DS1307 (I2C)
|
|
12
|
+
// - PCF8523, PCF8563 (I2C)
|
|
13
|
+
// - ESP32 internal RTC
|
|
14
|
+
//
|
|
15
|
+
// This example uses DS3231 with the RTClib library.
|
|
16
|
+
// Install: https://github.com/adafruit/RTClib
|
|
17
|
+
//************************************************************
|
|
18
|
+
|
|
19
|
+
#include "painlessMesh.h"
|
|
20
|
+
#include <RTClib.h> // Adafruit RTClib for DS3231
|
|
21
|
+
|
|
22
|
+
#define MESH_PREFIX "AlteriomMesh"
|
|
23
|
+
#define MESH_PASSWORD "somethingSneaky"
|
|
24
|
+
#define MESH_PORT 5555
|
|
25
|
+
|
|
26
|
+
Scheduler userScheduler;
|
|
27
|
+
painlessMesh mesh;
|
|
28
|
+
|
|
29
|
+
// DS3231 RTC instance
|
|
30
|
+
RTC_DS3231 rtc;
|
|
31
|
+
|
|
32
|
+
// RTC Interface implementation for DS3231
|
|
33
|
+
class DS3231Interface : public painlessmesh::rtc::RTCInterface {
|
|
34
|
+
private:
|
|
35
|
+
RTC_DS3231* rtcDevice;
|
|
36
|
+
|
|
37
|
+
public:
|
|
38
|
+
DS3231Interface(RTC_DS3231* device) : rtcDevice(device) {}
|
|
39
|
+
|
|
40
|
+
bool begin() override {
|
|
41
|
+
if (!rtcDevice->begin()) {
|
|
42
|
+
Serial.println("Couldn't find RTC");
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check if RTC lost power and needs time set
|
|
47
|
+
if (rtcDevice->lostPower()) {
|
|
48
|
+
Serial.println("RTC lost power, time needs to be set!");
|
|
49
|
+
// Note: Time will be set via NTP when Internet available
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
bool isAvailable() override {
|
|
56
|
+
return !rtcDevice->lostPower();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
uint32_t getUnixTime() override {
|
|
60
|
+
DateTime now = rtcDevice->now();
|
|
61
|
+
return now.unixtime();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
bool setUnixTime(uint32_t timestamp) override {
|
|
65
|
+
rtcDevice->adjust(DateTime(timestamp));
|
|
66
|
+
Serial.printf("RTC time set to: %u\n", timestamp);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
painlessmesh::rtc::RTCType getType() override {
|
|
71
|
+
return painlessmesh::rtc::RTC_DS3231;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Create RTC interface instance
|
|
76
|
+
DS3231Interface rtcInterface(&rtc);
|
|
77
|
+
|
|
78
|
+
// Task to send timestamped sensor data
|
|
79
|
+
void sendSensorData();
|
|
80
|
+
Task taskSendData(30000, TASK_FOREVER, &sendSensorData);
|
|
81
|
+
|
|
82
|
+
// Flag to track if we need NTP sync
|
|
83
|
+
bool needsNTPSync = true;
|
|
84
|
+
|
|
85
|
+
void setup() {
|
|
86
|
+
Serial.begin(115200);
|
|
87
|
+
|
|
88
|
+
// Initialize mesh
|
|
89
|
+
mesh.setDebugMsgTypes(ERROR | STARTUP | CONNECTION);
|
|
90
|
+
mesh.init(MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT);
|
|
91
|
+
|
|
92
|
+
// Set up mesh callbacks
|
|
93
|
+
mesh.onReceive(&receivedCallback);
|
|
94
|
+
mesh.onNewConnection(&newConnectionCallback);
|
|
95
|
+
mesh.onChangedConnections(&changedConnectionCallback);
|
|
96
|
+
mesh.onBridgeStatusChanged(&bridgeStatusCallback);
|
|
97
|
+
mesh.onRTCSyncComplete(&rtcSyncCompleteCallback);
|
|
98
|
+
|
|
99
|
+
// Enable RTC
|
|
100
|
+
if (mesh.enableRTC(&rtcInterface)) {
|
|
101
|
+
Serial.println("RTC enabled successfully!");
|
|
102
|
+
Serial.printf("RTC Type: %d\n", mesh.getRTCType());
|
|
103
|
+
|
|
104
|
+
// Print current RTC time
|
|
105
|
+
uint32_t rtcTime = mesh.getAccurateTime();
|
|
106
|
+
Serial.printf("Current RTC time: %u\n", rtcTime);
|
|
107
|
+
} else {
|
|
108
|
+
Serial.println("Failed to enable RTC - will use mesh time only");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Add sensor data task
|
|
112
|
+
userScheduler.addTask(taskSendData);
|
|
113
|
+
taskSendData.enable();
|
|
114
|
+
|
|
115
|
+
Serial.println("Setup complete");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
void loop() {
|
|
119
|
+
mesh.update();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
void sendSensorData() {
|
|
123
|
+
// Get accurate timestamp (RTC if available, mesh time otherwise)
|
|
124
|
+
uint32_t timestamp = mesh.getAccurateTime();
|
|
125
|
+
|
|
126
|
+
// Simulate sensor reading
|
|
127
|
+
float temperature = 25.5 + random(-50, 50) / 10.0;
|
|
128
|
+
float humidity = 60.0 + random(-100, 100) / 10.0;
|
|
129
|
+
|
|
130
|
+
// Create message with timestamp
|
|
131
|
+
String msg = "{\"type\":\"sensor\",";
|
|
132
|
+
msg += "\"nodeId\":" + String(mesh.getNodeId()) + ",";
|
|
133
|
+
msg += "\"timestamp\":" + String(timestamp) + ",";
|
|
134
|
+
msg += "\"temperature\":" + String(temperature, 1) + ",";
|
|
135
|
+
msg += "\"humidity\":" + String(humidity, 1) + ",";
|
|
136
|
+
msg += "\"hasRTC\":" + String(mesh.hasRTC() ? "true" : "false") + ",";
|
|
137
|
+
msg += "\"rtcType\":" + String(mesh.getRTCType());
|
|
138
|
+
msg += "}";
|
|
139
|
+
|
|
140
|
+
mesh.sendBroadcast(msg);
|
|
141
|
+
|
|
142
|
+
Serial.printf("Sent sensor data with timestamp: %u\n", timestamp);
|
|
143
|
+
Serial.printf("Time since last RTC sync: %u ms\n", mesh.getTimeSinceRTCSync());
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
void receivedCallback(uint32_t from, String &msg) {
|
|
147
|
+
Serial.printf("Received from %u: %s\n", from, msg.c_str());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
void newConnectionCallback(uint32_t nodeId) {
|
|
151
|
+
Serial.printf("New Connection: %u\n", nodeId);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
void changedConnectionCallback() {
|
|
155
|
+
Serial.printf("Changed connections\n");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
void bridgeStatusCallback(uint32_t bridgeNodeId, bool hasInternet) {
|
|
159
|
+
Serial.printf("Bridge %u - Internet: %s\n",
|
|
160
|
+
bridgeNodeId,
|
|
161
|
+
hasInternet ? "Connected" : "Disconnected");
|
|
162
|
+
|
|
163
|
+
if (hasInternet && needsNTPSync && mesh.hasRTC()) {
|
|
164
|
+
// Internet is available and we need to sync RTC
|
|
165
|
+
// In a real application, you would get NTP time here
|
|
166
|
+
// For this example, we'll use a placeholder
|
|
167
|
+
Serial.println("Internet available - would sync RTC from NTP now");
|
|
168
|
+
|
|
169
|
+
// Example: Get NTP time (you need to implement this)
|
|
170
|
+
// uint32_t ntpTime = getNTPTime();
|
|
171
|
+
// if (mesh.syncRTCFromNTP(ntpTime)) {
|
|
172
|
+
// Serial.println("RTC synced successfully!");
|
|
173
|
+
// needsNTPSync = false;
|
|
174
|
+
// }
|
|
175
|
+
|
|
176
|
+
// For demonstration purposes, let's sync to a known time
|
|
177
|
+
uint32_t demoTime = 1704067200; // 2024-01-01 00:00:00 UTC
|
|
178
|
+
if (mesh.syncRTCFromNTP(demoTime)) {
|
|
179
|
+
Serial.println("RTC synced to demo time!");
|
|
180
|
+
needsNTPSync = false;
|
|
181
|
+
}
|
|
182
|
+
} else if (!hasInternet) {
|
|
183
|
+
// Internet lost - RTC will keep accurate time offline
|
|
184
|
+
Serial.println("Internet offline - using RTC for timestamps");
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
void rtcSyncCompleteCallback(uint32_t timestamp) {
|
|
189
|
+
Serial.printf("RTC sync completed! New time: %u\n", timestamp);
|
|
190
|
+
|
|
191
|
+
// Convert timestamp to human-readable format
|
|
192
|
+
DateTime dt(timestamp);
|
|
193
|
+
Serial.printf("Synced to: %d-%02d-%02d %02d:%02d:%02d\n",
|
|
194
|
+
dt.year(), dt.month(), dt.day(),
|
|
195
|
+
dt.hour(), dt.minute(), dt.second());
|
|
196
|
+
}
|
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=AlteriomPainlessMesh
|
|
2
|
-
version=1.
|
|
2
|
+
version=1.8.1
|
|
3
3
|
author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh,Alteriom
|
|
4
4
|
maintainer=Alteriom
|
|
5
5
|
sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices with Alteriom extensions
|