@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/README.md +21 -11
  3. package/RELEASE_GUIDE.md +57 -8
  4. package/docs/API_DESIGN_GUIDELINES.md +414 -0
  5. package/docs/BOOLEAN_NAMING_CONVENTION.md +235 -0
  6. package/docs/README.md +2 -1
  7. package/docs/RELEASE_AGENT_SUMMARY.md +386 -0
  8. package/docs/alteriom/overview.md +23 -0
  9. package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +1 -1
  10. package/docs/troubleshooting/ESP32_C6_COMPATIBILITY.md +157 -0
  11. package/docs/troubleshooting/common-issues.md +28 -0
  12. package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +21 -3
  13. package/examples/alteriom/README.md +13 -1
  14. package/examples/alteriom/alteriom_sensor_package.hpp +377 -3
  15. package/examples/alteriom/platformio.ini +1 -1
  16. package/examples/alteriomImproved/platformio.ini +1 -1
  17. package/examples/alteriomMetricsHealth/metrics_health_node.ino +18 -7
  18. package/examples/alteriomMetricsHealth/platformio.ini +1 -1
  19. package/examples/alteriomPhase1/platformio.ini +1 -1
  20. package/examples/alteriomPhase2/platformio.ini +1 -1
  21. package/examples/alteriomSensorNode/platformio.ini +1 -1
  22. package/examples/basic/platformio.ini +1 -1
  23. package/examples/bridge/alteriom_sensor_package.hpp +1170 -0
  24. package/examples/bridge/bridge.ino +2 -2
  25. package/examples/bridge/enhanced_mqtt_bridge.hpp +1 -1
  26. package/examples/bridge/mqtt_command_bridge.hpp +2 -2
  27. package/examples/bridge/platformio.ini +2 -1
  28. package/examples/echoNode/platformio.ini +1 -1
  29. package/examples/logClient/platformio.ini +1 -1
  30. package/examples/logServer/platformio.ini +1 -1
  31. package/examples/mqttStatusBridge/platformio.ini +1 -1
  32. package/examples/namedMesh/platformio.ini +1 -1
  33. package/examples/otaReceiver/platformio.ini +1 -1
  34. package/examples/startHere/platformio.ini +1 -1
  35. package/examples/webServer/platformio.ini +1 -1
  36. package/library.json +93 -53
  37. package/library.properties +1 -1
  38. package/package.json +2 -2
  39. package/src/arduino/wifi.hpp +9 -0
  40. 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.2+)
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
- return JSON_OBJECT_SIZE(noJsonFields + 7) + firmwareVersion.length() +
139
- responseMessage.length();
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
- metrics.taskQueueSize = userScheduler.size();
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
- // Send the metrics
159
- String msg = metrics.toJsonString();
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
- // Send the health check
270
- String msg = health.toJsonString();
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
- DynamicJsonDocument doc(1024);
386
+ JsonDocument doc;
376
387
  deserializeJson(doc, msg);
377
388
  JsonObject obj = doc.as<JsonObject>();
378
- uint8_t msgType = obj["type"];
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
@@ -14,7 +14,7 @@ framework = arduino
14
14
  lib_extra_dirs = ../../
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