Skip to content

Commit 9f4392c

Browse files
Error codes + landed mode configurator support (#131)
* Add support for error codes that can be transmitted over messages or Horus V3 for exposing issues in firmware. Add support for landed mode in web firmware configurator. * Only push firmware configurator from main branch * Adjust text
1 parent d9b991a commit 9f4392c

30 files changed

Lines changed: 772 additions & 123 deletions

.github/workflows/deploy-web.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
1-
# Builds the web configurator (web/) and deploys it to GitHub Pages.
1+
# Builds the web firmware configurator (web/) and deploys it to GitHub Pages.
22
#
33
# One-time repo setup:
44
# Settings → Pages → Build and deployment → Source = "GitHub Actions".
55
# The site is served at https://<owner>.github.io/<repo>/ — vite.config.ts uses
66
# base "./" (relative paths) so it works from that subpath without further config.
77

8-
name: Deploy web configurator
8+
name: Deploy web firmware configurator
99

1010
on:
1111
push:
12-
# TEMPORARY: web-configurator is included so Pages can be tested from the branch
13-
# before merge. Revert to `branches: [main]` once verified.
14-
branches: [main, web-configurator]
12+
branches: [main]
1513
paths:
1614
- "web/**"
1715
- ".github/workflows/deploy-web.yml"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
/cmake-build-debug
44
/cmake-build-debug-arm-none-eabi
55
/build
6+
/build-tests
67
/samples
78
*~
89
.vscode/

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,20 @@ use command `file src/RS41ng.elf`.
655655
656656
NOTE: To save RAM, the heap size has been zeroed out. Dynamic memory allocations (`malloc`, etc.) will not function. Use static or stack-based allocation if needed.
657657
658+
## Building and running unit tests
659+
660+
```bash
661+
# Configure tests (defaults to -DRS41=1)
662+
cmake -S tests -B build-tests
663+
# Build all tests
664+
cmake --build build-tests
665+
# Run all tests
666+
ctest --test-dir build-tests --output-on-failure
667+
668+
# Run a single test
669+
ctest --test-dir build-tests -R template_test --output-on-failure
670+
```
671+
658672
## Hardware-specific Notes
659673

660674
This section is allocated for documentation of various features and oddities of specific radiosonde types.

config.yaml.example

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
# $loc8 Locator (8 chars)
2121
# $loc12 Locator (12 chars)
2222
# $bv Battery voltage in mV (up to 4 chars)
23+
# $bu Button ADC value (raw reading, up to 4 chars)
2324
# $te External temperature in C (up to 3 chars)
2425
# $ti Internal temperature in C (up to 3 chars)
2526
# $hu Humidity % (up to 3 chars)
@@ -38,7 +39,9 @@
3839
# $pc Pulse counter value (wraps to zero at 65535, 16-bit unsigned)
3940
# $ri Radiation intensity in uR/h (up to 5 chars)
4041
# $dc Data counter value (wraps to zero at 65535, 16-bit unsigned)
42+
# $apc APRS packet counter value (16-bit unsigned)
4143
# $gu GPS data update indicator (1 if updated, 0 otherwise)
44+
# $err System error code, zero-padded to 2 digits (see errors.h; 0 = no error)
4245
# $ct Clock calibration trim value (0-31, DFM-17 only)
4346
# $cc Clock calibration change count (DFM-17 only)
4447

@@ -551,6 +554,9 @@ horus_v3:
551554
# Disable SondeHub Upload — Disable uploading telemetry to SondeHub [intermediate]
552555
# defines HORUS_V3_NOHUB
553556
nohub: false
557+
# Transmit Error Code — Transmit the system error code (see errors.h, also available as $err) as an "err" extra sensor field [intermediate]
558+
# defines HORUS_V3_TX_ERROR_CODE
559+
error_code: false
554560
# Time Sync Interval (s) — Transmit every N seconds from top of hour. 0 = no time sync. [intermediate]
555561
# constraints: range 0..3600
556562
# defines HORUS_V3_TIME_SYNC_SECONDS
@@ -882,3 +888,64 @@ jt65:
882888
# constraints: range 0..3600
883889
# defines JT65_TIME_SYNC_OFFSET_SECONDS
884890
time_sync_offset_seconds: 1
891+
892+
# ========================================================================
893+
# Landed Mode
894+
# ========================================================================
895+
landed:
896+
# Enable Landed Mode — Enable battery conservation after the balloon has landed. [intermediate]
897+
# defines LANDED_MODE_ENABLE
898+
enable: false
899+
# Arm Altitude (m) — Altitude in meters that must be exceeded during ascent to arm landed mode. [advanced]
900+
# constraints: range 0..50000
901+
# applies when: landed.enable
902+
# defines LANDED_MODE_ARM_ALTITUDE_METERS
903+
arm_altitude_meters: 5000
904+
# Climb Threshold (cm/s) — Maximum absolute vertical speed in cm/s to consider "stationary" (50 = 0.5 m/s). [advanced]
905+
# constraints: range 0..10000
906+
# applies when: landed.enable
907+
# defines LANDED_MODE_CLIMB_THRESHOLD_CM_S
908+
climb_threshold_cm_s: 50
909+
# Speed Threshold (cm/s) — Maximum ground speed in cm/s to consider "stationary" (50 = 0.5 m/s). [advanced]
910+
# constraints: range 0..10000
911+
# applies when: landed.enable
912+
# defines LANDED_MODE_SPEED_THRESHOLD_CM_S
913+
speed_threshold_cm_s: 50
914+
# Stationary Time (s) — Number of consecutive seconds the unit must be stationary before entering landed mode. [advanced]
915+
# constraints: range 0..86400
916+
# applies when: landed.enable
917+
# defines LANDED_MODE_STATIONARY_SECONDS
918+
stationary_seconds: 600
919+
# Sleep Duration (s) — Sleep duration in seconds between wake-transmit cycles while in landed mode. [advanced]
920+
# constraints: range 0..86400
921+
# applies when: landed.enable
922+
# defines LANDED_MODE_SLEEP_SECONDS
923+
sleep_seconds: 300
924+
# GPS Fix Timeout (s) — Maximum time in seconds to wait for GPS fix after waking before transmitting anyway. [advanced]
925+
# constraints: range 0..3600
926+
# applies when: landed.enable
927+
# defines LANDED_MODE_GPS_FIX_TIMEOUT_SECONDS
928+
gps_fix_timeout_seconds: 120
929+
# Transmit Cycle Timeout (s) — Safety maximum time in seconds for the transmit cycle (normally the cycle completes on its own). [advanced]
930+
# constraints: range 0..3600
931+
# applies when: landed.enable
932+
# defines LANDED_MODE_TRANSMIT_SECONDS
933+
transmit_seconds: 30
934+
# Geofence Radius (m) — Geofence radius in meters from the landing point. If the unit moves beyond this radius, landed mode is disabled (returns to armed). Set to 0 to disable the geofence. [advanced]
935+
# constraints: range 0..100000
936+
# applies when: landed.enable
937+
# defines LANDED_MODE_GEOFENCE_RADIUS_METERS
938+
geofence_radius_meters: 100
939+
# Enable Pip While Sleeping — Emit a PIP at the configured interval while sleeping, without waking the GPS. Uses the existing PIP radio infrastructure (morse 'E' character). [advanced]
940+
# applies when: landed.enable
941+
# defines LANDED_MODE_PIP_ENABLE
942+
pip_enable: true
943+
# Pip Interval (s) — Interval in seconds between pips while sleeping in landed mode. [advanced]
944+
# constraints: range 1..3600
945+
# applies when: landed.enable && landed.pip_enable
946+
# defines LANDED_MODE_PIP_INTERVAL_SECONDS
947+
pip_interval_seconds: 10
948+
# LEDs Only During Transmit — When true, LEDs are forced off during landed sleep/acquire states and only enabled during TRANSMITTING or PIPPING to conserve power. [advanced]
949+
# applies when: landed.enable
950+
# defines LANDED_MODE_LEDS_TRANSMIT_ONLY
951+
leds_transmit_only: true

src/bme68x_handler.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,20 @@ bool bme68x_read_telemetry(telemetry_data *data)
161161
bool success;
162162

163163
if (bme680_initialization_required) {
164-
if(LEDS_ENABLE)
164+
if (LEDS_ENABLE) {
165165
set_red_led(true);
166+
}
166167

167-
log_info("BME re-init\n");
168+
log_info("BME68X re-init\n");
168169
success = bme68x_handler_init();
169-
log_info("BME re-init: %d\n", success);
170+
log_info("BME68X re-init: %d\n", success);
170171
if (!success) {
171172
data->temperature_celsius_100 = 0;
172173
data->pressure_mbar_100 = 0;
173174
data->humidity_percentage_100 = 0;
174175
data->bme6xx_gas_r = 0;
175176
data->ext_sensor_type = NO_EXT_SENSOR;
177+
set_error_code(ERROR_BME68X_READ);
176178
return false;
177179
} else if(LEDS_ENABLE) {
178180
set_red_led(false);
@@ -182,12 +184,13 @@ bool bme68x_read_telemetry(telemetry_data *data)
182184
success = bme68x_read(&data->temperature_celsius_100, &data->pressure_mbar_100, &data->humidity_percentage_100, &data->bme6xx_gas_r);
183185

184186
if (!success) {
185-
if(LEDS_ENABLE)
187+
if (LEDS_ENABLE) {
186188
set_red_led(true);
189+
}
187190

188-
log_info("BME re-init\n");
191+
log_info("BME68X re-init\n");
189192
success = bme68x_handler_init();
190-
log_info("BME re-init: %d\n", success);
193+
log_info("BME68X re-init: %d\n", success);
191194

192195
if (success) {
193196
success = bme68x_read(&data->temperature_celsius_100, &data->pressure_mbar_100, &data->humidity_percentage_100, &data->bme6xx_gas_r);
@@ -206,6 +209,9 @@ bool bme68x_read_telemetry(telemetry_data *data)
206209

207210
if(success) {
208211
data->ext_sensor_type = SENSOR_BME68X;
212+
clear_error_code(ERROR_BME68X_READ);
213+
} else {
214+
set_error_code(ERROR_BME68X_READ);
209215
}
210216

211217
bme680_initialization_required = !success;

src/bme690_handler.c

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,20 @@ bool bme690_read_telemetry(telemetry_data *data)
152152
bool success;
153153

154154
if (bme690_initialization_required) {
155-
if(LEDS_ENABLE)
155+
if (LEDS_ENABLE) {
156156
set_red_led(true);
157+
}
157158

158-
log_info("BME re-init\n");
159+
log_info("BME690 re-init\n");
159160
success = bme690_handler_init();
160-
log_info("BME re-init: %d\n", success);
161+
log_info("BME690 re-init: %d\n", success);
161162
if (!success) {
162163
data->temperature_celsius_100 = 0;
163164
data->pressure_mbar_100 = 0;
164165
data->humidity_percentage_100 = 0;
165166
data->bme6xx_gas_r = 0;
166167
data->ext_sensor_type = NO_EXT_SENSOR;
168+
set_error_code(ERROR_BME690_READ);
167169
return false;
168170
} else if(LEDS_ENABLE) {
169171
set_red_led(false);
@@ -173,12 +175,13 @@ bool bme690_read_telemetry(telemetry_data *data)
173175
success = bme690_read(&data->temperature_celsius_100, &data->pressure_mbar_100, &data->humidity_percentage_100, &data->bme6xx_gas_r);
174176

175177
if (!success) {
176-
if(LEDS_ENABLE)
178+
if (LEDS_ENABLE) {
177179
set_red_led(true);
180+
}
178181

179-
log_info("BME re-init\n");
182+
log_info("BME690 re-init\n");
180183
success = bme690_handler_init();
181-
log_info("BME re-init: %d\n", success);
184+
log_info("BME690 re-init: %d\n", success);
182185

183186
if (success) {
184187
success = bme690_read(&data->temperature_celsius_100, &data->pressure_mbar_100, &data->humidity_percentage_100, &data->bme6xx_gas_r);
@@ -197,6 +200,9 @@ bool bme690_read_telemetry(telemetry_data *data)
197200

198201
if(success) {
199202
data->ext_sensor_type = SENSOR_BME690;
203+
clear_error_code(ERROR_BME690_READ);
204+
} else {
205+
set_error_code(ERROR_BME690_READ);
200206
}
201207

202208
bme690_initialization_required = !success;

src/bmp280_handler.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,9 @@ bool bmp280_read_telemetry(telemetry_data *data)
5757
bool success;
5858

5959
if (bmp280_initialization_required) {
60-
if(LEDS_ENABLE)
60+
if (LEDS_ENABLE) {
6161
set_red_led(true);
62+
}
6263

6364
log_info("BMP280 re-init\n");
6465
success = bmp280_handler_init();
@@ -68,6 +69,7 @@ bool bmp280_read_telemetry(telemetry_data *data)
6869
data->pressure_mbar_100 = 0;
6970
data->humidity_percentage_100 = 0;
7071
data->ext_sensor_type = NO_EXT_SENSOR;
72+
set_error_code(ERROR_BMP280_READ);
7173
return false;
7274
} else if(LEDS_ENABLE) {
7375
set_red_led(false);
@@ -77,8 +79,9 @@ bool bmp280_read_telemetry(telemetry_data *data)
7779
success = bmp280_read(&data->temperature_celsius_100, &data->pressure_mbar_100, &data->humidity_percentage_100);
7880

7981
if (!success) {
80-
if(LEDS_ENABLE)
82+
if (LEDS_ENABLE) {
8183
set_red_led(true);
84+
}
8285

8386
log_info("BMP280 re-init\n");
8487
success = bmp280_handler_init();
@@ -100,6 +103,9 @@ bool bmp280_read_telemetry(telemetry_data *data)
100103

101104
if(success) {
102105
data->ext_sensor_type = (bmp280_dev.id == BMP280_CHIP_ID) ? SENSOR_BMP280 : SENSOR_BME280;
106+
clear_error_code(ERROR_BMP280_READ);
107+
} else {
108+
set_error_code(ERROR_BMP280_READ);
103109
}
104110

105111
bmp280_initialization_required = !success;

src/codecs/horus/horus_packet_v3.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,31 @@ size_t horus_packet_v3_create(uint8_t *payload, telemetry_data *data){
284284
}
285285
#endif
286286

287+
#if HORUS_V3_TX_ERROR_CODE
288+
// Add the system error code (see errors.h) as an "err" extra sensor field
289+
if (asnMessage.extraSensors.nCount < 4) {
290+
asnMessage.exist.extraSensors = true;
291+
horusAdditionalSensorType error_struct = {
292+
.name = "err",
293+
.exist = {
294+
.name = 1,
295+
.values = 1
296+
},
297+
.values = {
298+
.kind = horusInt_PRESENT,
299+
.u = {
300+
.horusInt = {
301+
.nCount = 1,
302+
.arr[0] = data->error_code
303+
}
304+
}
305+
}
306+
};
307+
asnMessage.extraSensors.arr[asnMessage.extraSensors.nCount] = error_struct;
308+
asnMessage.extraSensors.nCount += 1;
309+
}
310+
#endif
311+
287312
memset(&encodedMessage, 0, sizeof(encodedMessage));
288313

289314
// The Encoder may fail and update an error code
@@ -316,6 +341,7 @@ size_t horus_packet_v3_create(uint8_t *payload, telemetry_data *data){
316341
log_error("[error]: HORUS v3 Assert Failure, maybe hit buffer size limit\n");
317342
}
318343
// Need to check what happens here.
344+
set_error_code(ERROR_HORUS_V3_ENCODE);
319345
return 0;
320346
} else {
321347
// Encoding was successful!
@@ -347,6 +373,7 @@ size_t horus_packet_v3_create(uint8_t *payload, telemetry_data *data){
347373
log_error("[error]: HORUS v3 Extension Assert Failure, maybe hit buffer size limit\n");
348374
}
349375
// Need to check what happens here.
376+
set_error_code(ERROR_HORUS_V3_EXT_ENCODE);
350377
return 0;
351378
}
352379

@@ -378,6 +405,10 @@ size_t horus_packet_v3_create(uint8_t *payload, telemetry_data *data){
378405

379406
log_info("HORUS v3 ASN1: %i Frame: %i\n", encodedSize, frameSize);
380407

408+
// Encoding succeeded - clear any Horus encode error
409+
clear_error_code(ERROR_HORUS_V3_ENCODE);
410+
clear_error_code(ERROR_HORUS_V3_EXT_ENCODE);
411+
381412
return frameSize;
382413
}
383414

src/config.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ Setting, measured RF output power, relative DC power draw
304304
#define HORUS_V3_PREAMBLE_LENGTH 4
305305
#define HORUS_V3_TONE_SPACING_HZ_SI5351 270
306306
#define HORUS_V3_NOHUB false // Disable uploading to SondeHub
307+
// Transmit the system error code (see errors.h, also available as $err) as an "err" extra sensor field
308+
#define HORUS_V3_TX_ERROR_CODE false
307309

308310
// Schedule transmission every N seconds, counting from beginning of an hour (based on GPS time). Set to zero to disable time sync.
309311
// See the README file for more detailed documentation about time sync and its offset setting

src/config_internal.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88

99
#define RADIO_PAYLOAD_MAX_LENGTH 512
1010
#define RADIO_SYMBOL_DATA_MAX_LENGTH 256
11+
#ifdef RS41_RSM4x4
12+
// RSM4x4 has more space for the payload message
13+
#define RADIO_PAYLOAD_MESSAGE_MAX_LENGTH 128
14+
#else
1115
#define RADIO_PAYLOAD_MESSAGE_MAX_LENGTH 64
16+
#endif
1217

1318
#define RADIO_APRS_PAYLOAD_MAX_LENGTH 192
1419

@@ -36,6 +41,16 @@
3641

3742
#include <stdbool.h>
3843

44+
// System error codes and the set_error_code() / clear_error_code() API
45+
#include "errors.h"
46+
47+
// Transmit the system error code in Horus v3 as an "err" extra sensor field.
48+
// Defaults to off here so generated configs (config_generated.h) that don't set
49+
// it still build; manual config.h overrides this.
50+
#ifndef HORUS_V3_TX_ERROR_CODE
51+
#define HORUS_V3_TX_ERROR_CODE 0
52+
#endif
53+
3954
extern volatile bool system_initialized;
4055

4156
extern char *cw_message_templates[];

0 commit comments

Comments
 (0)