@alteriom/painlessmesh 1.7.6 → 1.7.8

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 (38) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/README.md +96 -14
  3. package/RELEASE_GUIDE.md +36 -0
  4. package/docs/API_DESIGN_GUIDELINES.md +414 -0
  5. package/docs/BOOLEAN_NAMING_CONVENTION.md +235 -0
  6. package/docs/MQTT_BRIDGE_COMMANDS.md +10 -10
  7. package/docs/MQTT_BRIDGE_IMPLEMENTATION_SUMMARY.md +1 -1
  8. package/docs/MQTT_SCHEMA_COMPLIANCE.md +57 -2
  9. package/docs/PHASE1_GUIDE.md +1 -1
  10. package/docs/alteriom/overview.md +25 -2
  11. package/docs/architecture/plugin-system.md +1 -1
  12. package/docs/archive/RELEASE_SUMMARY.md +1 -1
  13. package/docs/releases/RELEASE_CHECKLIST_v1.7.6.md +389 -0
  14. package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +391 -0
  15. package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +794 -0
  16. package/docs/wiki/API-Reference.md +2 -2
  17. package/docs/wiki/Complete-Documentation.md +1 -1
  18. package/examples/alteriom/README.md +150 -4
  19. package/examples/alteriom/alteriom.ino +1 -1
  20. package/examples/alteriom/alteriom_sensor_package.hpp +914 -4
  21. package/examples/alteriomImproved/alteriom_sensor_package.hpp +1 -1
  22. package/examples/alteriomImproved/improved_sensor_node.ino +1 -1
  23. package/examples/alteriomMetricsHealth/alteriom_sensor_package.hpp +796 -0
  24. package/examples/alteriomMetricsHealth/metrics_health_node.ino +418 -0
  25. package/examples/alteriomMetricsHealth/platformio.ini +26 -0
  26. package/examples/alteriomPhase1/alteriom_sensor_package.hpp +1 -1
  27. package/examples/alteriomPhase1/phase1_features.ino +2 -2
  28. package/examples/alteriomPhase2/alteriom_sensor_package.hpp +1 -1
  29. package/examples/alteriomSensorNode/alteriom_sensor_node.ino +1 -1
  30. package/examples/alteriomSensorNode/alteriom_sensor_package.hpp +1 -1
  31. package/examples/bridge/enhanced_mqtt_bridge.hpp +610 -0
  32. package/examples/bridge/enhanced_mqtt_bridge_example.ino +226 -0
  33. package/examples/meshCommandNode/alteriom_sensor_package.hpp +1 -1
  34. package/examples/mqttCommandBridge/alteriom_sensor_package.hpp +1 -1
  35. package/examples/mqttTopologyTest/mqttTopologyTest.ino +5 -1
  36. package/library.json +1 -1
  37. package/library.properties +1 -1
  38. package/package.json +2 -2
@@ -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
  /**
@@ -66,7 +193,8 @@ class CommandPackage : public painlessmesh::plugin::SinglePackage {
66
193
  TSTRING parameters = ""; // Command parameters as JSON string
67
194
  uint32_t commandId = 0; // Unique command identifier for tracking
68
195
 
69
- CommandPackage() : SinglePackage(201) {} // Type ID 201 for Alteriom commands
196
+ CommandPackage()
197
+ : SinglePackage(400) {} // Type ID 400 (COMMAND per mqtt-schema v0.7.2+)
70
198
 
71
199
  CommandPackage(JsonObject jsonObj) : SinglePackage(jsonObj) {
72
200
  command = jsonObj["cmd"];
@@ -93,6 +221,33 @@ class CommandPackage : public painlessmesh::plugin::SinglePackage {
93
221
 
94
222
  /**
95
223
  * @brief Status report package for device health monitoring
224
+ *
225
+ * BOOLEAN FIELD NAMING CONVENTION
226
+ * ================================
227
+ *
228
+ * This package follows a standardized naming convention for boolean fields
229
+ * to improve code clarity and reduce ambiguity. See
230
+ * docs/BOOLEAN_NAMING_CONVENTION.md for complete documentation.
231
+ *
232
+ * Three patterns are used:
233
+ *
234
+ * 1. *Set suffix: Configuration data has been provided
235
+ * Example: deviceSecretSet = true means secret is configured
236
+ * Does NOT indicate if feature is enabled or working
237
+ *
238
+ * 2. *Enabled suffix: Feature is currently active/turned on
239
+ * Example: displayEnabled = true means display feature is active
240
+ * Independent of whether required configuration exists
241
+ *
242
+ * 3. is* prefix or *Connected: Current runtime state
243
+ * Example: mqttConnected = true means currently connected
244
+ * Reflects actual runtime conditions, not configuration
245
+ *
246
+ * A feature may have both *Set and *Enabled fields:
247
+ * - otaServerSet=true, otaEnabled=false: Server configured but feature disabled
248
+ * - otaServerSet=false, otaEnabled=true: Feature enabled but no server
249
+ * (invalid)
250
+ * - otaServerSet=true, otaEnabled=true: Fully configured and active
96
251
  */
97
252
  class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
98
253
  public:
@@ -106,6 +261,50 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
106
261
  uint32_t responseToCommand = 0; // CommandId this is responding to
107
262
  TSTRING responseMessage = ""; // Success/error message
108
263
 
264
+ // Organization metadata (Build 8052 - Phase 2.7)
265
+ TSTRING organizationId = ""; // Organization identifier
266
+ TSTRING customerId = ""; // Customer identifier
267
+ TSTRING deviceGroup = ""; // Device group/category
268
+ TSTRING deviceName = ""; // Device name
269
+ TSTRING deviceLocation = ""; // Device location
270
+ bool deviceSecretSet =
271
+ false; // *Set: Has device secret been configured? (not enabled/disabled)
272
+
273
+ // Sensor configuration (Build 8057 - Gateway format compatibility)
274
+ // Note: Time fields follow Alteriom time field naming convention (see file
275
+ // header) Stored in milliseconds, serialized as both _ms and _s variants
276
+ uint32_t sensorReadInterval = 0; // Sensor read interval in milliseconds
277
+ uint32_t transmissionInterval = 0; // Transmission interval in milliseconds
278
+ double tempOffset = 0.0; // Temperature calibration offset
279
+ double humidityOffset = 0.0; // Humidity calibration offset
280
+ double pressureOffset = 0.0; // Pressure calibration offset
281
+
282
+ // Sensor inventory (Build 8057 - Separate from config to avoid collision)
283
+ uint8_t sensorCount = 0; // Number of sensors attached
284
+ uint8_t sensorTypeMask = 0; // Bitmask of sensor types present
285
+
286
+ // Display configuration
287
+ bool displayEnabled =
288
+ false; // *Enabled: Is display feature currently active?
289
+ uint8_t displayBrightness = 0; // Display brightness (0-255)
290
+ uint32_t displayTimeout = 0; // Display timeout in milliseconds
291
+
292
+ // Power configuration
293
+ bool deepSleepEnabled =
294
+ false; // *Enabled: Is deep sleep mode currently active?
295
+ uint32_t deepSleepInterval = 0; // Deep sleep interval in milliseconds
296
+ uint8_t batteryPercent = 0; // Battery percentage (0-100)
297
+
298
+ // MQTT retry configuration
299
+ uint8_t mqttMaxRetryAttempts = 0; // Maximum retry attempts
300
+ uint32_t mqttCircuitBreakerMs = 0; // Circuit breaker timeout in milliseconds
301
+ bool mqttHourlyRetryEnabled =
302
+ false; // *Enabled: Is hourly retry feature active?
303
+ uint32_t mqttInitialRetryMs = 0; // Initial retry delay in milliseconds
304
+ uint32_t mqttMaxRetryMs = 0; // Maximum retry delay in milliseconds
305
+ float mqttBackoffMultiplier =
306
+ 0.0; // Backoff multiplier for exponential backoff
307
+
109
308
  StatusPackage() : BroadcastPackage(202) {} // Type ID 202 for Alteriom status
110
309
 
111
310
  StatusPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
@@ -116,6 +315,69 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
116
315
  firmwareVersion = jsonObj["fw"].as<TSTRING>();
117
316
  responseToCommand = jsonObj["respTo"] | 0;
118
317
  responseMessage = jsonObj["respMsg"].as<TSTRING>();
318
+
319
+ // Deserialize organization metadata (camelCase format)
320
+ if (jsonObj["organization"].is<JsonObject>()) {
321
+ JsonObject org = jsonObj["organization"];
322
+ organizationId = org["organizationId"].as<TSTRING>();
323
+ customerId = org["customerId"].as<TSTRING>();
324
+ deviceGroup = org["deviceGroup"].as<TSTRING>();
325
+ deviceName = org["device_name"].as<TSTRING>();
326
+ deviceLocation = org["device_location"].as<TSTRING>();
327
+ deviceSecretSet = org["device_secret_set"] | false;
328
+ }
329
+
330
+ // Deserialize sensor configuration (Build 8057 - Gateway format)
331
+ if (jsonObj["sensors"].is<JsonObject>()) {
332
+ JsonObject sensors = jsonObj["sensors"];
333
+ sensorReadInterval = sensors["read_interval_ms"] | 0;
334
+ transmissionInterval = sensors["transmission_interval_ms"] | 0;
335
+
336
+ if (sensors["calibration"].is<JsonObject>()) {
337
+ JsonObject calibration = sensors["calibration"];
338
+ tempOffset = calibration["temperature_offset"] | 0.0;
339
+ humidityOffset = calibration["humidity_offset"] | 0.0;
340
+ pressureOffset = calibration["pressure_offset"] | 0.0;
341
+ }
342
+ }
343
+
344
+ // Deserialize sensor inventory (Build 8057 - Separate key)
345
+ if (jsonObj["sensor_inventory"].is<JsonObject>()) {
346
+ JsonObject sensorInventory = jsonObj["sensor_inventory"];
347
+ sensorCount = sensorInventory["count"] | 0;
348
+ sensorTypeMask = sensorInventory["type_mask"] | 0;
349
+ }
350
+
351
+ // Deserialize display configuration (with backward compatibility)
352
+ if (jsonObj["display_config"].is<JsonObject>()) {
353
+ JsonObject displayConfig = jsonObj["display_config"];
354
+ displayEnabled = displayConfig["enabled"] | false;
355
+ displayBrightness = displayConfig["brightness"] | 0;
356
+ // Support both old and new field names for backward compatibility
357
+ displayTimeout =
358
+ displayConfig["timeout_ms"] | displayConfig["timeout"] | 0;
359
+ }
360
+
361
+ // Deserialize power configuration (with backward compatibility)
362
+ if (jsonObj["power_config"].is<JsonObject>()) {
363
+ JsonObject powerConfig = jsonObj["power_config"];
364
+ deepSleepEnabled = powerConfig["deep_sleep_enabled"] | false;
365
+ // Support both old and new field names for backward compatibility
366
+ deepSleepInterval = powerConfig["deep_sleep_interval_ms"] |
367
+ powerConfig["deep_sleep_interval"] | 0;
368
+ batteryPercent = powerConfig["battery_percent"] | 0;
369
+ }
370
+
371
+ // Deserialize MQTT retry configuration
372
+ if (jsonObj["mqtt_retry"].is<JsonObject>()) {
373
+ JsonObject mqttRetry = jsonObj["mqtt_retry"];
374
+ mqttMaxRetryAttempts = mqttRetry["max_attempts"] | 0;
375
+ mqttCircuitBreakerMs = mqttRetry["circuit_breaker_ms"] | 0;
376
+ mqttHourlyRetryEnabled = mqttRetry["hourly_retry_enabled"] | false;
377
+ mqttInitialRetryMs = mqttRetry["initial_retry_ms"] | 0;
378
+ mqttMaxRetryMs = mqttRetry["max_retry_ms"] | 0;
379
+ mqttBackoffMultiplier = mqttRetry["backoff_multiplier"] | 0.0;
380
+ }
119
381
  }
120
382
 
121
383
  JsonObject addTo(JsonObject&& jsonObj) const {
@@ -129,13 +391,107 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
129
391
  jsonObj["respTo"] = responseToCommand;
130
392
  jsonObj["respMsg"] = responseMessage;
131
393
  }
394
+
395
+ // Serialize organization metadata (mixed case per MQTT Schema v0.7.2)
396
+ // Always serialize to ensure predictable JSON structure
397
+ JsonObject org = jsonObj["organization"].to<JsonObject>();
398
+ org["organizationId"] = organizationId;
399
+ org["customerId"] = customerId;
400
+ org["deviceGroup"] = deviceGroup;
401
+ org["device_name"] = deviceName;
402
+ org["device_location"] = deviceLocation;
403
+ org["device_secret_set"] = deviceSecretSet;
404
+
405
+ // Serialize sensor configuration (Build 8057 - Match gateway format)
406
+ // Always serialize to ensure predictable JSON structure
407
+ JsonObject sensors = jsonObj["sensors"].to<JsonObject>();
408
+ sensors["read_interval_ms"] = sensorReadInterval;
409
+ sensors["read_interval_s"] = sensorReadInterval / 1000;
410
+ sensors["transmission_interval_ms"] = transmissionInterval;
411
+ sensors["transmission_interval_s"] = transmissionInterval / 1000;
412
+
413
+ // Nested calibration object - always serialize for consistency
414
+ JsonObject calibration = sensors["calibration"].to<JsonObject>();
415
+ calibration["temperature_offset"] = tempOffset;
416
+ calibration["humidity_offset"] = humidityOffset;
417
+ calibration["pressure_offset"] = pressureOffset;
418
+
419
+ // Serialize sensor inventory (Build 8057 - Separate key to avoid collision)
420
+ // Always serialize to ensure predictable JSON structure
421
+ JsonObject sensorInventory = jsonObj["sensor_inventory"].to<JsonObject>();
422
+ sensorInventory["count"] = sensorCount;
423
+ sensorInventory["type_mask"] = sensorTypeMask;
424
+
425
+ // Serialize display configuration (with both _ms and _s variants)
426
+ // Always serialize to ensure predictable JSON structure
427
+ JsonObject displayConfig = jsonObj["display_config"].to<JsonObject>();
428
+ displayConfig["enabled"] = displayEnabled;
429
+ displayConfig["brightness"] = displayBrightness;
430
+ displayConfig["timeout_ms"] = displayTimeout;
431
+ displayConfig["timeout_s"] = displayTimeout / 1000;
432
+
433
+ // Serialize power configuration (with both _ms and _s variants)
434
+ // Always serialize to ensure predictable JSON structure
435
+ JsonObject powerConfig = jsonObj["power_config"].to<JsonObject>();
436
+ powerConfig["deep_sleep_enabled"] = deepSleepEnabled;
437
+ powerConfig["deep_sleep_interval_ms"] = deepSleepInterval;
438
+ powerConfig["deep_sleep_interval_s"] = deepSleepInterval / 1000;
439
+ powerConfig["battery_percent"] = batteryPercent;
440
+
441
+ // Serialize MQTT retry configuration (with both _ms and _s variants)
442
+ // Always serialize to ensure predictable JSON structure
443
+ JsonObject mqttRetry = jsonObj["mqtt_retry"].to<JsonObject>();
444
+ mqttRetry["max_attempts"] = mqttMaxRetryAttempts;
445
+ mqttRetry["circuit_breaker_ms"] = mqttCircuitBreakerMs;
446
+ mqttRetry["circuit_breaker_s"] = mqttCircuitBreakerMs / 1000;
447
+ mqttRetry["hourly_retry_enabled"] = mqttHourlyRetryEnabled;
448
+ mqttRetry["initial_retry_ms"] = mqttInitialRetryMs;
449
+ mqttRetry["initial_retry_s"] = mqttInitialRetryMs / 1000;
450
+ mqttRetry["max_retry_ms"] = mqttMaxRetryMs;
451
+ mqttRetry["max_retry_s"] = mqttMaxRetryMs / 1000;
452
+ mqttRetry["backoff_multiplier"] = mqttBackoffMultiplier;
453
+
132
454
  return jsonObj;
133
455
  }
134
456
 
135
457
  #if ARDUINOJSON_VERSION_MAJOR < 7
136
458
  size_t jsonObjectSize() const {
137
- return JSON_OBJECT_SIZE(noJsonFields + 7) + firmwareVersion.length() +
138
- responseMessage.length();
459
+ size_t size = JSON_OBJECT_SIZE(noJsonFields + 7) +
460
+ firmwareVersion.length() + responseMessage.length();
461
+
462
+ // Always add organization object size for predictable structure
463
+ size += JSON_OBJECT_SIZE(6) + organizationId.length() +
464
+ customerId.length() + deviceGroup.length() + deviceName.length() +
465
+ deviceLocation.length();
466
+
467
+ // Always add sensor configuration object size (Build 8057)
468
+ // sensors object with read_interval_ms, read_interval_s,
469
+ // transmission_interval_ms, transmission_interval_s, calibration
470
+ size += JSON_OBJECT_SIZE(5);
471
+ // calibration nested object - always included
472
+ size += JSON_OBJECT_SIZE(3);
473
+
474
+ // Always add sensor inventory object size (Build 8057)
475
+ size += JSON_OBJECT_SIZE(
476
+ 2); // sensor_inventory object with count and type_mask
477
+
478
+ // Always add display configuration object size
479
+ // display_config object with enabled, brightness, timeout_ms, timeout_s
480
+ size += JSON_OBJECT_SIZE(4);
481
+
482
+ // Always add power configuration object size
483
+ // power_config object with deep_sleep_enabled, deep_sleep_interval_ms,
484
+ // deep_sleep_interval_s, battery_percent
485
+ size += JSON_OBJECT_SIZE(4);
486
+
487
+ // Always add MQTT retry configuration object size
488
+ // mqtt_retry object with max_attempts, circuit_breaker_ms,
489
+ // circuit_breaker_s, hourly_retry_enabled, initial_retry_ms,
490
+ // initial_retry_s, max_retry_ms, max_retry_s, backoff_multiplier
491
+ size +=
492
+ JSON_OBJECT_SIZE(9) + 10; // Extra space for backoff_multiplier string
493
+
494
+ return size;
139
495
  }
140
496
  #endif
141
497
  };
@@ -174,7 +530,8 @@ class EnhancedStatusPackage : public painlessmesh::plugin::BroadcastPackage {
174
530
  TSTRING lastError = ""; // Last error message for diagnostics
175
531
 
176
532
  EnhancedStatusPackage()
177
- : BroadcastPackage(203) {} // Type ID 203 for enhanced status
533
+ : BroadcastPackage(604) {
534
+ } // Type ID 604 for enhanced status (MESH_STATUS per mqtt-schema v0.7.2+)
178
535
 
179
536
  EnhancedStatusPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
180
537
  deviceStatus = jsonObj["status"];
@@ -236,6 +593,559 @@ class EnhancedStatusPackage : public painlessmesh::plugin::BroadcastPackage {
236
593
  #endif
237
594
  };
238
595
 
596
+ /**
597
+ * @brief Detailed performance metrics package for monitoring (Phase 2)
598
+ *
599
+ * Provides comprehensive performance data including CPU usage, memory trends,
600
+ * network throughput, and other metrics useful for dashboards and monitoring.
601
+ * Type ID 204 for Alteriom metrics.
602
+ */
603
+ class MetricsPackage : public painlessmesh::plugin::BroadcastPackage {
604
+ public:
605
+ // CPU and Processing
606
+ uint8_t cpuUsage = 0; // CPU usage percentage (0-100)
607
+ uint32_t loopIterations = 0; // Loop iterations per second
608
+ uint16_t taskQueueSize = 0; // Number of pending tasks
609
+
610
+ // Memory Metrics
611
+ uint32_t freeHeap = 0; // Free heap memory in bytes
612
+ uint32_t minFreeHeap = 0; // Minimum free heap since boot
613
+ uint32_t heapFragmentation = 0; // Heap fragmentation percentage
614
+ uint32_t maxAllocHeap = 0; // Largest allocatable block
615
+
616
+ // Network Performance
617
+ uint32_t bytesReceived = 0; // Total bytes received
618
+ uint32_t bytesSent = 0; // Total bytes sent
619
+ uint16_t packetsReceived = 0; // Total packets received
620
+ uint16_t packetsSent = 0; // Total packets sent
621
+ uint16_t packetsDropped = 0; // Packets dropped
622
+ uint16_t currentThroughput = 0; // Current throughput in bytes/sec
623
+
624
+ // Timing and Latency
625
+ uint32_t avgResponseTime = 0; // Average response time in microseconds
626
+ uint32_t maxResponseTime = 0; // Maximum response time in microseconds
627
+ uint16_t avgMeshLatency = 0; // Average mesh latency in milliseconds
628
+
629
+ // Connection Quality
630
+ uint8_t connectionQuality = 0; // Overall connection quality (0-100)
631
+ int8_t wifiRSSI = 0; // WiFi RSSI in dBm
632
+
633
+ // Collection metadata
634
+ uint32_t collectionTimestamp = 0; // When metrics were collected
635
+ uint32_t collectionInterval = 0; // Interval between collections in ms
636
+
637
+ // MQTT Schema v0.7.2+ message_type for faster classification
638
+ uint16_t messageType = 204; // SENSOR_METRICS (aligns with schema v0.7.2+)
639
+
640
+ MetricsPackage() : BroadcastPackage(204) {}
641
+
642
+ MetricsPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
643
+ cpuUsage = jsonObj["cpu"];
644
+ loopIterations = jsonObj["loops"];
645
+ taskQueueSize = jsonObj["tasks"];
646
+
647
+ freeHeap = jsonObj["heap"];
648
+ minFreeHeap = jsonObj["minHeap"];
649
+ heapFragmentation = jsonObj["fragHeap"];
650
+ maxAllocHeap = jsonObj["maxHeap"];
651
+
652
+ bytesReceived = jsonObj["bytesRx"];
653
+ bytesSent = jsonObj["bytesTx"];
654
+ packetsReceived = jsonObj["pktsRx"];
655
+ packetsSent = jsonObj["pktsTx"];
656
+ packetsDropped = jsonObj["pktsDrop"];
657
+ currentThroughput = jsonObj["throughput"];
658
+
659
+ avgResponseTime = jsonObj["avgResp"];
660
+ maxResponseTime = jsonObj["maxResp"];
661
+ avgMeshLatency = jsonObj["avgLat"];
662
+
663
+ connectionQuality = jsonObj["connQual"];
664
+ wifiRSSI = jsonObj["rssi"];
665
+
666
+ collectionTimestamp = jsonObj["ts"];
667
+ collectionInterval = jsonObj["interval"];
668
+ messageType = jsonObj["message_type"] | 204;
669
+ }
670
+
671
+ JsonObject addTo(JsonObject&& jsonObj) const {
672
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
673
+
674
+ // CPU and Processing
675
+ jsonObj["cpu"] = cpuUsage;
676
+ jsonObj["loops"] = loopIterations;
677
+ jsonObj["tasks"] = taskQueueSize;
678
+
679
+ // Memory Metrics
680
+ jsonObj["heap"] = freeHeap;
681
+ jsonObj["minHeap"] = minFreeHeap;
682
+ jsonObj["fragHeap"] = heapFragmentation;
683
+ jsonObj["maxHeap"] = maxAllocHeap;
684
+
685
+ // Network Performance
686
+ jsonObj["bytesRx"] = bytesReceived;
687
+ jsonObj["bytesTx"] = bytesSent;
688
+ jsonObj["pktsRx"] = packetsReceived;
689
+ jsonObj["pktsTx"] = packetsSent;
690
+ jsonObj["pktsDrop"] = packetsDropped;
691
+ jsonObj["throughput"] = currentThroughput;
692
+
693
+ // Timing and Latency
694
+ jsonObj["avgResp"] = avgResponseTime;
695
+ jsonObj["maxResp"] = maxResponseTime;
696
+ jsonObj["avgLat"] = avgMeshLatency;
697
+
698
+ // Connection Quality
699
+ jsonObj["connQual"] = connectionQuality;
700
+ jsonObj["rssi"] = wifiRSSI;
701
+
702
+ // Metadata
703
+ jsonObj["ts"] = collectionTimestamp;
704
+ jsonObj["interval"] = collectionInterval;
705
+ jsonObj["message_type"] = messageType; // MQTT Schema v0.7.1+
706
+
707
+ return jsonObj;
708
+ }
709
+
710
+ #if ARDUINOJSON_VERSION_MAJOR < 7
711
+ size_t jsonObjectSize() const { return JSON_OBJECT_SIZE(noJsonFields + 22); }
712
+ #endif
713
+ };
714
+
715
+ /**
716
+ * @brief Health check package for proactive problem detection (Phase 2)
717
+ *
718
+ * Provides early warning indicators and health status to detect issues
719
+ * before they cause failures. Used for predictive maintenance and alerting.
720
+ * Type ID 605 for Alteriom health checks (MESH_METRICS per mqtt-schema
721
+ * v0.7.2+).
722
+ */
723
+ class HealthCheckPackage : public painlessmesh::plugin::BroadcastPackage {
724
+ public:
725
+ // Overall Health Status (0=critical, 1=warning, 2=healthy)
726
+ uint8_t healthStatus = 2;
727
+
728
+ // Problem Indicators (bit flags)
729
+ uint16_t problemFlags = 0; // Bit flags for specific problems
730
+ /*
731
+ * Problem flag bits:
732
+ * 0x0001 - Low memory warning
733
+ * 0x0002 - High CPU usage
734
+ * 0x0004 - Connection instability
735
+ * 0x0008 - High packet loss
736
+ * 0x0010 - Network congestion
737
+ * 0x0020 - Low battery (if applicable)
738
+ * 0x0040 - Thermal warning
739
+ * 0x0080 - Mesh partition detected
740
+ * 0x0100 - OTA in progress
741
+ * 0x0200 - Configuration error
742
+ */
743
+
744
+ // Memory Health
745
+ uint8_t memoryHealth = 100; // Memory health score (0-100)
746
+ uint32_t memoryTrend = 0; // Bytes/hour memory loss (for leak detection)
747
+
748
+ // Network Health
749
+ uint8_t networkHealth = 100; // Network health score (0-100)
750
+ uint8_t packetLossPercent = 0; // Packet loss percentage
751
+ uint8_t reconnectionCount = 0; // Reconnections in last hour
752
+
753
+ // Performance Health
754
+ uint8_t performanceHealth = 100; // Performance health score (0-100)
755
+ uint32_t missedDeadlines = 0; // Missed task deadlines
756
+ uint16_t maxLoopTime = 0; // Maximum loop execution time in ms
757
+
758
+ // Environmental (if sensors available)
759
+ int8_t temperature = 0; // Device temperature in Celsius
760
+ uint8_t temperatureHealth = 100; // Temperature health score
761
+
762
+ // Uptime and Stability
763
+ uint32_t uptime = 0; // Uptime in seconds
764
+ uint16_t crashCount = 0; // Crash/restart count
765
+ uint32_t lastRebootReason = 0; // Last reboot reason code
766
+
767
+ // Predictive indicators
768
+ uint16_t estimatedTimeToFailure =
769
+ 0; // Estimated hours until failure (0=unknown)
770
+ TSTRING recommendations = ""; // Recommended actions
771
+
772
+ // Check metadata
773
+ uint32_t checkTimestamp = 0; // When check was performed
774
+ uint32_t nextCheckDue = 0; // When next check is due
775
+
776
+ // MQTT Schema v0.7.2+ message_type for faster classification
777
+ uint16_t messageType = 605; // MESH_METRICS type code (mesh performance
778
+ // health per mqtt-schema v0.7.2+)
779
+
780
+ HealthCheckPackage() : BroadcastPackage(605) {}
781
+
782
+ HealthCheckPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
783
+ healthStatus = jsonObj["health"];
784
+ problemFlags = jsonObj["problems"];
785
+
786
+ memoryHealth = jsonObj["memHealth"];
787
+ memoryTrend = jsonObj["memTrend"];
788
+
789
+ networkHealth = jsonObj["netHealth"];
790
+ packetLossPercent = jsonObj["loss"];
791
+ reconnectionCount = jsonObj["reconn"];
792
+
793
+ performanceHealth = jsonObj["perfHealth"];
794
+ missedDeadlines = jsonObj["missed"];
795
+ maxLoopTime = jsonObj["maxLoop"];
796
+
797
+ temperature = jsonObj["temp"];
798
+ temperatureHealth = jsonObj["tempHealth"];
799
+
800
+ uptime = jsonObj["uptime"];
801
+ crashCount = jsonObj["crashes"];
802
+ lastRebootReason = jsonObj["reboot"];
803
+
804
+ estimatedTimeToFailure = jsonObj["ettf"];
805
+ recommendations = jsonObj["recommend"].as<TSTRING>();
806
+
807
+ checkTimestamp = jsonObj["ts"];
808
+ nextCheckDue = jsonObj["nextCheck"];
809
+ messageType = jsonObj["message_type"] | 605;
810
+ }
811
+
812
+ JsonObject addTo(JsonObject&& jsonObj) const {
813
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
814
+
815
+ jsonObj["health"] = healthStatus;
816
+ jsonObj["problems"] = problemFlags;
817
+
818
+ jsonObj["memHealth"] = memoryHealth;
819
+ jsonObj["memTrend"] = memoryTrend;
820
+
821
+ jsonObj["netHealth"] = networkHealth;
822
+ jsonObj["loss"] = packetLossPercent;
823
+ jsonObj["reconn"] = reconnectionCount;
824
+
825
+ jsonObj["perfHealth"] = performanceHealth;
826
+ jsonObj["missed"] = missedDeadlines;
827
+ jsonObj["maxLoop"] = maxLoopTime;
828
+
829
+ jsonObj["temp"] = temperature;
830
+ jsonObj["tempHealth"] = temperatureHealth;
831
+
832
+ jsonObj["uptime"] = uptime;
833
+ jsonObj["crashes"] = crashCount;
834
+ jsonObj["reboot"] = lastRebootReason;
835
+
836
+ jsonObj["ettf"] = estimatedTimeToFailure;
837
+ jsonObj["recommend"] = recommendations;
838
+
839
+ jsonObj["ts"] = checkTimestamp;
840
+ jsonObj["nextCheck"] = nextCheckDue;
841
+ jsonObj["message_type"] = messageType; // MQTT Schema v0.7.1+
842
+
843
+ return jsonObj;
844
+ }
845
+
846
+ #if ARDUINOJSON_VERSION_MAJOR < 7
847
+ size_t jsonObjectSize() const {
848
+ return JSON_OBJECT_SIZE(noJsonFields + 20) + recommendations.length();
849
+ }
850
+ #endif
851
+ };
852
+
853
+ /**
854
+ * @brief Mesh node information for a single node
855
+ */
856
+ struct MeshNodeInfo {
857
+ uint32_t nodeId = 0; // Node identifier
858
+ uint8_t status = 0; // 0=offline, 1=online, 2=unreachable
859
+ uint32_t lastSeen = 0; // Unix timestamp of last communication
860
+ int8_t signalStrength = 0; // RSSI in dBm
861
+ };
862
+
863
+ /**
864
+ * @brief Mesh node list package (Type 600 - MESH_NODE_LIST)
865
+ *
866
+ * Provides list of all nodes in the mesh network with their status.
867
+ * Type ID 600 for MESH_NODE_LIST per mqtt-schema v0.7.2+.
868
+ */
869
+ class MeshNodeListPackage : public painlessmesh::plugin::BroadcastPackage {
870
+ public:
871
+ // Array of node information (max 50 nodes)
872
+ MeshNodeInfo nodes[50];
873
+ uint8_t nodeCount = 0; // Actual number of nodes
874
+ TSTRING meshId = ""; // Mesh network identifier
875
+
876
+ // MQTT Schema v0.7.2+ message_type
877
+ uint16_t messageType = 600; // MESH_NODE_LIST
878
+
879
+ MeshNodeListPackage() : BroadcastPackage(600) {}
880
+
881
+ MeshNodeListPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
882
+ JsonArray nodesArray = jsonObj["nodes"];
883
+ nodeCount = nodesArray.size();
884
+ if (nodeCount > 50) nodeCount = 50;
885
+
886
+ for (uint8_t i = 0; i < nodeCount; i++) {
887
+ JsonObject node = nodesArray[i];
888
+ nodes[i].nodeId = node["nodeId"];
889
+ nodes[i].status = node["status"];
890
+ nodes[i].lastSeen = node["lastSeen"];
891
+ nodes[i].signalStrength = node["rssi"];
892
+ }
893
+
894
+ meshId = jsonObj["meshId"].as<TSTRING>();
895
+ messageType = jsonObj["message_type"] | 600;
896
+ }
897
+
898
+ JsonObject addTo(JsonObject&& jsonObj) const {
899
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
900
+
901
+ JsonArray nodesArray = jsonObj["nodes"].to<JsonArray>();
902
+ for (uint8_t i = 0; i < nodeCount; i++) {
903
+ JsonObject node = nodesArray.add<JsonObject>();
904
+ node["nodeId"] = nodes[i].nodeId;
905
+ node["status"] = nodes[i].status;
906
+ node["lastSeen"] = nodes[i].lastSeen;
907
+ node["rssi"] = nodes[i].signalStrength;
908
+ }
909
+
910
+ jsonObj["nodeCount"] = nodeCount;
911
+ jsonObj["meshId"] = meshId;
912
+ jsonObj["message_type"] = messageType;
913
+
914
+ return jsonObj;
915
+ }
916
+
917
+ #if ARDUINOJSON_VERSION_MAJOR < 7
918
+ size_t jsonObjectSize() const {
919
+ return JSON_OBJECT_SIZE(noJsonFields + 3) + JSON_ARRAY_SIZE(nodeCount) +
920
+ nodeCount * JSON_OBJECT_SIZE(4) + meshId.length();
921
+ }
922
+ #endif
923
+ };
924
+
925
+ /**
926
+ * @brief Mesh connection information
927
+ */
928
+ struct MeshConnection {
929
+ uint32_t fromNode = 0; // Source node ID
930
+ uint32_t toNode = 0; // Destination node ID
931
+ float linkQuality = 0.0; // Link quality 0.0-1.0
932
+ uint16_t latencyMs = 0; // Latency in milliseconds
933
+ uint8_t hopCount = 1; // Number of hops
934
+ };
935
+
936
+ /**
937
+ * @brief Mesh topology package (Type 601 - MESH_TOPOLOGY)
938
+ *
939
+ * Provides mesh network topology with all connections.
940
+ * Type ID 601 for MESH_TOPOLOGY per mqtt-schema v0.7.2+.
941
+ */
942
+ class MeshTopologyPackage : public painlessmesh::plugin::BroadcastPackage {
943
+ public:
944
+ // Array of connections (max 100 connections)
945
+ MeshConnection connections[100];
946
+ uint8_t connectionCount = 0; // Actual number of connections
947
+ uint32_t rootNode = 0; // Root/gateway node ID
948
+
949
+ // MQTT Schema v0.7.2+ message_type
950
+ uint16_t messageType = 601; // MESH_TOPOLOGY
951
+
952
+ MeshTopologyPackage() : BroadcastPackage(601) {}
953
+
954
+ MeshTopologyPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
955
+ JsonArray connsArray = jsonObj["connections"];
956
+ connectionCount = connsArray.size();
957
+ if (connectionCount > 100) connectionCount = 100;
958
+
959
+ for (uint8_t i = 0; i < connectionCount; i++) {
960
+ JsonObject conn = connsArray[i];
961
+ connections[i].fromNode = conn["from"];
962
+ connections[i].toNode = conn["to"];
963
+ connections[i].linkQuality = conn["quality"];
964
+ connections[i].latencyMs = conn["latency"];
965
+ connections[i].hopCount = conn["hops"];
966
+ }
967
+
968
+ rootNode = jsonObj["rootNode"];
969
+ messageType = jsonObj["message_type"] | 601;
970
+ }
971
+
972
+ JsonObject addTo(JsonObject&& jsonObj) const {
973
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
974
+
975
+ JsonArray connsArray = jsonObj["connections"].to<JsonArray>();
976
+ for (uint8_t i = 0; i < connectionCount; i++) {
977
+ JsonObject conn = connsArray.add<JsonObject>();
978
+ conn["from"] = connections[i].fromNode;
979
+ conn["to"] = connections[i].toNode;
980
+ conn["quality"] = connections[i].linkQuality;
981
+ conn["latency"] = connections[i].latencyMs;
982
+ conn["hops"] = connections[i].hopCount;
983
+ }
984
+
985
+ jsonObj["totalConnections"] = connectionCount;
986
+ jsonObj["rootNode"] = rootNode;
987
+ jsonObj["message_type"] = messageType;
988
+
989
+ return jsonObj;
990
+ }
991
+
992
+ #if ARDUINOJSON_VERSION_MAJOR < 7
993
+ size_t jsonObjectSize() const {
994
+ return JSON_OBJECT_SIZE(noJsonFields + 3) +
995
+ JSON_ARRAY_SIZE(connectionCount) +
996
+ connectionCount * JSON_OBJECT_SIZE(5);
997
+ }
998
+ #endif
999
+ };
1000
+
1001
+ /**
1002
+ * @brief Mesh alert information
1003
+ */
1004
+ struct MeshAlert {
1005
+ uint8_t alertType =
1006
+ 0; // 0=low_memory, 1=node_offline, 2=connection_lost, etc.
1007
+ uint8_t severity = 0; // 0=info, 1=warning, 2=critical
1008
+ TSTRING message = ""; // Human-readable message
1009
+ uint32_t nodeId = 0; // Related node ID
1010
+ float metricValue = 0.0; // Related metric value
1011
+ float threshold = 0.0; // Threshold that triggered alert
1012
+ uint32_t alertId = 0; // Unique alert ID
1013
+ };
1014
+
1015
+ /**
1016
+ * @brief Mesh alert package (Type 602 - MESH_ALERT)
1017
+ *
1018
+ * Provides mesh network alerts for critical events.
1019
+ * Type ID 602 for MESH_ALERT per mqtt-schema v0.7.2+.
1020
+ */
1021
+ class MeshAlertPackage : public painlessmesh::plugin::BroadcastPackage {
1022
+ public:
1023
+ // Array of alerts (max 20 alerts)
1024
+ MeshAlert alerts[20];
1025
+ uint8_t alertCount = 0; // Actual number of alerts
1026
+
1027
+ // MQTT Schema v0.7.2+ message_type
1028
+ uint16_t messageType = 602; // MESH_ALERT
1029
+
1030
+ MeshAlertPackage() : BroadcastPackage(602) {}
1031
+
1032
+ MeshAlertPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
1033
+ JsonArray alertsArray = jsonObj["alerts"];
1034
+ alertCount = alertsArray.size();
1035
+ if (alertCount > 20) alertCount = 20;
1036
+
1037
+ for (uint8_t i = 0; i < alertCount; i++) {
1038
+ JsonObject alert = alertsArray[i];
1039
+ alerts[i].alertType = alert["type"];
1040
+ alerts[i].severity = alert["severity"];
1041
+ alerts[i].message = alert["msg"].as<TSTRING>();
1042
+ alerts[i].nodeId = alert["nodeId"];
1043
+ alerts[i].metricValue = alert["value"];
1044
+ alerts[i].threshold = alert["threshold"];
1045
+ alerts[i].alertId = alert["alertId"];
1046
+ }
1047
+
1048
+ messageType = jsonObj["message_type"] | 602;
1049
+ }
1050
+
1051
+ JsonObject addTo(JsonObject&& jsonObj) const {
1052
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
1053
+
1054
+ JsonArray alertsArray = jsonObj["alerts"].to<JsonArray>();
1055
+ for (uint8_t i = 0; i < alertCount; i++) {
1056
+ JsonObject alert = alertsArray.add<JsonObject>();
1057
+ alert["type"] = alerts[i].alertType;
1058
+ alert["severity"] = alerts[i].severity;
1059
+ alert["msg"] = alerts[i].message;
1060
+ alert["nodeId"] = alerts[i].nodeId;
1061
+ alert["value"] = alerts[i].metricValue;
1062
+ alert["threshold"] = alerts[i].threshold;
1063
+ alert["alertId"] = alerts[i].alertId;
1064
+ }
1065
+
1066
+ jsonObj["alertCount"] = alertCount;
1067
+ jsonObj["message_type"] = messageType;
1068
+
1069
+ return jsonObj;
1070
+ }
1071
+
1072
+ #if ARDUINOJSON_VERSION_MAJOR < 7
1073
+ size_t jsonObjectSize() const {
1074
+ size_t size = JSON_OBJECT_SIZE(noJsonFields + 2) +
1075
+ JSON_ARRAY_SIZE(alertCount) +
1076
+ alertCount * JSON_OBJECT_SIZE(7);
1077
+ for (uint8_t i = 0; i < alertCount; i++) {
1078
+ size += alerts[i].message.length();
1079
+ }
1080
+ return size;
1081
+ }
1082
+ #endif
1083
+ };
1084
+
1085
+ /**
1086
+ * @brief Mesh bridge package (Type 603 - MESH_BRIDGE)
1087
+ *
1088
+ * Encapsulates native mesh protocol messages for bridging.
1089
+ * Type ID 603 for MESH_BRIDGE per mqtt-schema v0.7.2+.
1090
+ */
1091
+ class MeshBridgePackage : public painlessmesh::plugin::BroadcastPackage {
1092
+ public:
1093
+ uint8_t meshProtocol = 0; // 0=painlessMesh, 1=esp-now, 2=ble-mesh, etc.
1094
+ uint32_t fromNodeId = 0; // Source node ID
1095
+ uint32_t toNodeId = 0; // Destination node ID (0=broadcast)
1096
+ uint16_t meshType = 0; // Mesh protocol-specific message type
1097
+ TSTRING rawPayload = ""; // Raw payload (hex/base64 encoded)
1098
+ int8_t rssi = 0; // Signal strength
1099
+ uint8_t hopCount = 0; // Number of hops
1100
+ uint32_t meshTimestamp = 0; // Mesh protocol timestamp
1101
+ uint32_t gatewayNodeId = 0; // Gateway's node ID
1102
+ TSTRING meshNetworkId = ""; // Mesh network identifier
1103
+
1104
+ // MQTT Schema v0.7.2+ message_type
1105
+ uint16_t messageType = 603; // MESH_BRIDGE
1106
+
1107
+ MeshBridgePackage() : BroadcastPackage(603) {}
1108
+
1109
+ MeshBridgePackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
1110
+ meshProtocol = jsonObj["protocol"];
1111
+ fromNodeId = jsonObj["fromNode"];
1112
+ toNodeId = jsonObj["toNode"];
1113
+ meshType = jsonObj["meshType"];
1114
+ rawPayload = jsonObj["payload"].as<TSTRING>();
1115
+ rssi = jsonObj["rssi"];
1116
+ hopCount = jsonObj["hops"];
1117
+ meshTimestamp = jsonObj["meshTs"];
1118
+ gatewayNodeId = jsonObj["gateway"];
1119
+ meshNetworkId = jsonObj["meshId"].as<TSTRING>();
1120
+ messageType = jsonObj["message_type"] | 603;
1121
+ }
1122
+
1123
+ JsonObject addTo(JsonObject&& jsonObj) const {
1124
+ jsonObj = BroadcastPackage::addTo(std::move(jsonObj));
1125
+
1126
+ jsonObj["protocol"] = meshProtocol;
1127
+ jsonObj["fromNode"] = fromNodeId;
1128
+ jsonObj["toNode"] = toNodeId;
1129
+ jsonObj["meshType"] = meshType;
1130
+ jsonObj["payload"] = rawPayload;
1131
+ jsonObj["rssi"] = rssi;
1132
+ jsonObj["hops"] = hopCount;
1133
+ jsonObj["meshTs"] = meshTimestamp;
1134
+ jsonObj["gateway"] = gatewayNodeId;
1135
+ jsonObj["meshId"] = meshNetworkId;
1136
+ jsonObj["message_type"] = messageType;
1137
+
1138
+ return jsonObj;
1139
+ }
1140
+
1141
+ #if ARDUINOJSON_VERSION_MAJOR < 7
1142
+ size_t jsonObjectSize() const {
1143
+ return JSON_OBJECT_SIZE(noJsonFields + 11) + rawPayload.length() +
1144
+ meshNetworkId.length();
1145
+ }
1146
+ #endif
1147
+ };
1148
+
239
1149
  } // namespace alteriom
240
1150
 
241
1151
  #endif // ALTERIOM_SENSOR_PACKAGE_HPP