@alteriom/painlessmesh 1.7.7 → 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.
- package/CHANGELOG.md +71 -1
- package/README.md +15 -8
- package/docs/API_DESIGN_GUIDELINES.md +414 -0
- package/docs/BOOLEAN_NAMING_CONVENTION.md +235 -0
- package/docs/alteriom/overview.md +23 -0
- package/docs/releases/RELEASE_SUMMARY_v1.7.7.md +1 -1
- package/docs/v1.7.7_MQTT_IMPROVEMENTS.md +21 -3
- package/examples/alteriom/README.md +13 -1
- package/examples/alteriom/alteriom_sensor_package.hpp +357 -2
- package/library.json +1 -1
- package/library.properties +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -19,7 +19,77 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
19
19
|
|
|
20
20
|
- TBD
|
|
21
21
|
|
|
22
|
-
## [1.7.
|
|
22
|
+
## [1.7.8] - 2025-11-04
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **BRIDGE_TO_INTERNET.md** - Comprehensive documentation for bridging mesh networks to the Internet via WiFi router
|
|
27
|
+
- Complete code examples with AP+STA mode configuration
|
|
28
|
+
- WiFi channel matching requirements and best practices
|
|
29
|
+
- Links to working bridge examples (basic, MQTT, web server, enhanced MQTT)
|
|
30
|
+
- Architecture diagrams and forwarding patterns
|
|
31
|
+
- Troubleshooting and additional resources
|
|
32
|
+
|
|
33
|
+
- **Enhanced StatusPackage** - New organization and sensor configuration fields
|
|
34
|
+
- Organization fields: `organizationId`, `organizationName`, `organizationDomain`
|
|
35
|
+
- Sensor configuration: `sensorTypes` array, `sensorConfig` JSON, `sensorInventory` array
|
|
36
|
+
- Separate JSON serialization keys for sensors data vs configuration
|
|
37
|
+
- CamelCase field naming convention for consistency
|
|
38
|
+
|
|
39
|
+
- **API Design Guidelines** - `docs/API_DESIGN_GUIDELINES.md`
|
|
40
|
+
- Field naming conventions (camelCase, units in field names)
|
|
41
|
+
- Boolean naming patterns (`is`, `has`, `should`, `can`)
|
|
42
|
+
- Time field naming with units (`_ms`, `_s`, `_us` suffixes)
|
|
43
|
+
- Serialization patterns and consistency rules
|
|
44
|
+
- Comprehensive validation tests
|
|
45
|
+
|
|
46
|
+
- **Manual Publishing Workflow** - `.github/workflows/manual-publish.yml`
|
|
47
|
+
- On-demand NPM and GitHub Packages publishing
|
|
48
|
+
- Fixes cases where automated release doesn't trigger package publication
|
|
49
|
+
- Configurable options for selective publishing
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- **Time Field Naming Convention** - Consistent unit suffixes across all packages
|
|
54
|
+
- `collectionTimestamp` → `collectionTimestamp_ms`
|
|
55
|
+
- `avgResponseTime` → `avgResponseTime_us`
|
|
56
|
+
- `estimatedTimeToFailure` → `estimatedTimeToFailure_s`
|
|
57
|
+
- All time fields now include explicit units in field names
|
|
58
|
+
- Documentation: `docs/architecture/TIME_FIELD_NAMING.md`
|
|
59
|
+
|
|
60
|
+
- **StatusPackage JSON Structure** - Improved field organization
|
|
61
|
+
- Sensor data uses `sensors` key (array of readings)
|
|
62
|
+
- Sensor configuration uses separate keys (`sensorTypes`, `sensorConfig`, `sensorInventory`)
|
|
63
|
+
- No key collisions between runtime data and configuration
|
|
64
|
+
- Unconditional serialization for predictable JSON structure
|
|
65
|
+
|
|
66
|
+
- **MQTT Retry Logic** - Fixed serialization to include all retry fields
|
|
67
|
+
- Proper condition for including retry configuration
|
|
68
|
+
- Epsilon comparison for floating-point backoff multiplier
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
|
|
72
|
+
- **CI Pipeline** - Made validate-release depend on CI completion
|
|
73
|
+
- Prevents release validation from running before tests complete
|
|
74
|
+
- Ensures all tests pass before release can proceed
|
|
75
|
+
|
|
76
|
+
- **ArduinoJson API** - Updated deprecated API usage
|
|
77
|
+
- Fixed deprecated JsonVariant::is<JsonObject>() calls
|
|
78
|
+
- Updated to ArduinoJson 7.x compatible patterns
|
|
79
|
+
- Code formatting improvements
|
|
80
|
+
|
|
81
|
+
- **ESP8266 Compatibility** - Fixed `getDeviceId()` function
|
|
82
|
+
- Added proper ESP8266 implementation in mqttTopologyTest
|
|
83
|
+
- Platform-specific device ID retrieval
|
|
84
|
+
|
|
85
|
+
- **Documentation** - Multiple improvements
|
|
86
|
+
- Fixed v1.7.7 release date in documentation
|
|
87
|
+
- Added comprehensive mqtt-schema v0.7.2+ message type codes table
|
|
88
|
+
- Corrected CommandPackage type number (400, not 201)
|
|
89
|
+
- Enhanced Alteriom Extensions section in README
|
|
90
|
+
- Added GitHub Packages authentication for npm install
|
|
91
|
+
|
|
92
|
+
## [1.7.7] - 2025-11-05
|
|
23
93
|
|
|
24
94
|
### Added
|
|
25
95
|
|
package/README.md
CHANGED
|
@@ -245,9 +245,9 @@ void receivedCallback(uint32_t from, String& msg) {
|
|
|
245
245
|
| Type | Class | Purpose | Fields |
|
|
246
246
|
|------|-------|---------|--------|
|
|
247
247
|
| 200 | `SensorPackage` | Environmental data | `temperature`, `humidity`, `pressure`, `sensorId`, `timestamp`, `batteryLevel` |
|
|
248
|
-
| 201 | `CommandPackage` | Device control | `command`, `targetDevice`, `parameters`, `commandId` |
|
|
249
248
|
| 202 | `StatusPackage` | Health monitoring | `deviceStatus`, `uptime`, `freeMemory`, `wifiStrength`, `firmwareVersion` |
|
|
250
249
|
| 204 | `MetricsPackage` | Sensor metrics (v1.7.7+, aligns with schema v0.7.2+) | `cpuUsage`, `freeHeap`, `bytesReceived`, `currentThroughput`, `connectionQuality`, `wifiRSSI` |
|
|
250
|
+
| 400 | `CommandPackage` | Device control (v1.7.7+, moved from 201) | `command`, `targetDevice`, `parameters`, `commandId` |
|
|
251
251
|
| 600 | `MeshNodeListPackage` | Mesh node list (v1.7.7+, MESH_NODE_LIST) | `nodes[]` (nodeId, status, lastSeen, signalStrength), `nodeCount`, `meshId` |
|
|
252
252
|
| 601 | `MeshTopologyPackage` | Mesh topology (v1.7.7+, MESH_TOPOLOGY) | `connections[]` (fromNode, toNode, linkQuality, latencyMs), `rootNode` |
|
|
253
253
|
| 602 | `MeshAlertPackage` | Mesh alerts (v1.7.7+, MESH_ALERT) | `alerts[]` (alertType, severity, message, nodeId), `alertCount` |
|
|
@@ -282,16 +282,23 @@ void receivedCallback(uint32_t from, String& msg) {
|
|
|
282
282
|
- **Event Coordination** - Synchronized displays, distributed processing
|
|
283
283
|
- **Bridge Networks** - Connect mesh to WiFi/Internet/MQTT
|
|
284
284
|
|
|
285
|
-
##
|
|
285
|
+
## Development Version: v1.7.8
|
|
286
286
|
|
|
287
|
-
**
|
|
287
|
+
**In Development** - Next release after v1.7.7
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
- ✅ **ESP32 & ESP8266** - All platforms now compile successfully
|
|
291
|
-
- ✅ **FreeRTOS Stability** - Maintained ~85% crash reduction on ESP32
|
|
292
|
-
- 🚨 **v1.7.4/v1.7.5 Users** - Upgrade immediately (those versions don't compile)
|
|
289
|
+
See [CHANGELOG](CHANGELOG.md) for upcoming changes.
|
|
293
290
|
|
|
294
|
-
|
|
291
|
+
## Latest Release: v1.7.7 (November 5, 2025)
|
|
292
|
+
|
|
293
|
+
**MQTT Schema v0.7.2 Compliance with Enhanced Monitoring**:
|
|
294
|
+
|
|
295
|
+
- ✅ **MetricsPackage (Type 204)** - Comprehensive performance metrics for real-time monitoring
|
|
296
|
+
- ✅ **HealthCheckPackage (Type 605)** - Proactive health monitoring with problem detection
|
|
297
|
+
- ✅ **Mesh Topology Packages** - Complete network visualization (Types 600-603)
|
|
298
|
+
- ✅ **Enhanced MQTT Bridge** - On-demand metrics, health checks, and aggregated statistics
|
|
299
|
+
- ✅ **100% Backward Compatible** - All existing code continues to work
|
|
300
|
+
|
|
301
|
+
**[📋 Full Release Notes](docs/releases/RELEASE_SUMMARY_v1.7.7.md)** | **[🔖 CHANGELOG](CHANGELOG.md)**
|
|
295
302
|
|
|
296
303
|
## Getting Help
|
|
297
304
|
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# API Design Guidelines for Alteriom Packages
|
|
2
|
+
|
|
3
|
+
This document provides guidelines for designing consistent and maintainable JSON configuration structures in Alteriom packages, particularly for StatusPackage and related message types.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Nesting vs Flat Structure Guidelines](#nesting-vs-flat-structure-guidelines)
|
|
9
|
+
- [Current Structure Patterns](#current-structure-patterns)
|
|
10
|
+
- [Decision Tree](#decision-tree)
|
|
11
|
+
- [Examples](#examples)
|
|
12
|
+
- [Best Practices](#best-practices)
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Alteriom packages use JSON serialization for configuration and status data. This document establishes clear patterns for when to use nested structures versus flat key-value pairs to ensure consistency and maintainability across the codebase.
|
|
17
|
+
|
|
18
|
+
**Related Issues:**
|
|
19
|
+
- [Issue #28](https://github.com/Alteriom/painlessMesh/issues/28) - Inconsistent Nested vs Flat Configuration Structure
|
|
20
|
+
- [Issue #29](https://github.com/Alteriom/painlessMesh/issues/29) - Inconsistent Optional vs Required Field Serialization Pattern
|
|
21
|
+
- [PR #36](https://github.com/Alteriom/painlessMesh/pull/36) - Documented nesting patterns (this file)
|
|
22
|
+
- [PR #37](https://github.com/Alteriom/painlessMesh/pull/37) - Removed conditional serialization for predictable JSON structure
|
|
23
|
+
|
|
24
|
+
### Key Principles
|
|
25
|
+
|
|
26
|
+
1. **Consistency over perfection** - Follow existing patterns in similar sections
|
|
27
|
+
2. **Simplicity by default** - Use flat structures unless nesting provides clear benefits
|
|
28
|
+
3. **Future-proof** - Consider extensibility when designing structures
|
|
29
|
+
4. **Clarity** - Structure should reflect logical grouping
|
|
30
|
+
5. **Predictable structure** - All sections always serialize with default values (addressed in PR #37)
|
|
31
|
+
|
|
32
|
+
## Nesting vs Flat Structure Guidelines
|
|
33
|
+
|
|
34
|
+
### Use FLAT Structure When:
|
|
35
|
+
|
|
36
|
+
- **< 4 total fields** in a configuration section
|
|
37
|
+
- **No clear logical subsystems** within the section
|
|
38
|
+
- **Simple value types** without complex relationships
|
|
39
|
+
- **Low likelihood of expansion** in the future
|
|
40
|
+
|
|
41
|
+
**Benefits:**
|
|
42
|
+
- Simpler code (fewer nested object creations)
|
|
43
|
+
- Easier to parse and validate
|
|
44
|
+
- More concise JSON output
|
|
45
|
+
- Faster serialization/deserialization
|
|
46
|
+
|
|
47
|
+
### Use NESTED Structure When:
|
|
48
|
+
|
|
49
|
+
- **3+ fields belong to same logical subsystem**
|
|
50
|
+
- **Clear semantic grouping** exists
|
|
51
|
+
- **Future extensibility anticipated** for subsystem
|
|
52
|
+
- **Subsystem has distinct meaning** separate from parent
|
|
53
|
+
|
|
54
|
+
**Benefits:**
|
|
55
|
+
- Better logical organization
|
|
56
|
+
- Easier to add related fields without cluttering parent
|
|
57
|
+
- Clear separation of concerns
|
|
58
|
+
- More extensible architecture
|
|
59
|
+
|
|
60
|
+
## Current Structure Patterns
|
|
61
|
+
|
|
62
|
+
**Important Note (as of PR #37):** All configuration sections now **always serialize** regardless of whether values are at their defaults. This provides predictable JSON structure and eliminates the need for consumers to check key existence. Default values (0, false, "") clearly indicate "not configured" state.
|
|
63
|
+
|
|
64
|
+
### Flat Sections (No Nesting)
|
|
65
|
+
|
|
66
|
+
These sections use simple key-value pairs at a single level:
|
|
67
|
+
|
|
68
|
+
#### Display Configuration
|
|
69
|
+
```json
|
|
70
|
+
"display_config": {
|
|
71
|
+
"enabled": true,
|
|
72
|
+
"brightness": 128,
|
|
73
|
+
"timeout_ms": 30000,
|
|
74
|
+
"timeout_s": 30
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Rationale:** Only 3-4 fields, all directly related to display, no subsystems.
|
|
79
|
+
|
|
80
|
+
#### Power Configuration
|
|
81
|
+
```json
|
|
82
|
+
"power_config": {
|
|
83
|
+
"deep_sleep_enabled": false,
|
|
84
|
+
"deep_sleep_interval_ms": 300000,
|
|
85
|
+
"deep_sleep_interval_s": 300,
|
|
86
|
+
"battery_percent": 85
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Rationale:** Small number of fields (4), even though battery and sleep are different concerns, nesting would add unnecessary complexity.
|
|
91
|
+
|
|
92
|
+
#### MQTT Retry Configuration
|
|
93
|
+
```json
|
|
94
|
+
"mqtt_retry": {
|
|
95
|
+
"max_attempts": 5,
|
|
96
|
+
"circuit_breaker_ms": 60000,
|
|
97
|
+
"circuit_breaker_s": 60,
|
|
98
|
+
"hourly_retry_enabled": true,
|
|
99
|
+
"initial_retry_ms": 1000,
|
|
100
|
+
"initial_retry_s": 1,
|
|
101
|
+
"max_retry_ms": 30000,
|
|
102
|
+
"max_retry_s": 30,
|
|
103
|
+
"backoff_multiplier": 2.0
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Rationale:** While this has 9 fields with distinct concerns (retry policy vs backoff strategy), it remains flat for simplicity. The retry configuration is cohesive enough that nesting would fragment it without clear benefit.
|
|
108
|
+
|
|
109
|
+
### Nested Sections (With Subsystems)
|
|
110
|
+
|
|
111
|
+
These sections use nested objects for logical grouping:
|
|
112
|
+
|
|
113
|
+
#### Sensor Configuration with Calibration
|
|
114
|
+
```json
|
|
115
|
+
"sensors": {
|
|
116
|
+
"read_interval_ms": 30000,
|
|
117
|
+
"read_interval_s": 30,
|
|
118
|
+
"transmission_interval_ms": 60000,
|
|
119
|
+
"transmission_interval_s": 60,
|
|
120
|
+
"calibration": {
|
|
121
|
+
"temperature_offset": 0.5,
|
|
122
|
+
"humidity_offset": -2.0,
|
|
123
|
+
"pressure_offset": 0.0
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Rationale:** Calibration is a distinct subsystem with its own semantic meaning. It's optional, extensible, and conceptually separate from sensor timing configuration.
|
|
129
|
+
|
|
130
|
+
**Benefits of nesting here:**
|
|
131
|
+
- Calibration can be added/removed as a unit
|
|
132
|
+
- Easy to add more calibration fields without cluttering main sensors object
|
|
133
|
+
- Clear semantic boundary - calibration is a specific tuning operation
|
|
134
|
+
|
|
135
|
+
#### Organization Metadata
|
|
136
|
+
```json
|
|
137
|
+
"organization": {
|
|
138
|
+
"organizationId": "org-123",
|
|
139
|
+
"customerId": "cust-456",
|
|
140
|
+
"deviceGroup": "sensors",
|
|
141
|
+
"device_name": "sensor-01",
|
|
142
|
+
"device_location": "warehouse-a",
|
|
143
|
+
"device_secret_set": true
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Rationale:** Organization metadata is an optional, self-contained subsystem that may not be present on all devices.
|
|
148
|
+
|
|
149
|
+
## Decision Tree
|
|
150
|
+
|
|
151
|
+
Use this decision tree when designing new configuration sections:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
START: New configuration section needed
|
|
155
|
+
│
|
|
156
|
+
├─ Does section have < 4 fields?
|
|
157
|
+
│ ├─ YES → Use FLAT structure
|
|
158
|
+
│ └─ NO → Continue
|
|
159
|
+
│
|
|
160
|
+
├─ Do 3+ fields belong to same logical subsystem?
|
|
161
|
+
│ ├─ NO → Use FLAT structure
|
|
162
|
+
│ └─ YES → Continue
|
|
163
|
+
│
|
|
164
|
+
├─ Is subsystem likely to grow in future?
|
|
165
|
+
│ ├─ NO → Consider FLAT (unless strong semantic grouping)
|
|
166
|
+
│ └─ YES → Continue
|
|
167
|
+
│
|
|
168
|
+
├─ Would nesting improve clarity significantly?
|
|
169
|
+
│ ├─ NO → Use FLAT structure
|
|
170
|
+
│ └─ YES → Use NESTED structure
|
|
171
|
+
│
|
|
172
|
+
END
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Examples
|
|
176
|
+
|
|
177
|
+
### Example 1: Adding OTA Configuration (Flat Approach)
|
|
178
|
+
|
|
179
|
+
**Scenario:** Adding Over-The-Air update configuration with 3 fields.
|
|
180
|
+
|
|
181
|
+
```cpp
|
|
182
|
+
// C++ Fields
|
|
183
|
+
bool otaEnabled = false;
|
|
184
|
+
TSTRING otaServer = "";
|
|
185
|
+
uint16_t otaPort = 0;
|
|
186
|
+
|
|
187
|
+
// JSON Serialization (FLAT)
|
|
188
|
+
JsonObject ota = jsonObj["ota"].to<JsonObject>();
|
|
189
|
+
ota["enabled"] = otaEnabled;
|
|
190
|
+
ota["server"] = otaServer;
|
|
191
|
+
ota["port"] = otaPort;
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Result:**
|
|
195
|
+
```json
|
|
196
|
+
"ota": {
|
|
197
|
+
"enabled": true,
|
|
198
|
+
"server": "ota.example.com",
|
|
199
|
+
"port": 8080
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Decision:** Keep FLAT - only 3 fields, no subsystems.
|
|
204
|
+
|
|
205
|
+
### Example 2: Adding Sensor Thresholds (Nested Approach)
|
|
206
|
+
|
|
207
|
+
**Scenario:** Adding temperature, humidity, and pressure thresholds to sensor configuration.
|
|
208
|
+
|
|
209
|
+
```cpp
|
|
210
|
+
// C++ Fields (added to existing sensor config)
|
|
211
|
+
double tempThresholdMin = -40.0;
|
|
212
|
+
double tempThresholdMax = 85.0;
|
|
213
|
+
double humidityThresholdMin = 0.0;
|
|
214
|
+
double humidityThresholdMax = 100.0;
|
|
215
|
+
|
|
216
|
+
// JSON Serialization (NESTED under sensors)
|
|
217
|
+
JsonObject sensors = jsonObj["sensors"].to<JsonObject>();
|
|
218
|
+
// ... existing sensor fields ...
|
|
219
|
+
|
|
220
|
+
JsonObject thresholds = sensors["thresholds"].to<JsonObject>();
|
|
221
|
+
thresholds["temperature_min"] = tempThresholdMin;
|
|
222
|
+
thresholds["temperature_max"] = tempThresholdMax;
|
|
223
|
+
thresholds["humidity_min"] = humidityThresholdMin;
|
|
224
|
+
thresholds["humidity_max"] = humidityThresholdMax;
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Result:**
|
|
228
|
+
```json
|
|
229
|
+
"sensors": {
|
|
230
|
+
"read_interval_ms": 30000,
|
|
231
|
+
"calibration": { ... },
|
|
232
|
+
"thresholds": {
|
|
233
|
+
"temperature_min": -40.0,
|
|
234
|
+
"temperature_max": 85.0,
|
|
235
|
+
"humidity_min": 0.0,
|
|
236
|
+
"humidity_max": 100.0
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Decision:** Use NESTED - 4+ fields, clear subsystem (alerting/validation logic), logical grouping.
|
|
242
|
+
|
|
243
|
+
### Example 3: Adding Encoding Configuration (Flat Approach)
|
|
244
|
+
|
|
245
|
+
**Scenario:** Adding message encoding/compression settings.
|
|
246
|
+
|
|
247
|
+
```cpp
|
|
248
|
+
// C++ Fields
|
|
249
|
+
bool compressionEnabled = false;
|
|
250
|
+
TSTRING encodingType = "json";
|
|
251
|
+
uint8_t compressionLevel = 6;
|
|
252
|
+
|
|
253
|
+
// JSON Serialization (FLAT)
|
|
254
|
+
JsonObject encoding = jsonObj["encoding"].to<JsonObject>();
|
|
255
|
+
encoding["compression_enabled"] = compressionEnabled;
|
|
256
|
+
encoding["encoding_type"] = encodingType;
|
|
257
|
+
encoding["compression_level"] = compressionLevel;
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Result:**
|
|
261
|
+
```json
|
|
262
|
+
"encoding": {
|
|
263
|
+
"compression_enabled": false,
|
|
264
|
+
"encoding_type": "json",
|
|
265
|
+
"compression_level": 6
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Decision:** Keep FLAT - only 3 fields, cohesive purpose.
|
|
270
|
+
|
|
271
|
+
## Best Practices
|
|
272
|
+
|
|
273
|
+
### 1. Be Conservative with Nesting
|
|
274
|
+
|
|
275
|
+
**Rationale:** Flat structures are simpler to implement and consume.
|
|
276
|
+
|
|
277
|
+
**Rule:** When in doubt, start flat. Nesting can be added later if needed, but removing nesting is a breaking change.
|
|
278
|
+
|
|
279
|
+
### 2. Consider Backward Compatibility
|
|
280
|
+
|
|
281
|
+
When adding fields to existing sections:
|
|
282
|
+
|
|
283
|
+
```cpp
|
|
284
|
+
// Deserialization with backward compatibility
|
|
285
|
+
displayTimeout = displayConfig["timeout_ms"] | displayConfig["timeout"] | 0;
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
This allows reading old format (`timeout`) while preferring new format (`timeout_ms`).
|
|
289
|
+
|
|
290
|
+
### 3. Group Optional Subsystems
|
|
291
|
+
|
|
292
|
+
**Good:**
|
|
293
|
+
```json
|
|
294
|
+
"sensors": {
|
|
295
|
+
"read_interval_ms": 30000,
|
|
296
|
+
"calibration": { ... } // Optional, can be omitted entirely
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Avoid:**
|
|
301
|
+
```json
|
|
302
|
+
"sensors": {
|
|
303
|
+
"read_interval_ms": 30000,
|
|
304
|
+
"temperature_offset": 0.0, // Mixed levels - unclear if calibration is a concept
|
|
305
|
+
"humidity_offset": 0.0,
|
|
306
|
+
"pressure_offset": 0.0
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### 4. Maintain Consistent Field Naming
|
|
311
|
+
|
|
312
|
+
Follow existing conventions:
|
|
313
|
+
- Time fields: Follow [Time Field Naming Convention](../examples/alteriom/alteriom_sensor_package.hpp#L10-L55)
|
|
314
|
+
- Boolean fields: Follow [Boolean Naming Convention](BOOLEAN_NAMING_CONVENTION.md)
|
|
315
|
+
- Use snake_case for JSON keys
|
|
316
|
+
- Use camelCase for C++ field names
|
|
317
|
+
|
|
318
|
+
### 5. Document Structure Decisions
|
|
319
|
+
|
|
320
|
+
Add comments explaining nesting choices:
|
|
321
|
+
|
|
322
|
+
```cpp
|
|
323
|
+
// Display configuration (flat - only 3 fields, no subsystems)
|
|
324
|
+
JsonObject displayConfig = jsonObj["display_config"].to<JsonObject>();
|
|
325
|
+
|
|
326
|
+
// Sensor configuration with nested calibration (calibration is distinct subsystem)
|
|
327
|
+
JsonObject sensors = jsonObj["sensors"].to<JsonObject>();
|
|
328
|
+
JsonObject calibration = sensors["calibration"].to<JsonObject>();
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 6. Test Structure Consistency
|
|
332
|
+
|
|
333
|
+
Create tests to validate structure patterns:
|
|
334
|
+
|
|
335
|
+
```cpp
|
|
336
|
+
TEST(StatusPackage, StructureConsistency) {
|
|
337
|
+
alteriom::StatusPackage pkg;
|
|
338
|
+
pkg.tempOffset = 0.5;
|
|
339
|
+
|
|
340
|
+
JsonDocument doc;
|
|
341
|
+
JsonObject obj = doc.to<JsonObject>();
|
|
342
|
+
pkg.addTo(std::move(obj));
|
|
343
|
+
|
|
344
|
+
// Verify nested structures
|
|
345
|
+
REQUIRE(obj["sensors"]["calibration"].is<JsonObject>());
|
|
346
|
+
|
|
347
|
+
// Verify flat structures remain flat
|
|
348
|
+
REQUIRE(obj["display_config"]["enabled"].is<bool>());
|
|
349
|
+
REQUIRE_FALSE(obj["display_config"].containsKey("nested_section"));
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Migration Path
|
|
354
|
+
|
|
355
|
+
If restructuring becomes necessary:
|
|
356
|
+
|
|
357
|
+
1. **Add new structure** while maintaining old structure
|
|
358
|
+
2. **Support both formats** in deserialization
|
|
359
|
+
3. **Deprecate old format** (document in release notes)
|
|
360
|
+
4. **Remove old format** in next major version
|
|
361
|
+
|
|
362
|
+
```cpp
|
|
363
|
+
// Example: Supporting both flat and nested
|
|
364
|
+
if (jsonObj["network"]["wifi"].is<JsonObject>()) {
|
|
365
|
+
// New nested format
|
|
366
|
+
JsonObject wifi = jsonObj["network"]["wifi"];
|
|
367
|
+
wifiSSID = wifi["ssid"].as<TSTRING>();
|
|
368
|
+
} else {
|
|
369
|
+
// Old flat format (deprecated)
|
|
370
|
+
wifiSSID = jsonObj["network"]["wifi_ssid"].as<TSTRING>();
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Summary
|
|
375
|
+
|
|
376
|
+
| Criteria | Flat Structure | Nested Structure |
|
|
377
|
+
|----------|---------------|------------------|
|
|
378
|
+
| **Field Count** | < 4 fields | 3+ fields in subsystem |
|
|
379
|
+
| **Logical Grouping** | No clear subsystems | Clear semantic grouping |
|
|
380
|
+
| **Extensibility** | Low likelihood of growth | Anticipated expansion |
|
|
381
|
+
| **Complexity** | Simple values | Complex relationships |
|
|
382
|
+
| **Serialization** | Always present (PR #37) | Always present (PR #37) |
|
|
383
|
+
| **Examples** | display_config, power_config, ota, encoding, mqtt_retry | sensors.calibration, organization |
|
|
384
|
+
|
|
385
|
+
**Golden Rule:** When uncertain, prefer flat structures. Nesting should provide clear organizational or extensibility benefits to justify the added complexity.
|
|
386
|
+
|
|
387
|
+
### Resolution of Issue #28
|
|
388
|
+
|
|
389
|
+
Issue #28 identified inconsistent nesting patterns and proposed three options:
|
|
390
|
+
|
|
391
|
+
1. **Option 1: Keep Current Structure (Document Pattern)** ✅ **ADOPTED**
|
|
392
|
+
2. Option 2: Nest Network Configuration (Breaking Change)
|
|
393
|
+
3. Option 3: Nest MQTT Retry Backoff Settings (Minimal Change)
|
|
394
|
+
|
|
395
|
+
**Decision Rationale:**
|
|
396
|
+
- Current flat structure for most sections (display_config, power_config, mqtt_retry) is functional and simple
|
|
397
|
+
- Only sensors.calibration uses nesting, which is justified by its semantic separation
|
|
398
|
+
- Avoiding breaking changes preserves compatibility with existing consumers
|
|
399
|
+
- Clear documentation (this file) addresses the inconsistency concern
|
|
400
|
+
- PR #37's unconditional serialization provides the predictable structure that was the real underlying concern
|
|
401
|
+
|
|
402
|
+
**Result:** The pattern is now documented and validated. Future additions should follow the decision tree in this document.
|
|
403
|
+
|
|
404
|
+
## References
|
|
405
|
+
|
|
406
|
+
- [StatusPackage Implementation](../examples/alteriom/alteriom_sensor_package.hpp)
|
|
407
|
+
- [Boolean Naming Convention](BOOLEAN_NAMING_CONVENTION.md)
|
|
408
|
+
- [Time Field Naming Convention](../examples/alteriom/alteriom_sensor_package.hpp#L10-L55)
|
|
409
|
+
- [Test Cases](../test/catch/catch_alteriom_packages.cpp)
|
|
410
|
+
|
|
411
|
+
## Revision History
|
|
412
|
+
|
|
413
|
+
- **2025-11-04 (PR #37)**: Updated to reflect unconditional serialization pattern - all sections always serialize with default values for predictable structure
|
|
414
|
+
- **2025-11-04 (PR #36)**: Initial version documenting StatusPackage nesting patterns in response to Issue #28
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Boolean Field Naming Convention
|
|
2
|
+
|
|
3
|
+
**Related Issue**: #27
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This document establishes the standard naming conventions for boolean fields in Alteriom packages, particularly in `StatusPackage`. Consistent naming patterns make the code self-documenting and help developers understand field semantics at a glance.
|
|
8
|
+
|
|
9
|
+
## Three Naming Patterns
|
|
10
|
+
|
|
11
|
+
### Pattern 1: `*Set` Suffix
|
|
12
|
+
|
|
13
|
+
**Purpose**: Indicates that required configuration data has been provided (typically for sensitive data like passwords or secrets).
|
|
14
|
+
|
|
15
|
+
**When to use**:
|
|
16
|
+
- Field represents whether configuration data exists
|
|
17
|
+
- Typically used for passwords, secrets, API keys, server URLs
|
|
18
|
+
- Does NOT indicate if the feature is active or working
|
|
19
|
+
|
|
20
|
+
**Examples**:
|
|
21
|
+
```cpp
|
|
22
|
+
bool deviceSecretSet = false; // Has device secret been configured?
|
|
23
|
+
bool wifiPasswordSet = false; // Has WiFi password been provided?
|
|
24
|
+
bool meshPasswordSet = false; // Has mesh password been provided?
|
|
25
|
+
bool otaServerSet = false; // Has OTA server URL been configured?
|
|
26
|
+
bool mqttBrokerSet = false; // Has MQTT broker been configured?
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Semantic meaning**:
|
|
30
|
+
- `true` = Configuration data has been provided
|
|
31
|
+
- `false` = Configuration data is missing or not yet provided
|
|
32
|
+
- Does NOT indicate the feature is enabled or currently working
|
|
33
|
+
|
|
34
|
+
### Pattern 2: `*Enabled` Suffix
|
|
35
|
+
|
|
36
|
+
**Purpose**: Indicates that a feature is currently active or turned on.
|
|
37
|
+
|
|
38
|
+
**When to use**:
|
|
39
|
+
- Field represents a feature toggle (on/off)
|
|
40
|
+
- User or system can enable/disable the feature
|
|
41
|
+
- Feature state is controllable and intentional
|
|
42
|
+
|
|
43
|
+
**Examples**:
|
|
44
|
+
```cpp
|
|
45
|
+
bool displayEnabled = false; // Is display feature enabled?
|
|
46
|
+
bool deepSleepEnabled = false; // Is deep sleep mode enabled?
|
|
47
|
+
bool mqttHourlyRetryEnabled = false; // Is hourly retry feature enabled?
|
|
48
|
+
bool otaEnabled = false; // Are OTA updates enabled?
|
|
49
|
+
bool encryptionEnabled = false; // Is data encryption enabled?
|
|
50
|
+
bool encodingEnabled = false; // Is data encoding enabled?
|
|
51
|
+
bool logTimestampEnabled = false; // Are log timestamps enabled?
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Semantic meaning**:
|
|
55
|
+
- `true` = Feature is currently active/turned on
|
|
56
|
+
- `false` = Feature is currently inactive/turned off
|
|
57
|
+
- Independent of whether required configuration exists
|
|
58
|
+
|
|
59
|
+
### Pattern 3: Runtime State (`is*` Prefix or `*Connected`)
|
|
60
|
+
|
|
61
|
+
**Purpose**: Indicates current runtime status or operational state (not configuration).
|
|
62
|
+
|
|
63
|
+
**When to use**:
|
|
64
|
+
- Field represents current operational status
|
|
65
|
+
- Status changes at runtime based on system behavior
|
|
66
|
+
- Not directly controlled by configuration
|
|
67
|
+
|
|
68
|
+
**Examples**:
|
|
69
|
+
```cpp
|
|
70
|
+
bool isConfigured = false; // Has device completed configuration?
|
|
71
|
+
bool mqttConnected = false; // Currently connected to MQTT broker?
|
|
72
|
+
bool meshIsRoot = false; // Is this node currently the mesh root?
|
|
73
|
+
bool isOnline = false; // Is device currently online?
|
|
74
|
+
bool wifiConnected = false; // Currently connected to WiFi?
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Semantic meaning**:
|
|
78
|
+
- `true` = Currently in this state
|
|
79
|
+
- `false` = Not currently in this state
|
|
80
|
+
- Reflects actual runtime conditions, not configuration
|
|
81
|
+
|
|
82
|
+
## Combining Patterns
|
|
83
|
+
|
|
84
|
+
A feature may legitimately have multiple boolean fields using different patterns:
|
|
85
|
+
|
|
86
|
+
### Example: OTA (Over-The-Air) Updates
|
|
87
|
+
|
|
88
|
+
```cpp
|
|
89
|
+
bool otaServerSet = false; // Has OTA server URL been configured? (*Set)
|
|
90
|
+
bool otaEnabled = false; // Are OTA updates enabled? (*Enabled)
|
|
91
|
+
bool otaInProgress = false; // Is an OTA update currently running? (is* / runtime state)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Valid states**:
|
|
95
|
+
- `otaServerSet=true, otaEnabled=false` → Server configured but feature disabled
|
|
96
|
+
- `otaServerSet=false, otaEnabled=true` → Feature enabled but no server (invalid/warning state)
|
|
97
|
+
- `otaServerSet=true, otaEnabled=true` → Fully configured and active
|
|
98
|
+
- `otaServerSet=true, otaEnabled=true, otaInProgress=true` → Update in progress
|
|
99
|
+
|
|
100
|
+
### Example: MQTT Connection
|
|
101
|
+
|
|
102
|
+
```cpp
|
|
103
|
+
bool mqttBrokerSet = false; // Has MQTT broker been configured? (*Set)
|
|
104
|
+
bool mqttEnabled = false; // Is MQTT feature enabled? (*Enabled)
|
|
105
|
+
bool mqttConnected = false; // Currently connected to broker? (runtime state)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Current StatusPackage Implementation
|
|
109
|
+
|
|
110
|
+
### Existing `*Set` Fields (Build 8057)
|
|
111
|
+
```cpp
|
|
112
|
+
bool deviceSecretSet = false; // Whether device secret is configured
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Potential additions** (if needed):
|
|
116
|
+
```cpp
|
|
117
|
+
bool wifiPasswordSet = false; // Whether WiFi password is configured
|
|
118
|
+
bool meshPasswordSet = false; // Whether mesh password is configured
|
|
119
|
+
bool mqttBrokerSet = false; // Whether MQTT broker is configured
|
|
120
|
+
bool otaServerSet = false; // Whether OTA server URL is configured
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Existing `*Enabled` Fields
|
|
124
|
+
```cpp
|
|
125
|
+
bool displayEnabled = false; // Display feature enabled
|
|
126
|
+
bool deepSleepEnabled = false; // Deep sleep feature enabled
|
|
127
|
+
bool mqttHourlyRetryEnabled = false; // Hourly retry feature enabled
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Potential additions** (if needed):
|
|
131
|
+
```cpp
|
|
132
|
+
bool meshEnabled = false; // Is WiFi mesh feature enabled?
|
|
133
|
+
bool otaEnabled = false; // Are OTA updates enabled?
|
|
134
|
+
bool encryptionEnabled = false; // Is encryption enabled?
|
|
135
|
+
bool encodingEnabled = false; // Is data encoding enabled?
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Potential Runtime State Fields
|
|
139
|
+
Currently, StatusPackage does not have explicit runtime state fields. If needed in the future:
|
|
140
|
+
|
|
141
|
+
```cpp
|
|
142
|
+
bool isConfigured = false; // Device has complete valid configuration
|
|
143
|
+
bool mqttConnected = false; // Currently connected to MQTT broker
|
|
144
|
+
bool meshIsRoot = false; // Currently acting as mesh root
|
|
145
|
+
bool wifiConnected = false; // Currently connected to WiFi
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Best Practices
|
|
149
|
+
|
|
150
|
+
### DO ✅
|
|
151
|
+
|
|
152
|
+
1. **Use `*Set` for configuration presence**
|
|
153
|
+
```cpp
|
|
154
|
+
bool deviceSecretSet = false; // ✅ Indicates if secret is configured
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
2. **Use `*Enabled` for feature toggles**
|
|
158
|
+
```cpp
|
|
159
|
+
bool displayEnabled = false; // ✅ Indicates if feature is on/off
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
3. **Use `is*` or `*Connected` for runtime state**
|
|
163
|
+
```cpp
|
|
164
|
+
bool isConfigured = false; // ✅ Indicates current state
|
|
165
|
+
bool mqttConnected = false; // ✅ Indicates connection status
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
4. **Document field purpose clearly**
|
|
169
|
+
```cpp
|
|
170
|
+
bool otaServerSet = false; // Has OTA server URL been configured? (Build XXXX)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### DON'T ❌
|
|
174
|
+
|
|
175
|
+
1. **Don't mix patterns without clear semantics**
|
|
176
|
+
```cpp
|
|
177
|
+
bool wifiSet = false; // ❌ Ambiguous - set to what? On/off or configured?
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
2. **Don't use generic boolean names**
|
|
181
|
+
```cpp
|
|
182
|
+
bool wifi = false; // ❌ Unclear meaning
|
|
183
|
+
bool display = false; // ❌ What about display?
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
3. **Don't use `*Set` for feature toggles**
|
|
187
|
+
```cpp
|
|
188
|
+
bool displaySet = false; // ❌ Confusing - use displayEnabled instead
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
4. **Don't use `*Enabled` for configuration presence**
|
|
192
|
+
```cpp
|
|
193
|
+
bool deviceSecretEnabled = false; // ❌ Confusing - use deviceSecretSet instead
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Validation
|
|
197
|
+
|
|
198
|
+
When adding new boolean fields, ask these questions:
|
|
199
|
+
|
|
200
|
+
1. **Does this field indicate configuration presence?**
|
|
201
|
+
- YES → Use `*Set` suffix
|
|
202
|
+
- Example: `mqttBrokerSet`, `deviceSecretSet`
|
|
203
|
+
|
|
204
|
+
2. **Does this field toggle a feature on/off?**
|
|
205
|
+
- YES → Use `*Enabled` suffix
|
|
206
|
+
- Example: `displayEnabled`, `otaEnabled`
|
|
207
|
+
|
|
208
|
+
3. **Does this field reflect current runtime state?**
|
|
209
|
+
- YES → Use `is*` prefix or `*Connected` suffix
|
|
210
|
+
- Example: `isConfigured`, `mqttConnected`
|
|
211
|
+
|
|
212
|
+
4. **Does this field fit multiple categories?**
|
|
213
|
+
- Consider creating separate fields for each semantic meaning
|
|
214
|
+
- Example: `otaServerSet` AND `otaEnabled` AND `otaInProgress`
|
|
215
|
+
|
|
216
|
+
## Benefits
|
|
217
|
+
|
|
218
|
+
1. **Self-Documenting Code**: Field names clearly indicate semantic meaning
|
|
219
|
+
2. **Reduced Confusion**: Developers immediately understand what each boolean represents
|
|
220
|
+
3. **Better API Design**: Consistent patterns across entire codebase
|
|
221
|
+
4. **Easier Onboarding**: New developers can infer meaning from field names
|
|
222
|
+
5. **Fewer Bugs**: Clear semantics reduce misunderstandings and implementation errors
|
|
223
|
+
|
|
224
|
+
## References
|
|
225
|
+
|
|
226
|
+
- StatusPackage implementation: `examples/alteriom/alteriom_sensor_package.hpp`
|
|
227
|
+
- Test validation: `test/catch/catch_alteriom_packages.cpp`
|
|
228
|
+
- Time field conventions: See header documentation in `alteriom_sensor_package.hpp`
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
**Document Version**: 1.0
|
|
233
|
+
**Last Updated**: 2025-11-04
|
|
234
|
+
**Status**: Active
|
|
235
|
+
**Applies To**: StatusPackage, EnhancedStatusPackage, and all future Alteriom packages
|
|
@@ -498,11 +498,34 @@ public:
|
|
|
498
498
|
3. **Validate under memory pressure**
|
|
499
499
|
4. **Test with maximum expected node count**
|
|
500
500
|
|
|
501
|
+
## Code Conventions
|
|
502
|
+
|
|
503
|
+
### Boolean Field Naming
|
|
504
|
+
|
|
505
|
+
Alteriom packages follow a consistent naming convention for boolean fields to improve code clarity:
|
|
506
|
+
|
|
507
|
+
- **`*Set` suffix**: Configuration data has been provided (e.g., `deviceSecretSet`)
|
|
508
|
+
- **`*Enabled` suffix**: Feature is currently active (e.g., `displayEnabled`)
|
|
509
|
+
- **`is*` prefix or `*Connected`**: Current runtime state (e.g., `mqttConnected`)
|
|
510
|
+
|
|
511
|
+
See [Boolean Naming Convention](../BOOLEAN_NAMING_CONVENTION.md) for complete guidelines.
|
|
512
|
+
|
|
513
|
+
### Time Field Naming
|
|
514
|
+
|
|
515
|
+
Time-based configuration fields follow a dual-unit convention:
|
|
516
|
+
|
|
517
|
+
- **Internal storage**: Always milliseconds (e.g., `sensorReadInterval`)
|
|
518
|
+
- **JSON serialization**: Both milliseconds (`_ms`) and seconds (`_s`) variants
|
|
519
|
+
- **JSON deserialization**: Read from milliseconds (`_ms`) variant
|
|
520
|
+
|
|
521
|
+
See package header documentation for complete details.
|
|
522
|
+
|
|
501
523
|
## Next Steps
|
|
502
524
|
|
|
503
525
|
- Learn about [Sensor Packages](sensor-packages.md) in detail
|
|
504
526
|
- Explore [Command System](command-system.md) implementation
|
|
505
527
|
- Study [Status Monitoring](status-monitoring.md) patterns
|
|
528
|
+
- Review [Boolean Naming Convention](../BOOLEAN_NAMING_CONVENTION.md) guidelines
|
|
506
529
|
- See [Tutorial Examples](../tutorials/sensor-networks.md) for hands-on practice
|
|
507
530
|
|
|
508
531
|
The Alteriom extensions provide a solid foundation for building robust IoT applications with painlessMesh. They demonstrate production-ready patterns while remaining flexible enough to adapt to your specific needs.
|
|
@@ -350,9 +350,27 @@ Packages now include the `message_type` field for 90% faster message classificat
|
|
|
350
350
|
}
|
|
351
351
|
```
|
|
352
352
|
|
|
353
|
-
**Message Type Codes:**
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
**Message Type Codes (mqtt-schema v0.7.2+):**
|
|
354
|
+
|
|
355
|
+
For performance optimization and standardized routing, all Alteriom packages include the `message_type` field:
|
|
356
|
+
|
|
357
|
+
| Code | Constant | Message Type | Category | Description | PainlessMesh Package |
|
|
358
|
+
|------|----------|--------------|----------|-------------|---------------------|
|
|
359
|
+
| 200 | SENSOR_DATA | sensor_data | telemetry | Sensor telemetry readings | SensorPackage |
|
|
360
|
+
| 202 | SENSOR_STATUS | sensor_status | telemetry | Sensor status change | StatusPackage |
|
|
361
|
+
| 204 | SENSOR_METRICS | sensor_metrics | telemetry | Sensor health and performance metrics | MetricsPackage |
|
|
362
|
+
| 400 | COMMAND | command | control | Device control command | CommandPackage |
|
|
363
|
+
| 600 | MESH_NODE_LIST | mesh_node_list | mesh | Mesh node inventory | MeshNodeListPackage |
|
|
364
|
+
| 601 | MESH_TOPOLOGY | mesh_topology | mesh | Mesh network topology | MeshTopologyPackage |
|
|
365
|
+
| 602 | MESH_ALERT | mesh_alert | mesh | Mesh network alert | MeshAlertPackage |
|
|
366
|
+
| 603 | MESH_BRIDGE | mesh_bridge | mesh | Mesh protocol bridge | MeshBridgePackage |
|
|
367
|
+
| 604 | MESH_STATUS | mesh_status | mesh | Mesh network health status | EnhancedStatusPackage |
|
|
368
|
+
| 605 | MESH_METRICS | mesh_metrics | mesh | Mesh network performance metrics | HealthCheckPackage |
|
|
369
|
+
|
|
370
|
+
**Key Points:**
|
|
371
|
+
- The `message_type` field enables 90% faster message classification compared to parsing JSON
|
|
372
|
+
- All Alteriom packages align with @alteriom/mqtt-schema v0.7.2+ standards
|
|
373
|
+
- Message type codes are consistent across MQTT bridge, gateway, and mesh nodes
|
|
356
374
|
|
|
357
375
|
## Implementation Guide
|
|
358
376
|
|
|
@@ -271,4 +271,16 @@ This validates:
|
|
|
271
271
|
- Package type consistency
|
|
272
272
|
- Field preservation
|
|
273
273
|
- Edge case handling
|
|
274
|
-
- Integration with painlessMesh plugin system
|
|
274
|
+
- Integration with painlessMesh plugin system
|
|
275
|
+
- JSON structure consistency (nested vs flat)
|
|
276
|
+
|
|
277
|
+
## Documentation
|
|
278
|
+
|
|
279
|
+
For developers adding new configuration fields to Alteriom packages:
|
|
280
|
+
|
|
281
|
+
- **[API Design Guidelines](../../docs/API_DESIGN_GUIDELINES.md)** - Comprehensive guide on when to use nested vs flat JSON structures
|
|
282
|
+
- **[Time Field Naming Convention](alteriom_sensor_package.hpp#L10-L55)** - How to handle time-based fields (ms/s variants)
|
|
283
|
+
- **[Boolean Naming Convention](../../docs/BOOLEAN_NAMING_CONVENTION.md)** - Consistent patterns for boolean fields (*Set, *Enabled, is*)
|
|
284
|
+
- **[JSON Structure Guidelines](alteriom_sensor_package.hpp#L57-L121)** - Quick reference for nesting patterns
|
|
285
|
+
|
|
286
|
+
These guidelines ensure consistency and maintainability across all Alteriom packages.
|
|
@@ -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
|
/**
|
|
@@ -94,6 +221,33 @@ class CommandPackage : public painlessmesh::plugin::SinglePackage {
|
|
|
94
221
|
|
|
95
222
|
/**
|
|
96
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
|
|
97
251
|
*/
|
|
98
252
|
class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
99
253
|
public:
|
|
@@ -107,6 +261,50 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
107
261
|
uint32_t responseToCommand = 0; // CommandId this is responding to
|
|
108
262
|
TSTRING responseMessage = ""; // Success/error message
|
|
109
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
|
+
|
|
110
308
|
StatusPackage() : BroadcastPackage(202) {} // Type ID 202 for Alteriom status
|
|
111
309
|
|
|
112
310
|
StatusPackage(JsonObject jsonObj) : BroadcastPackage(jsonObj) {
|
|
@@ -117,6 +315,69 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
117
315
|
firmwareVersion = jsonObj["fw"].as<TSTRING>();
|
|
118
316
|
responseToCommand = jsonObj["respTo"] | 0;
|
|
119
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
|
+
}
|
|
120
381
|
}
|
|
121
382
|
|
|
122
383
|
JsonObject addTo(JsonObject&& jsonObj) const {
|
|
@@ -130,13 +391,107 @@ class StatusPackage : public painlessmesh::plugin::BroadcastPackage {
|
|
|
130
391
|
jsonObj["respTo"] = responseToCommand;
|
|
131
392
|
jsonObj["respMsg"] = responseMessage;
|
|
132
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
|
+
|
|
133
454
|
return jsonObj;
|
|
134
455
|
}
|
|
135
456
|
|
|
136
457
|
#if ARDUINOJSON_VERSION_MAJOR < 7
|
|
137
458
|
size_t jsonObjectSize() const {
|
|
138
|
-
|
|
139
|
-
|
|
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;
|
|
140
495
|
}
|
|
141
496
|
#endif
|
|
142
497
|
};
|
package/library.json
CHANGED
package/library.properties
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name=AlteriomPainlessMesh
|
|
2
|
-
version=1.7.
|
|
2
|
+
version=1.7.8
|
|
3
3
|
author=Coopdis,Scotty Franzyshen,Edwin van Leeuwen,Germán Martín,Maximilian Schwarz,Doanh Doanh,Alteriom
|
|
4
4
|
maintainer=Alteriom
|
|
5
5
|
sentence=A painless way to setup a mesh with ESP8266 and ESP32 devices with Alteriom extensions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alteriom/painlessmesh",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"description": "painlessMesh is a user-friendly library for creating mesh networks with ESP8266 and ESP32 devices. This Alteriom fork includes additional packages for sensor data (SensorPackage), device commands (CommandPackage), and status monitoring (StatusPackage). It handles routing and network management automatically, so you can focus on your application. The library uses JSON-based messaging and syncs time across all nodes, making it ideal for coordinated behaviour like synchronized light displays or sensor networks reporting to a central node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"arduino",
|