Skip to content

Commit e42fb83

Browse files
authored
Merge pull request #29 from RTGS-Lab/feature/configuration-updates
Update to allow OTA configuration changes and dynamic sensor management
2 parents 4b4ebad + 5b68fe5 commit e42fb83

14 files changed

Lines changed: 1903 additions & 151 deletions

.github/workflows/release-workflow.yaml

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,37 @@ jobs:
9999
commit: ${{ steps.commit.outputs.updated-version-sha || github.sha }}
100100
token: ${{ steps.app-token.outputs.token }}
101101

102-
upload:
103-
name: Upload to Particle
102+
upload-to-particle:
103+
name: Upload to Particle Projects
104104
needs: release
105105
runs-on: ubuntu-latest
106106
# Only run if release job has completed and the firmware version was updated
107107
if: needs.release.outputs.firmware-version-updated == 'true'
108+
strategy:
109+
matrix:
110+
project:
111+
- name: "GEMS Demo"
112+
product_id_secret: "PARTICLE_GEMS_DEMO_PRODUCT_ID"
113+
- name: "Runk Lab (B SoM)"
114+
product_id_secret: "PARTICLE_RUNCK_LAB_BSOM_PRODUCT_ID"
115+
- name: "WinterTurf - v3 International"
116+
product_id_secret: "PARTICLE_WINTERTURF_INTERNATIONAL_PRODUCT_ID"
117+
- name: "WinterTurf - v3"
118+
product_id_secret: "PARTICLE_WINTERTURF_PRODUCT_ID"
119+
- name: "Plant Pathways"
120+
product_id_secret: "PARTICLE_PLANT_PATHWAYS_PRODUCT_ID"
121+
- name: "LCCMR Irrigation"
122+
product_id_secret: "PARTICLE_LCCMR_IRRIGATION_PRODUCT_ID"
123+
- name: "Roadside Turf"
124+
product_id_secret: "PARTICLE_ROADSIDE_TURF_PRODUCT_ID"
125+
- name: "PepsiCo"
126+
product_id_secret: "PARTICLE_PEPSICO_PRODUCT_ID"
127+
- name: "Stellenbosch"
128+
product_id_secret: "PARTICLE_STELLENBOSCH_PRODUCT_ID"
129+
- name: "Runk Lab (B5 SoM)"
130+
product_id_secret: "PARTICLE_RUNCK_LAB_B5SOM_PRODUCT_ID"
131+
- name: "LCCMR Irrigation Sensing"
132+
product_id_secret: "PARTICLE_LCCMR_IRRIGATION_SENSING_PRODUCT_ID"
108133
steps:
109134
- name: Checkout code
110135
uses: actions/checkout@v4
@@ -121,12 +146,16 @@ jobs:
121146
FIRMWARE=$(find ./release -name "*.bin" -type f | head -n 1)
122147
echo "firmware-path=$FIRMWARE" >> $GITHUB_OUTPUT
123148
124-
- name: Upload product firmware to Particle
149+
- name: Upload firmware to ${{ matrix.project.name }}
125150
uses: particle-iot/firmware-upload-action@v1
126151
with:
127152
particle-access-token: ${{ secrets.PARTICLE_ACCESS_TOKEN }}
128153
firmware-path: ${{ steps.find_binary.outputs.firmware-path }}
129154
firmware-version: ${{ needs.release.outputs.firmware-version }}
130-
product-id: ${{ secrets.PARTICLE_GEMS_DEMO_PRODUCT_ID }}
155+
product-id: ${{ secrets[matrix.project.product_id_secret] }}
131156
title: 'Firmware v${{ needs.release.outputs.firmware-version }}'
132-
description: '[Firmware v${{ needs.release.outputs.firmware-version }} GitHub Release](${{ needs.release.outputs.release-url }}'
157+
description: '[Firmware v${{ needs.release.outputs.firmware-version }} GitHub Release](${{ needs.release.outputs.release-url }})'
158+
159+
- name: Log upload success
160+
run: |
161+
echo "✅ Successfully uploaded firmware v${{ needs.release.outputs.firmware-version }} to ${{ matrix.project.name }}"

CONFIGURATION.md

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
### Configuration Management
2+
3+
#### Loading Configuration
4+
5+
Configuration is loaded at startup in the following priority order:
6+
7+
1. **SD Card**: `config.json` file on the SD card
8+
2. **Default**: Built-in default configuration if SD file not found
9+
10+
#### Updating Configuration
11+
12+
Configuration can be updated through:
13+
14+
1. **Cloud Function**: `updateConfig` Particle function
15+
- copy the intended config.json and paste it as and argument into updateConfig
16+
- If successful, the device will restart without returning any value.
17+
- verify that the first metadata packet after reset matches your configuration
18+
- See below for other types of responses.
19+
3. **SD Card**: Replace `config.json` file and restart system
20+
21+
#### updateConfig Function Details
22+
23+
The `updateConfig` Particle cloud function accepts a JSON configuration string and applies it to the system. The function provides detailed feedback about the configuration update process.
24+
25+
##### Usage
26+
27+
```bash
28+
# Using Particle CLI
29+
particle call <device_name> updateConfig '{"config":{"system":{"logPeriod":600}}}'
30+
31+
# Using Particle Console
32+
# Paste the JSON configuration directly into the function argument field
33+
34+
# Remove configuration (revert to defaults)
35+
particle call <device_name> updateConfig 'remove'
36+
```
37+
38+
##### Response Types
39+
40+
The `updateConfig` function returns different responses based on the outcome:
41+
42+
**Success Responses:**
43+
- **Return 1**: Configuration updated successfully, device will restart automatically
44+
- **Return 0**: Configuration removed successfully (when using "remove" command)
45+
- Verify success by checking the first metadata packet after reset matches your configuration
46+
- New configuration UIDs will be available via `getSystemConfig` and `getSensorConfig`
47+
48+
**Error Responses:**
49+
The function returns specific error codes when configuration updates fail:
50+
51+
| Error Code | Description | Troubleshooting |
52+
|------------|-------------|-----------------|
53+
| `0` | Success - Configuration removed from SD card | |
54+
| `1` | Success - Configuration updated, system restarting | |
55+
| `-1` | Failed to remove configuration from SD card | |
56+
| `-2` | Invalid configuration format - Missing 'config' element | |
57+
| `-3` | Invalid configuration format - Missing 'system' element | |
58+
| `-4` | Invalid configuration format - Missing 'sensors' element | |
59+
| `-5` | Failed to write test file to SD card | |
60+
| `-6` | Failed to remove current configuration from SD card | |
61+
| `-7` | Failed to write new configuration to SD card | |
62+
63+
##### Configuration Validation Rules
64+
65+
The system performs several validation checks:
66+
67+
1. **JSON Structure Validation**
68+
- Must contain "config" root element (checked by string search)
69+
- Must contain "system" section within config (checked by string search)
70+
- Must contain "sensors" section within config (checked by string search)
71+
- All whitespace, newlines, carriage returns, and tabs are automatically stripped
72+
73+
2. **SD Card Validation**
74+
- SD card must be accessible for writing
75+
- System tests write capability before attempting configuration update
76+
- Current configuration must be removable before writing new configuration
77+
78+
3. **Special Commands**
79+
- Use "remove" as the configuration string to delete config.json and revert to defaults
80+
81+
##### Example Error Scenarios
82+
83+
###### Missing Configuration Elements
84+
```bash
85+
particle call device_name updateConfig '{"system":{"logPeriod":300}}' # Missing "config" wrapper
86+
# Returns: -2
87+
88+
particle call device_name updateConfig '{"config":{"sensors":{"numSoil":3}}}' # Missing "system" section
89+
# Returns: -3
90+
91+
particle call device_name updateConfig '{"config":{"system":{"logPeriod":300}}}' # Missing "sensors" section
92+
# Returns: -4
93+
```
94+
95+
###### SD Card Issues
96+
```bash
97+
# If SD card is not available or full
98+
particle call device_name updateConfig '{"config":{"system":{"logPeriod":300},"sensors":{}}}'
99+
# May return: -5, -6, or -7 depending on the specific SD card failure point
100+
```
101+
102+
###### Configuration Removal
103+
```bash
104+
particle call device_name updateConfig 'remove'
105+
# Returns: 0 (success) or -1 (failed to remove)
106+
```
107+
108+
##### Best Practices
109+
110+
1. **Validate JSON First**: Use a JSON validator before sending to the device
111+
2. **Check Parameter Ranges**: Verify all values are within acceptable ranges
112+
3. **Plan Hardware Requirements**: Ensure sufficient Talons for sensor configuration
113+
4. **Monitor Device Status**: Watch for restart after successful configuration
114+
5. **Verify Configuration**: Check UIDs after restart to confirm changes applied
115+
116+
#### Configuration UIDs
117+
118+
The system generates unique identifiers for configuration tracking:
119+
120+
- **System Configuration UID**: Changes when system parameters are modified
121+
- **Sensor Configuration UID**: Changes when sensor counts are modified
122+
123+
These UIDs can be retrieved via cloud functions:
124+
- `getSystemConfig`: Returns system configuration UID
125+
- `getSensorConfig`: Returns sensor configuration UID
126+
127+
##### UID Encoding Format
128+
129+
The Configuration UIDs are encoded as 32-bit integers using bit-packing to efficiently store multiple configuration parameters in a single value.
130+
131+
###### System Configuration UID Encoding
132+
133+
The System Configuration UID is constructed using the following bit layout:
134+
135+
```
136+
Bits: 31-16 15-12 11-10 9-8 7-6 5-4 3-2 1-0
137+
Field: logPeriod backhaul powerSave logMode numAux numI2C numSDI12 reserved
138+
```
139+
140+
| Field | Bits | Description | Range |
141+
|-------|------|-------------|-------|
142+
| `logPeriod` | 31-16 | Logging period in seconds | 0-65535 |
143+
| `backhaulCount` | 15-12 | Number of logs before backhaul | 0-15 |
144+
| `powerSaveMode` | 11-10 | Power management mode | 0-3 |
145+
| `loggingMode` | 9-8 | Logging behavior mode | 0-3 |
146+
| `numAuxTalons` | 7-6 | Number of Auxiliary Talons | 0-3 |
147+
| `numI2CTalons` | 5-4 | Number of I2C Talons | 0-3 |
148+
| `numSDI12Talons` | 3-2 | Number of SDI-12 Talons | 0-3 |
149+
| Reserved | 1-0 | Reserved for future use | 0-3 |
150+
151+
**Encoding Formula:**
152+
```cpp
153+
int systemUID = (logPeriod << 16) |
154+
(backhaulCount << 12) |
155+
(powerSaveMode << 10) |
156+
(loggingMode << 8) |
157+
(numAuxTalons << 6) |
158+
(numI2CTalons << 4) |
159+
(numSDI12Talons << 2);
160+
```
161+
162+
###### Sensor Configuration UID Encoding
163+
164+
The Sensor Configuration UID uses the following bit layout:
165+
166+
```
167+
Bits: 31-28 27-24 23-20 19-16 15-12 11-8 7-4 3-0
168+
Field: numET numHaar numSoil numApogee numCO2 numO2 numPress reserved
169+
```
170+
171+
| Field | Bits | Description | Range |
172+
|-------|------|-------------|-------|
173+
| `numET` | 31-28 | Number of ET sensors (LI-710) | 0-15 |
174+
| `numHaar` | 27-24 | Number of Haar atmospheric sensors | 0-15 |
175+
| `numSoil` | 23-20 | Number of soil sensors (TDR315H) | 0-15 |
176+
| `numApogeeSolar` | 19-16 | Number of Apogee solar sensors | 0-15 |
177+
| `numCO2` | 15-12 | Number of CO2 sensors (Hedorah) | 0-15 |
178+
| `numO2` | 11-8 | Number of O2 sensors (SO421) | 0-15 |
179+
| `numPressure` | 7-4 | Number of pressure sensors | 0-15 |
180+
| Reserved | 3-0 | Reserved for future use | 0-15 |
181+
182+
**Encoding Formula:**
183+
```cpp
184+
int sensorUID = (numET << 28) |
185+
(numHaar << 24) |
186+
(numSoil << 20) |
187+
(numApogeeSolar << 16) |
188+
(numCO2 << 12) |
189+
(numO2 << 8) |
190+
(numPressure << 4);
191+
```
192+
193+
##### UID Decoding Examples
194+
195+
###### Decoding System Configuration UID
196+
197+
To extract individual values from a System Configuration UID:
198+
199+
```cpp
200+
// Example UID: 1234567890 (decimal) = 0x499602D2 (hex)
201+
int systemUID = 1234567890;
202+
203+
// Extract each field
204+
int logPeriod = (systemUID >> 16) & 0xFFFF; // Bits 31-16
205+
int backhaulCount = (systemUID >> 12) & 0xF; // Bits 15-12
206+
int powerSaveMode = (systemUID >> 10) & 0x3; // Bits 11-10
207+
int loggingMode = (systemUID >> 8) & 0x3; // Bits 9-8
208+
int numAuxTalons = (systemUID >> 6) & 0x3; // Bits 7-6
209+
int numI2CTalons = (systemUID >> 4) & 0x3; // Bits 5-4
210+
int numSDI12Talons = (systemUID >> 2) & 0x3; // Bits 3-2
211+
```
212+
213+
###### Decoding Sensor Configuration UID
214+
215+
```cpp
216+
// Example UID: 305419896 (decimal) = 0x12345678 (hex)
217+
int sensorUID = 305419896;
218+
219+
// Extract each field
220+
int numET = (sensorUID >> 28) & 0xF; // Bits 31-28
221+
int numHaar = (sensorUID >> 24) & 0xF; // Bits 27-24
222+
int numSoil = (sensorUID >> 20) & 0xF; // Bits 23-20
223+
int numApogeeSolar = (sensorUID >> 16) & 0xF; // Bits 19-16
224+
int numCO2 = (sensorUID >> 12) & 0xF; // Bits 15-12
225+
int numO2 = (sensorUID >> 8) & 0xF; // Bits 11-8
226+
int numPressure = (sensorUID >> 4) & 0xF; // Bits 7-4
227+
```
228+
229+
A tool has been developed to help parse this UID and make sense of it, found [in RTGS_Lab gems_sensing_db_tools](https://github.com/RTGS-Lab/gems_sensing_db_tools)
230+
231+
##### Practical Examples
232+
233+
###### Example 1: Default Configuration
234+
```json
235+
{
236+
"system": {
237+
"logPeriod": 300,
238+
"backhaulCount": 4,
239+
"powerSaveMode": 1,
240+
"loggingMode": 0,
241+
"numAuxTalons": 1,
242+
"numI2CTalons": 1,
243+
"numSDI12Talons": 1
244+
}
245+
}
246+
```
247+
248+
**System UID Calculation:**
249+
- logPeriod (300) << 16 = 19660800
250+
- backhaulCount (4) << 12 = 16384
251+
- powerSaveMode (1) << 10 = 1024
252+
- loggingMode (0) << 8 = 0
253+
- numAuxTalons (1) << 6 = 64
254+
- numI2CTalons (1) << 4 = 16
255+
- numSDI12Talons (1) << 2 = 4
256+
257+
**System UID = 19678292** (decimal) or **0x12C4154** (hex)
258+
259+
###### Example 2: Sensor Configuration
260+
```json
261+
{
262+
"sensors": {
263+
"numET": 1,
264+
"numHaar": 2,
265+
"numSoil": 3,
266+
"numApogeeSolar": 1,
267+
"numCO2": 1,
268+
"numO2": 1,
269+
"numPressure": 1
270+
}
271+
}
272+
```
273+
274+
**Sensor UID Calculation:**
275+
- numET (1) << 28 = 268435456
276+
- numHaar (2) << 24 = 33554432
277+
- numSoil (3) << 20 = 3145728
278+
- numApogeeSolar (1) << 16 = 65536
279+
- numCO2 (1) << 12 = 4096
280+
- numO2 (1) << 8 = 256
281+
- numPressure (1) << 4 = 16
282+
283+
**Sensor UID = 305205520** (decimal) or **0x12311110** (hex)
284+
285+
##### UID Usage
286+
287+
Configuration UIDs are used for:
288+
289+
1. **Change Detection**: Compare current UID with stored UID to detect configuration changes
290+
2. **Remote Monitoring**: Cloud functions return UIDs for remote configuration verification
291+
3. **Debugging**: Quick identification of active configuration without full JSON parsing
292+
4. **Optimization**: Fast configuration comparison without string operations

0 commit comments

Comments
 (0)