@alteriom/painlessmesh 1.7.7 → 1.7.9
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 +115 -1
- package/README.md +21 -11
- package/RELEASE_GUIDE.md +57 -8
- package/docs/API_DESIGN_GUIDELINES.md +414 -0
- package/docs/BOOLEAN_NAMING_CONVENTION.md +235 -0
- package/docs/README.md +2 -1
- package/docs/RELEASE_AGENT_SUMMARY.md +386 -0
- package/docs/alteriom/overview.md +23 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +1 -1
- package/docs/troubleshooting/ESP32_C6_COMPATIBILITY.md +157 -0
- package/docs/troubleshooting/common-issues.md +28 -0
- package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +21 -3
- package/examples/alteriom/README.md +13 -1
- package/examples/alteriom/alteriom_sensor_package.hpp +377 -3
- package/examples/alteriom/platformio.ini +1 -1
- package/examples/alteriomImproved/platformio.ini +1 -1
- package/examples/alteriomMetricsHealth/metrics_health_node.ino +18 -7
- package/examples/alteriomMetricsHealth/platformio.ini +1 -1
- package/examples/alteriomPhase1/platformio.ini +1 -1
- package/examples/alteriomPhase2/platformio.ini +1 -1
- package/examples/alteriomSensorNode/platformio.ini +1 -1
- package/examples/basic/platformio.ini +1 -1
- package/examples/bridge/alteriom_sensor_package.hpp +1170 -0
- package/examples/bridge/bridge.ino +2 -2
- package/examples/bridge/enhanced_mqtt_bridge.hpp +1 -1
- package/examples/bridge/mqtt_command_bridge.hpp +2 -2
- package/examples/bridge/platformio.ini +2 -1
- package/examples/echoNode/platformio.ini +1 -1
- package/examples/logClient/platformio.ini +1 -1
- package/examples/logServer/platformio.ini +1 -1
- package/examples/mqttStatusBridge/platformio.ini +1 -1
- package/examples/namedMesh/platformio.ini +1 -1
- package/examples/otaReceiver/platformio.ini +1 -1
- package/examples/startHere/platformio.ini +1 -1
- package/examples/webServer/platformio.ini +1 -1
- package/library.json +93 -53
- package/library.properties +1 -1
- package/package.json +2 -2
- package/src/arduino/wifi.hpp +9 -0
- package/src/painlessMeshSTA.cpp +5 -0
|
@@ -3,6 +3,133 @@
|
|
|
3
3
|
|
|
4
4
|
#include "painlessmesh/plugin.hpp"
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @file alteriom_sensor_package.hpp
|
|
8
|
+
* @brief Alteriom custom package definitions for painlessMesh
|
|
9
|
+
*
|
|
10
|
+
* TIME FIELD NAMING CONVENTION
|
|
11
|
+
* ============================
|
|
12
|
+
*
|
|
13
|
+
* For consistency and developer convenience, all time-based configuration
|
|
14
|
+
* fields in Alteriom packages follow a standardized naming convention:
|
|
15
|
+
*
|
|
16
|
+
* INTERNAL STORAGE:
|
|
17
|
+
* - Always use milliseconds (uint32_t)
|
|
18
|
+
* - Use descriptive field names WITHOUT unit suffixes
|
|
19
|
+
* Examples: sensorReadInterval, transmissionInterval
|
|
20
|
+
*
|
|
21
|
+
* JSON SERIALIZATION:
|
|
22
|
+
* - ALWAYS provide BOTH millisecond and second variants:
|
|
23
|
+
* - {fieldname}_ms : The value in milliseconds (uint32_t)
|
|
24
|
+
* - {fieldname}_s : The value in seconds (uint32_t, calculated as ms/1000)
|
|
25
|
+
*
|
|
26
|
+
* JSON DESERIALIZATION:
|
|
27
|
+
* - Read from the _ms variant (milliseconds are the source of truth)
|
|
28
|
+
* - The _s variant is provided for consumer convenience but not used for input
|
|
29
|
+
*
|
|
30
|
+
* EXAMPLE:
|
|
31
|
+
* ```cpp
|
|
32
|
+
* // C++ field declaration
|
|
33
|
+
* uint32_t sensorReadInterval = 0; // Internal storage in milliseconds
|
|
34
|
+
*
|
|
35
|
+
* // JSON serialization (in addTo method)
|
|
36
|
+
* sensors["read_interval_ms"] = sensorReadInterval; // 30000
|
|
37
|
+
* sensors["read_interval_s"] = sensorReadInterval / 1000; // 30
|
|
38
|
+
*
|
|
39
|
+
* // JSON deserialization (in constructor)
|
|
40
|
+
* sensorReadInterval = sensors["read_interval_ms"] | 0;
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* BENEFITS:
|
|
44
|
+
* - Consumer Convenience: No mental overhead for unit conversion
|
|
45
|
+
* - Flexibility: Consumers choose the unit appropriate for their context
|
|
46
|
+
* - Self-Documenting: Field names clearly indicate available units
|
|
47
|
+
* - Precision: Millisecond precision preserved, seconds for readability
|
|
48
|
+
* - Consistency: Predictable pattern across all time-based fields
|
|
49
|
+
*
|
|
50
|
+
* WHEN TO APPLY:
|
|
51
|
+
* - Use this convention for ALL time-based configuration/interval fields
|
|
52
|
+
* - Applies to fields typically >= 1000ms (1 second)
|
|
53
|
+
* - Examples: intervals, timeouts, durations, delays
|
|
54
|
+
* - Does NOT apply to timestamps (which should remain in seconds as per Unix
|
|
55
|
+
* convention)
|
|
56
|
+
*
|
|
57
|
+
*
|
|
58
|
+
* JSON STRUCTURE NESTING GUIDELINES
|
|
59
|
+
* ==================================
|
|
60
|
+
*
|
|
61
|
+
* Alteriom packages follow consistent patterns for organizing configuration
|
|
62
|
+
* data in JSON structures. This ensures maintainability and predictability
|
|
63
|
+
* across the API.
|
|
64
|
+
*
|
|
65
|
+
* IMPORTANT: As of PR #37, ALL configuration sections always serialize with
|
|
66
|
+
* default values, providing predictable JSON structure. Consumers no longer
|
|
67
|
+
* need to check for key existence. Default values (0, false, "") indicate "not
|
|
68
|
+
* configured" state.
|
|
69
|
+
*
|
|
70
|
+
* See docs/API_DESIGN_GUIDELINES.md for comprehensive documentation.
|
|
71
|
+
*
|
|
72
|
+
* QUICK REFERENCE:
|
|
73
|
+
*
|
|
74
|
+
* Use FLAT structure (simple key-value pairs) when:
|
|
75
|
+
* - Section has < 4 total fields
|
|
76
|
+
* - No clear logical subsystems
|
|
77
|
+
* - Simple value types
|
|
78
|
+
*
|
|
79
|
+
* Use NESTED structure (grouped subsections) when:
|
|
80
|
+
* - 3+ fields belong to same logical subsystem
|
|
81
|
+
* - Clear semantic grouping exists
|
|
82
|
+
* - Future extensibility anticipated
|
|
83
|
+
* - Subsystem has distinct meaning
|
|
84
|
+
*
|
|
85
|
+
* CURRENT STRUCTURE PATTERNS:
|
|
86
|
+
*
|
|
87
|
+
* Flat Sections:
|
|
88
|
+
* - display_config: enabled, brightness, timeout (3 fields, no subsystems)
|
|
89
|
+
* - power_config: deep_sleep_enabled, deep_sleep_interval, battery_percent (3
|
|
90
|
+
* fields)
|
|
91
|
+
* - mqtt_retry: retry settings and backoff parameters (9 fields, cohesive
|
|
92
|
+
* purpose)
|
|
93
|
+
* - ota: enabled, server, port (3 fields)
|
|
94
|
+
* - encoding: compression and format settings (3 fields)
|
|
95
|
+
*
|
|
96
|
+
* Nested Sections:
|
|
97
|
+
* - sensors.calibration: temperature_offset, humidity_offset, pressure_offset
|
|
98
|
+
* (Rationale: Calibration is distinct subsystem, optional, semantically
|
|
99
|
+
* separate)
|
|
100
|
+
* - organization: organizationId, customerId, deviceGroup, device_name, etc.
|
|
101
|
+
* (Rationale: Optional metadata subsystem, may not be present on all devices)
|
|
102
|
+
*
|
|
103
|
+
* EXAMPLES:
|
|
104
|
+
*
|
|
105
|
+
* Flat structure:
|
|
106
|
+
* ```json
|
|
107
|
+
* "display_config": {
|
|
108
|
+
* "enabled": true,
|
|
109
|
+
* "brightness": 128,
|
|
110
|
+
* "timeout_ms": 30000,
|
|
111
|
+
* "timeout_s": 30
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* Nested structure:
|
|
116
|
+
* ```json
|
|
117
|
+
* "sensors": {
|
|
118
|
+
* "read_interval_ms": 30000,
|
|
119
|
+
* "read_interval_s": 30,
|
|
120
|
+
* "calibration": {
|
|
121
|
+
* "temperature_offset": 0.5,
|
|
122
|
+
* "humidity_offset": -2.0,
|
|
123
|
+
* "pressure_offset": 0.0
|
|
124
|
+
* }
|
|
125
|
+
* }
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* DECISION RULE:
|
|
129
|
+
* When in doubt, prefer flat structures. Nesting should provide clear
|
|
130
|
+
* organizational or extensibility benefits to justify the added complexity.
|
|
131
|
+
*/
|
|
132
|
+
|
|
6
133
|
namespace alteriom {
|
|
7
134
|
|
|
8
135
|
/**
|
|
@@ -26,6 +153,9 @@ class SensorPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
26
153
|
// Battery level percentage
|
|
27
154
|
uint8_t batteryLevel = 0;
|
|
28
155
|
|
|
156
|
+
// MQTT Schema v0.7.3+ message_type for faster classification
|
|
157
|
+
uint16_t messageType = 200; // SENSOR_DATA
|
|
158
|
+
|
|
29
159
|
// Type ID 200 for Alteriom sensors
|
|
30
160
|
SensorPackage() : BroadcastPackage(200) {}
|
|
31
161
|
|
|
@@ -36,6 +166,7 @@ class SensorPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
36
166
|
sensorId = jsonObj["sid"];
|
|
37
167
|
timestamp = jsonObj["ts"];
|
|
38
168
|
batteryLevel = jsonObj["bat"];
|
|
169
|
+
messageType = jsonObj["message_type"] | 200;
|
|
39
170
|
}
|
|
40
171
|
|
|
41
172
|
JsonObject addTo(JsonObject&& jsonObj) const {
|
|
@@ -46,6 +177,7 @@ class SensorPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
46
177
|
jsonObj["sid"] = sensorId;
|
|
47
178
|
jsonObj["ts"] = timestamp;
|
|
48
179
|
jsonObj["bat"] = batteryLevel;
|
|
180
|
+
jsonObj["message_type"] = messageType;
|
|
49
181
|
return jsonObj;
|
|
50
182
|
}
|
|
51
183
|
|
|
@@ -66,14 +198,18 @@ class CommandPackage : public painlessmesh::plugin::SinglePackage {
|
|
|
66
198
|
TSTRING parameters = ""; // Command parameters as JSON string
|
|
67
199
|
uint32_t commandId = 0; // Unique command identifier for tracking
|
|
68
200
|
|
|
201
|
+
// MQTT Schema v0.7.3+ message_type for faster classification
|
|
202
|
+
uint16_t messageType = 400; // COMMAND
|
|
203
|
+
|
|
69
204
|
CommandPackage()
|
|
70
|
-
: SinglePackage(400) {} // Type ID 400 (COMMAND per mqtt-schema v0.7.
|
|
205
|
+
: SinglePackage(400) {} // Type ID 400 (COMMAND per mqtt-schema v0.7.3+)
|
|
71
206
|
|
|
72
207
|
CommandPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {
|
|
73
208
|
command = jsonObj["cmd"];
|
|
74
209
|
targetDevice = jsonObj["target"];
|
|
75
210
|
parameters = jsonObj["params"].as<TSTRING>();
|
|
76
211
|
commandId = jsonObj["cid"];
|
|
212
|
+
messageType = jsonObj["message_type"] | 400;
|
|
77
213
|
}
|
|
78
214
|
|
|
79
215
|
JsonObject addTo(JsonObject&& jsonObj) const {
|
|
@@ -82,6 +218,7 @@ class CommandPackage : public painlessmesh::plugin::SinglePackage {
|
|
|
82
218
|
jsonObj["target"] = targetDevice;
|
|
83
219
|
jsonObj["params"] = parameters;
|
|
84
220
|
jsonObj["cid"] = commandId;
|
|
221
|
+
jsonObj["message_type"] = messageType;
|
|
85
222
|
return jsonObj;
|
|
86
223
|
}
|
|
87
224
|
|
|
@@ -94,6 +231,33 @@ class CommandPackage : public painlessmesh::plugin::SinglePackage {
|
|
|
94
231
|
|
|
95
232
|
/**
|
|
96
233
|
* @brief Status report package for device health monitoring
|
|
234
|
+
*
|
|
235
|
+
* BOOLEAN FIELD NAMING CONVENTION
|
|
236
|
+
* ================================
|
|
237
|
+
*
|
|
238
|
+
* This package follows a standardized naming convention for boolean fields
|
|
239
|
+
* to improve code clarity and reduce ambiguity. See
|
|
240
|
+
* docs/BOOLEAN_NAMING_CONVENTION.md for complete documentation.
|
|
241
|
+
*
|
|
242
|
+
* Three patterns are used:
|
|
243
|
+
*
|
|
244
|
+
* 1. *Set suffix: Configuration data has been provided
|
|
245
|
+
* Example: deviceSecretSet = true means secret is configured
|
|
246
|
+
* Does NOT indicate if feature is enabled or working
|
|
247
|
+
*
|
|
248
|
+
* 2. *Enabled suffix: Feature is currently active/turned on
|
|
249
|
+
* Example: displayEnabled = true means display feature is active
|
|
250
|
+
* Independent of whether required configuration exists
|
|
251
|
+
*
|
|
252
|
+
* 3. is* prefix or *Connected: Current runtime state
|
|
253
|
+
* Example: mqttConnected = true means currently connected
|
|
254
|
+
* Reflects actual runtime conditions, not configuration
|
|
255
|
+
*
|
|
256
|
+
* A feature may have both *Set and *Enabled fields:
|
|
257
|
+
* - otaServerSet=true, otaEnabled=false: Server configured but feature disabled
|
|
258
|
+
* - otaServerSet=false, otaEnabled=true: Feature enabled but no server
|
|
259
|
+
* (invalid)
|
|
260
|
+
* - otaServerSet=true, otaEnabled=true: Fully configured and active
|
|
97
261
|
*/
|
|
98
262
|
class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
99
263
|
public:
|
|
@@ -107,6 +271,53 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
107
271
|
uint32_t responseToCommand = 0; // CommandId this is responding to
|
|
108
272
|
TSTRING responseMessage = ""; // Success/error message
|
|
109
273
|
|
|
274
|
+
// Organization metadata (Build 8052 - Phase 2.7)
|
|
275
|
+
TSTRING organizationId = ""; // Organization identifier
|
|
276
|
+
TSTRING customerId = ""; // Customer identifier
|
|
277
|
+
TSTRING deviceGroup = ""; // Device group/category
|
|
278
|
+
TSTRING deviceName = ""; // Device name
|
|
279
|
+
TSTRING deviceLocation = ""; // Device location
|
|
280
|
+
bool deviceSecretSet =
|
|
281
|
+
false; // *Set: Has device secret been configured? (not enabled/disabled)
|
|
282
|
+
|
|
283
|
+
// Sensor configuration (Build 8057 - Gateway format compatibility)
|
|
284
|
+
// Note: Time fields follow Alteriom time field naming convention (see file
|
|
285
|
+
// header) Stored in milliseconds, serialized as both _ms and _s variants
|
|
286
|
+
uint32_t sensorReadInterval = 0; // Sensor read interval in milliseconds
|
|
287
|
+
uint32_t transmissionInterval = 0; // Transmission interval in milliseconds
|
|
288
|
+
double tempOffset = 0.0; // Temperature calibration offset
|
|
289
|
+
double humidityOffset = 0.0; // Humidity calibration offset
|
|
290
|
+
double pressureOffset = 0.0; // Pressure calibration offset
|
|
291
|
+
|
|
292
|
+
// Sensor inventory (Build 8057 - Separate from config to avoid collision)
|
|
293
|
+
uint8_t sensorCount = 0; // Number of sensors attached
|
|
294
|
+
uint8_t sensorTypeMask = 0; // Bitmask of sensor types present
|
|
295
|
+
|
|
296
|
+
// Display configuration
|
|
297
|
+
bool displayEnabled =
|
|
298
|
+
false; // *Enabled: Is display feature currently active?
|
|
299
|
+
uint8_t displayBrightness = 0; // Display brightness (0-255)
|
|
300
|
+
uint32_t displayTimeout = 0; // Display timeout in milliseconds
|
|
301
|
+
|
|
302
|
+
// Power configuration
|
|
303
|
+
bool deepSleepEnabled =
|
|
304
|
+
false; // *Enabled: Is deep sleep mode currently active?
|
|
305
|
+
uint32_t deepSleepInterval = 0; // Deep sleep interval in milliseconds
|
|
306
|
+
uint8_t batteryPercent = 0; // Battery percentage (0-100)
|
|
307
|
+
|
|
308
|
+
// MQTT retry configuration
|
|
309
|
+
uint8_t mqttMaxRetryAttempts = 0; // Maximum retry attempts
|
|
310
|
+
uint32_t mqttCircuitBreakerMs = 0; // Circuit breaker timeout in milliseconds
|
|
311
|
+
bool mqttHourlyRetryEnabled =
|
|
312
|
+
false; // *Enabled: Is hourly retry feature active?
|
|
313
|
+
uint32_t mqttInitialRetryMs = 0; // Initial retry delay in milliseconds
|
|
314
|
+
uint32_t mqttMaxRetryMs = 0; // Maximum retry delay in milliseconds
|
|
315
|
+
float mqttBackoffMultiplier =
|
|
316
|
+
0.0; // Backoff multiplier for exponential backoff
|
|
317
|
+
|
|
318
|
+
// MQTT Schema v0.7.3+ message_type for faster classification
|
|
319
|
+
uint16_t messageType = 202; // SENSOR_STATUS
|
|
320
|
+
|
|
110
321
|
StatusPackage() : BroadcastPackage(202) {} // Type ID 202 for Alteriom status
|
|
111
322
|
|
|
112
323
|
StatusPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
|
|
@@ -117,6 +328,72 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
117
328
|
firmwareVersion = jsonObj["fw"].as<TSTRING>();
|
|
118
329
|
responseToCommand = jsonObj["respTo"] | 0;
|
|
119
330
|
responseMessage = jsonObj["respMsg"].as<TSTRING>();
|
|
331
|
+
|
|
332
|
+
// Deserialize organization metadata (camelCase format)
|
|
333
|
+
if (jsonObj["organization"].is<JsonObject>()) {
|
|
334
|
+
JsonObject org = jsonObj["organization"];
|
|
335
|
+
organizationId = org["organizationId"].as<TSTRING>();
|
|
336
|
+
customerId = org["customerId"].as<TSTRING>();
|
|
337
|
+
deviceGroup = org["deviceGroup"].as<TSTRING>();
|
|
338
|
+
deviceName = org["device_name"].as<TSTRING>();
|
|
339
|
+
deviceLocation = org["device_location"].as<TSTRING>();
|
|
340
|
+
deviceSecretSet = org["device_secret_set"] | false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Deserialize sensor configuration (Build 8057 - Gateway format)
|
|
344
|
+
if (jsonObj["sensors"].is<JsonObject>()) {
|
|
345
|
+
JsonObject sensors = jsonObj["sensors"];
|
|
346
|
+
sensorReadInterval = sensors["read_interval_ms"] | 0;
|
|
347
|
+
transmissionInterval = sensors["transmission_interval_ms"] | 0;
|
|
348
|
+
|
|
349
|
+
if (sensors["calibration"].is<JsonObject>()) {
|
|
350
|
+
JsonObject calibration = sensors["calibration"];
|
|
351
|
+
tempOffset = calibration["temperature_offset"] | 0.0;
|
|
352
|
+
humidityOffset = calibration["humidity_offset"] | 0.0;
|
|
353
|
+
pressureOffset = calibration["pressure_offset"] | 0.0;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Deserialize sensor inventory (Build 8057 - Separate key)
|
|
358
|
+
if (jsonObj["sensor_inventory"].is<JsonObject>()) {
|
|
359
|
+
JsonObject sensorInventory = jsonObj["sensor_inventory"];
|
|
360
|
+
sensorCount = sensorInventory["count"] | 0;
|
|
361
|
+
sensorTypeMask = sensorInventory["type_mask"] | 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Deserialize display configuration (with backward compatibility)
|
|
365
|
+
if (jsonObj["display_config"].is<JsonObject>()) {
|
|
366
|
+
JsonObject displayConfig = jsonObj["display_config"];
|
|
367
|
+
displayEnabled = displayConfig["enabled"] | false;
|
|
368
|
+
displayBrightness = displayConfig["brightness"] | 0;
|
|
369
|
+
// Support both old and new field names for backward compatibility
|
|
370
|
+
displayTimeout =
|
|
371
|
+
displayConfig["timeout_ms"] | displayConfig["timeout"] | 0;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Deserialize power configuration (with backward compatibility)
|
|
375
|
+
if (jsonObj["power_config"].is<JsonObject>()) {
|
|
376
|
+
JsonObject powerConfig = jsonObj["power_config"];
|
|
377
|
+
deepSleepEnabled = powerConfig["deep_sleep_enabled"] | false;
|
|
378
|
+
// Support both old and new field names for backward compatibility
|
|
379
|
+
deepSleepInterval = powerConfig["deep_sleep_interval_ms"] |
|
|
380
|
+
powerConfig["deep_sleep_interval"] | 0;
|
|
381
|
+
batteryPercent = powerConfig["battery_percent"] | 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Deserialize MQTT retry configuration
|
|
385
|
+
if (jsonObj["mqtt_retry"].is<JsonObject>()) {
|
|
386
|
+
JsonObject mqttRetry = jsonObj["mqtt_retry"];
|
|
387
|
+
mqttMaxRetryAttempts = mqttRetry["max_attempts"] | 0;
|
|
388
|
+
mqttCircuitBreakerMs = mqttRetry["circuit_breaker_ms"] | 0;
|
|
389
|
+
mqttHourlyRetryEnabled = mqttRetry["hourly_retry_enabled"] | false;
|
|
390
|
+
mqttInitialRetryMs = mqttRetry["initial_retry_ms"] | 0;
|
|
391
|
+
mqttMaxRetryMs = mqttRetry["max_retry_ms"] | 0;
|
|
392
|
+
mqttBackoffMultiplier = mqttRetry["backoff_multiplier"] | 0.0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Deserialize message_type field
|
|
396
|
+
messageType = jsonObj["message_type"] | 202;
|
|
120
397
|
}
|
|
121
398
|
|
|
122
399
|
JsonObject addTo(JsonObject&& jsonObj) const {
|
|
@@ -130,13 +407,110 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
130
407
|
jsonObj["respTo"] = responseToCommand;
|
|
131
408
|
jsonObj["respMsg"] = responseMessage;
|
|
132
409
|
}
|
|
410
|
+
|
|
411
|
+
// Serialize organization metadata (mixed case per MQTT Schema v0.7.2)
|
|
412
|
+
// Always serialize to ensure predictable JSON structure
|
|
413
|
+
JsonObject org = jsonObj["organization"].to<JsonObject>();
|
|
414
|
+
org["organizationId"] = organizationId;
|
|
415
|
+
org["customerId"] = customerId;
|
|
416
|
+
org["deviceGroup"] = deviceGroup;
|
|
417
|
+
org["device_name"] = deviceName;
|
|
418
|
+
org["device_location"] = deviceLocation;
|
|
419
|
+
org["device_secret_set"] = deviceSecretSet;
|
|
420
|
+
|
|
421
|
+
// Serialize sensor configuration (Build 8057 - Match gateway format)
|
|
422
|
+
// Always serialize to ensure predictable JSON structure
|
|
423
|
+
JsonObject sensors = jsonObj["sensors"].to<JsonObject>();
|
|
424
|
+
sensors["read_interval_ms"] = sensorReadInterval;
|
|
425
|
+
sensors["read_interval_s"] = sensorReadInterval / 1000;
|
|
426
|
+
sensors["transmission_interval_ms"] = transmissionInterval;
|
|
427
|
+
sensors["transmission_interval_s"] = transmissionInterval / 1000;
|
|
428
|
+
|
|
429
|
+
// Nested calibration object - always serialize for consistency
|
|
430
|
+
JsonObject calibration = sensors["calibration"].to<JsonObject>();
|
|
431
|
+
calibration["temperature_offset"] = tempOffset;
|
|
432
|
+
calibration["humidity_offset"] = humidityOffset;
|
|
433
|
+
calibration["pressure_offset"] = pressureOffset;
|
|
434
|
+
|
|
435
|
+
// Serialize sensor inventory (Build 8057 - Separate key to avoid collision)
|
|
436
|
+
// Always serialize to ensure predictable JSON structure
|
|
437
|
+
JsonObject sensorInventory = jsonObj["sensor_inventory"].to<JsonObject>();
|
|
438
|
+
sensorInventory["count"] = sensorCount;
|
|
439
|
+
sensorInventory["type_mask"] = sensorTypeMask;
|
|
440
|
+
|
|
441
|
+
// Serialize display configuration (with both _ms and _s variants)
|
|
442
|
+
// Always serialize to ensure predictable JSON structure
|
|
443
|
+
JsonObject displayConfig = jsonObj["display_config"].to<JsonObject>();
|
|
444
|
+
displayConfig["enabled"] = displayEnabled;
|
|
445
|
+
displayConfig["brightness"] = displayBrightness;
|
|
446
|
+
displayConfig["timeout_ms"] = displayTimeout;
|
|
447
|
+
displayConfig["timeout_s"] = displayTimeout / 1000;
|
|
448
|
+
|
|
449
|
+
// Serialize power configuration (with both _ms and _s variants)
|
|
450
|
+
// Always serialize to ensure predictable JSON structure
|
|
451
|
+
JsonObject powerConfig = jsonObj["power_config"].to<JsonObject>();
|
|
452
|
+
powerConfig["deep_sleep_enabled"] = deepSleepEnabled;
|
|
453
|
+
powerConfig["deep_sleep_interval_ms"] = deepSleepInterval;
|
|
454
|
+
powerConfig["deep_sleep_interval_s"] = deepSleepInterval / 1000;
|
|
455
|
+
powerConfig["battery_percent"] = batteryPercent;
|
|
456
|
+
|
|
457
|
+
// Serialize MQTT retry configuration (with both _ms and _s variants)
|
|
458
|
+
// Always serialize to ensure predictable JSON structure
|
|
459
|
+
JsonObject mqttRetry = jsonObj["mqtt_retry"].to<JsonObject>();
|
|
460
|
+
mqttRetry["max_attempts"] = mqttMaxRetryAttempts;
|
|
461
|
+
mqttRetry["circuit_breaker_ms"] = mqttCircuitBreakerMs;
|
|
462
|
+
mqttRetry["circuit_breaker_s"] = mqttCircuitBreakerMs / 1000;
|
|
463
|
+
mqttRetry["hourly_retry_enabled"] = mqttHourlyRetryEnabled;
|
|
464
|
+
mqttRetry["initial_retry_ms"] = mqttInitialRetryMs;
|
|
465
|
+
mqttRetry["initial_retry_s"] = mqttInitialRetryMs / 1000;
|
|
466
|
+
mqttRetry["max_retry_ms"] = mqttMaxRetryMs;
|
|
467
|
+
mqttRetry["max_retry_s"] = mqttMaxRetryMs / 1000;
|
|
468
|
+
mqttRetry["backoff_multiplier"] = mqttBackoffMultiplier;
|
|
469
|
+
|
|
470
|
+
// Serialize message_type field
|
|
471
|
+
jsonObj["message_type"] = messageType;
|
|
472
|
+
|
|
133
473
|
return jsonObj;
|
|
134
474
|
}
|
|
135
475
|
|
|
136
476
|
#if ARDUINOJSON_VERSION_MAJOR < 7
|
|
137
477
|
size_t jsonObjectSize() const {
|
|
138
|
-
|
|
139
|
-
|
|
478
|
+
size_t size = JSON_OBJECT_SIZE(noJsonFields + 7) +
|
|
479
|
+
firmwareVersion.length() + responseMessage.length();
|
|
480
|
+
|
|
481
|
+
// Always add organization object size for predictable structure
|
|
482
|
+
size += JSON_OBJECT_SIZE(6) + organizationId.length() +
|
|
483
|
+
customerId.length() + deviceGroup.length() + deviceName.length() +
|
|
484
|
+
deviceLocation.length();
|
|
485
|
+
|
|
486
|
+
// Always add sensor configuration object size (Build 8057)
|
|
487
|
+
// sensors object with read_interval_ms, read_interval_s,
|
|
488
|
+
// transmission_interval_ms, transmission_interval_s, calibration
|
|
489
|
+
size += JSON_OBJECT_SIZE(5);
|
|
490
|
+
// calibration nested object - always included
|
|
491
|
+
size += JSON_OBJECT_SIZE(3);
|
|
492
|
+
|
|
493
|
+
// Always add sensor inventory object size (Build 8057)
|
|
494
|
+
size += JSON_OBJECT_SIZE(
|
|
495
|
+
2); // sensor_inventory object with count and type_mask
|
|
496
|
+
|
|
497
|
+
// Always add display configuration object size
|
|
498
|
+
// display_config object with enabled, brightness, timeout_ms, timeout_s
|
|
499
|
+
size += JSON_OBJECT_SIZE(4);
|
|
500
|
+
|
|
501
|
+
// Always add power configuration object size
|
|
502
|
+
// power_config object with deep_sleep_enabled, deep_sleep_interval_ms,
|
|
503
|
+
// deep_sleep_interval_s, battery_percent
|
|
504
|
+
size += JSON_OBJECT_SIZE(4);
|
|
505
|
+
|
|
506
|
+
// Always add MQTT retry configuration object size
|
|
507
|
+
// mqtt_retry object with max_attempts, circuit_breaker_ms,
|
|
508
|
+
// circuit_breaker_s, hourly_retry_enabled, initial_retry_ms,
|
|
509
|
+
// initial_retry_s, max_retry_ms, max_retry_s, backoff_multiplier
|
|
510
|
+
size +=
|
|
511
|
+
JSON_OBJECT_SIZE(9) + 10; // Extra space for backoff_multiplier string
|
|
512
|
+
|
|
513
|
+
return size;
|
|
140
514
|
}
|
|
141
515
|
#endif
|
|
142
516
|
};
|
|
@@ -14,7 +14,7 @@ framework = arduino
|
|
|
14
14
|
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
15
15
|
lib_deps =
|
|
16
16
|
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
-
esp32async/ESPAsyncTCP ; Only for ESP8266
|
|
17
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
18
|
|
|
19
19
|
[env:esp32]
|
|
20
20
|
platform = espressif32
|
|
@@ -20,7 +20,7 @@ framework = arduino
|
|
|
20
20
|
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
21
21
|
lib_deps =
|
|
22
22
|
${env.lib_deps} ; Inherit common dependencies
|
|
23
|
-
esp32async/ESPAsyncTCP ; Only for ESP8266
|
|
23
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
24
24
|
|
|
25
25
|
[env:esp32]
|
|
26
26
|
platform = espressif32
|
|
@@ -116,7 +116,8 @@ void sendMetrics() {
|
|
|
116
116
|
// CPU and Processing
|
|
117
117
|
metrics.loopIterations = ((loopCount - lastLoopCount) * 1000) / timeDelta;
|
|
118
118
|
metrics.cpuUsage = calculateCPUUsage();
|
|
119
|
-
|
|
119
|
+
// Note: TaskScheduler doesn't provide a size() method, so we'll use 0 or estimate
|
|
120
|
+
metrics.taskQueueSize = 0; // TaskScheduler API doesn't expose queue size
|
|
120
121
|
|
|
121
122
|
// Memory Metrics
|
|
122
123
|
metrics.freeHeap = ESP.getFreeHeap();
|
|
@@ -155,8 +156,13 @@ void sendMetrics() {
|
|
|
155
156
|
metrics.collectionTimestamp = mesh.getNodeTime();
|
|
156
157
|
metrics.collectionInterval = METRICS_INTERVAL;
|
|
157
158
|
|
|
158
|
-
//
|
|
159
|
-
|
|
159
|
+
// Serialize and send the metrics
|
|
160
|
+
JsonDocument doc;
|
|
161
|
+
JsonObject obj = doc.to<JsonObject>();
|
|
162
|
+
metrics.addTo(std::move(obj));
|
|
163
|
+
|
|
164
|
+
String msg;
|
|
165
|
+
serializeJson(doc, msg);
|
|
160
166
|
bool sent = mesh.sendBroadcast(msg);
|
|
161
167
|
|
|
162
168
|
if (sent) {
|
|
@@ -266,8 +272,13 @@ void sendHealthCheck() {
|
|
|
266
272
|
health.checkTimestamp = mesh.getNodeTime();
|
|
267
273
|
health.nextCheckDue = health.checkTimestamp + (HEALTH_INTERVAL * 1000);
|
|
268
274
|
|
|
269
|
-
//
|
|
270
|
-
|
|
275
|
+
// Serialize and send the health check
|
|
276
|
+
JsonDocument doc;
|
|
277
|
+
JsonObject obj = doc.to<JsonObject>();
|
|
278
|
+
health.addTo(std::move(obj));
|
|
279
|
+
|
|
280
|
+
String msg;
|
|
281
|
+
serializeJson(doc, msg);
|
|
271
282
|
bool sent = mesh.sendBroadcast(msg);
|
|
272
283
|
|
|
273
284
|
if (sent) {
|
|
@@ -372,10 +383,10 @@ void receivedCallback(uint32_t from, String& msg) {
|
|
|
372
383
|
totalPacketsRx++;
|
|
373
384
|
|
|
374
385
|
// Parse message type
|
|
375
|
-
|
|
386
|
+
JsonDocument doc;
|
|
376
387
|
deserializeJson(doc, msg);
|
|
377
388
|
JsonObject obj = doc.as<JsonObject>();
|
|
378
|
-
|
|
389
|
+
uint16_t msgType = obj["type"];
|
|
379
390
|
|
|
380
391
|
Serial.printf("\nReceived Type %d from %u\n", msgType, from);
|
|
381
392
|
|
|
@@ -14,7 +14,7 @@ framework = arduino
|
|
|
14
14
|
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
15
15
|
lib_deps =
|
|
16
16
|
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
-
esp32async/ESPAsyncTCP ; Only for ESP8266
|
|
17
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
18
|
|
|
19
19
|
[env:esp32]
|
|
20
20
|
platform = espressif32
|
|
@@ -14,7 +14,7 @@ framework = arduino
|
|
|
14
14
|
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
15
15
|
lib_deps =
|
|
16
16
|
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
-
esp32async/ESPAsyncTCP ; Only for ESP8266
|
|
17
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
18
|
|
|
19
19
|
[env:esp32]
|
|
20
20
|
platform = espressif32
|
|
@@ -14,7 +14,7 @@ framework = arduino
|
|
|
14
14
|
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
15
15
|
lib_deps =
|
|
16
16
|
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
-
esp32async/ESPAsyncTCP ; Only for ESP8266
|
|
17
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
18
|
|
|
19
19
|
[env:esp32]
|
|
20
20
|
platform = espressif32
|
|
@@ -14,7 +14,7 @@ framework = arduino
|
|
|
14
14
|
lib_extra_dirs = ../../ ; Load the local copy of painlessmesh. For your own example add painlessmesh to the lib_deps
|
|
15
15
|
lib_deps =
|
|
16
16
|
${env.lib_deps} ; Inherit common dependencies
|
|
17
|
-
esp32async/ESPAsyncTCP ; Only for ESP8266
|
|
17
|
+
esp32async/ESPAsyncTCP@^2.0.0 ; Only for ESP8266
|
|
18
18
|
|
|
19
19
|
[env:esp32]
|
|
20
20
|
platform = espressif32
|