Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions components/airgradient-local-server/internal/config_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,6 @@ ParseStatus apply_item(const cJSON *item, LocalServerConfig &out, ConfigFieldId
return take_enum(item, GPS_MODE_VALUES, out.gps_mode) ? ParseStatus::Ok
: ParseStatus::InvalidValue;
}
if (std::strcmp(key, fields::GPS_INTERVAL) == 0) {
field = ConfigFieldId::GpsInterval;
return take_int(item, out.gps_interval_seconds) ? ParseStatus::Ok : ParseStatus::InvalidValue;
}
if (std::strcmp(key, fields::FRONT_LED_BRIGHTNESS) == 0) {
field = ConfigFieldId::FrontLedBrightness;
return take_int(item, out.front_led_brightness) ? ParseStatus::Ok : ParseStatus::InvalidValue;
Expand Down Expand Up @@ -446,10 +442,6 @@ size_t serialize(const LocalServerConfig &cfg, char *buf, size_t buf_len) {
if (cfg.gps_mode.has_value()) {
cJSON_AddStringToObject(root, fields::GPS_MODE, cfg.gps_mode->c_str());
}
if (cfg.gps_interval_seconds.has_value()) {
cJSON_AddNumberToObject(root, fields::GPS_INTERVAL,
static_cast<double>(*cfg.gps_interval_seconds));
}
if (cfg.front_led_brightness.has_value()) {
cJSON_AddNumberToObject(root, fields::FRONT_LED_BRIGHTNESS,
static_cast<double>(*cfg.front_led_brightness));
Expand Down Expand Up @@ -532,8 +524,6 @@ const char *config_field_wire_key(ConfigFieldId id) {
return fields::MEASUREMENT_INTERVAL;
case ConfigFieldId::GpsMode:
return fields::GPS_MODE;
case ConfigFieldId::GpsInterval:
return fields::GPS_INTERVAL;
case ConfigFieldId::FrontLedBrightness:
return fields::FRONT_LED_BRIGHTNESS;
case ConfigFieldId::BackLedBrightness:
Expand Down
1 change: 0 additions & 1 deletion components/airgradient-local-server/internal/field_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ inline constexpr const char *CLOUD_CONNECTION = "cloudConnection";
inline constexpr const char *CONFIGURATION_CONTROL = "configurationControl";
inline constexpr const char *MEASUREMENT_INTERVAL = "measurementInterval";
inline constexpr const char *GPS_MODE = "gpsMode";
inline constexpr const char *GPS_INTERVAL = "gpsInterval";
inline constexpr const char *FRONT_LED_BRIGHTNESS = "frontLedBrightness";
inline constexpr const char *BACK_LED_BRIGHTNESS = "backLedBrightness";
inline constexpr const char *TOUCH_LED_INTENSITY = "touchLedIntensity";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,12 @@ TEST_CASE("config parse: all enum fields accept catalog values", "[config][parse
TEST_CASE("config parse: Go product fields parse with exact types", "[config][parse]") {
LocalServerConfig cfg;
const auto res = parse(
R"({"measurementInterval":30,"gpsMode":"always","gpsInterval":15,"frontLedBrightness":1,"backLedBrightness":2,"touchLedIntensity":2,"buzzerEnabled":true})",
R"({"measurementInterval":30,"gpsMode":"always","frontLedBrightness":1,"backLedBrightness":2,"touchLedIntensity":2,"buzzerEnabled":true})",
cfg);

REQUIRE(res.status == config_json::ParseStatus::Ok);
REQUIRE(cfg.measurement_interval_seconds == 30);
REQUIRE(cfg.gps_mode == "always");
REQUIRE(cfg.gps_interval_seconds == 15);
REQUIRE(cfg.front_led_brightness == 1);
REQUIRE(cfg.back_led_brightness == 2);
REQUIRE(cfg.touch_led_intensity == 2);
Expand Down Expand Up @@ -273,7 +272,6 @@ TEST_CASE("config serialize: emits only present fields", "[config][serialize]")
cfg.post_data_to_cloud = false;
cfg.measurement_interval_seconds = 30;
cfg.gps_mode = "always";
cfg.gps_interval_seconds = 15;
cfg.front_led_brightness = 1;
cfg.back_led_brightness = 2;
cfg.touch_led_intensity = 2;
Expand All @@ -291,7 +289,6 @@ TEST_CASE("config serialize: emits only present fields", "[config][serialize]")
REQUIRE(cJSON_IsFalse(cJSON_GetObjectItem(root, "postDataToCloud")));
REQUIRE(cJSON_GetObjectItem(root, "measurementInterval")->valueint == 30);
REQUIRE(std::strcmp(cJSON_GetObjectItem(root, "gpsMode")->valuestring, "always") == 0);
REQUIRE(cJSON_GetObjectItem(root, "gpsInterval")->valueint == 15);
REQUIRE(cJSON_GetObjectItem(root, "frontLedBrightness")->valueint == 1);
REQUIRE(cJSON_GetObjectItem(root, "backLedBrightness")->valueint == 2);
REQUIRE(cJSON_GetObjectItem(root, "touchLedIntensity")->valueint == 2);
Expand Down Expand Up @@ -369,8 +366,6 @@ TEST_CASE("config field wire keys map correctly", "[config][parse]") {
REQUIRE(std::strcmp(config_json::config_field_wire_key(ConfigFieldId::MeasurementInterval),
"measurementInterval") == 0);
REQUIRE(std::strcmp(config_json::config_field_wire_key(ConfigFieldId::GpsMode), "gpsMode") == 0);
REQUIRE(std::strcmp(config_json::config_field_wire_key(ConfigFieldId::GpsInterval),
"gpsInterval") == 0);
REQUIRE(std::strcmp(config_json::config_field_wire_key(ConfigFieldId::FrontLedBrightness),
"frontLedBrightness") == 0);
REQUIRE(std::strcmp(config_json::config_field_wire_key(ConfigFieldId::BackLedBrightness),
Expand Down
1 change: 0 additions & 1 deletion components/airgradient-local-server/types/local_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ struct LocalServerConfig {
std::optional<std::string> configuration_control; // "configurationControl"
std::optional<int> measurement_interval_seconds; // "measurementInterval"
std::optional<std::string> gps_mode; // "gpsMode"
std::optional<int> gps_interval_seconds; // "gpsInterval"
std::optional<int> front_led_brightness; // "frontLedBrightness"
std::optional<int> back_led_brightness; // "backLedBrightness"
std::optional<int> touch_led_intensity; // "touchLedIntensity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ enum class ConfigFieldId : uint8_t {
CorrectionsHumidity, // "corrections.humidity"
MeasurementInterval, // "measurementInterval"
GpsMode, // "gpsMode"
GpsInterval, // "gpsInterval"
FrontLedBrightness, // "frontLedBrightness"
BackLedBrightness, // "backLedBrightness"
TouchLedIntensity, // "touchLedIntensity"
Expand Down
11 changes: 5 additions & 6 deletions products/go/docs/ble_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ All characteristic payloads use CBOR (RFC 8949) encoded with TinyCBOR's
|---|---|---|---|
| Measures | ~120B | ~135B | Yes |
| Status | ~95B | ~115B | Yes |
| Config (read, 19 keys) | — | <512B | Yes (Read-Long) |
| Config (read, 18 keys) | — | <512B | Yes (Read-Long) |
| Config (notify, one field + type) | — | <180B | Yes |
| History control (CBOR) | ~40B | ~180B | Yes |
| History data (binary, 4 pts) | 223B | 223B | Yes |
Expand Down Expand Up @@ -376,17 +376,16 @@ config**, **set config values**, and **execute commands**.

### Read (phone reads characteristic)

Returns the full device configuration as a 19-key CBOR map. The BLE service
Returns the full device configuration as an 18-key CBOR map. The BLE service
keeps this value updated whenever the orchestrator calls `update_config()`.

#### CBOR Payload (Map) — 19 Keys
#### CBOR Payload (Map) — 18 Keys

| Key | CBOR Type | `GoSettings` field | Encoded with |
|---|---|---|---|
| `"meas_int"` | uint | `measure_interval_seconds` | `cbor_encode_uint` (1–3600 seconds) |
| `"temp_f"` | bool | `use_fahrenheit` | `cbor_encode_boolean` |
| `"pm_aqi"` | bool | `pm_use_usaqi` | `cbor_encode_boolean` |
| `"gps_int"` | uint | `gps_interval_seconds` | `cbor_encode_uint` (1–60 seconds) |
| `"gps_mode"` | text | `gps_mode` | See mapping below |
| `"inact_to"` | uint | `inactivity_timeout_seconds` | `cbor_encode_uint` |
| `"auto_lock"` | uint | `auto_lock_seconds` | `cbor_encode_uint` |
Expand Down Expand Up @@ -897,7 +896,7 @@ failed `setup_ble()` is non-fatal (advertise without OTA). See
| `notify_tracking_status(power, gps, tracking, session_id)` | Refreshes the full 9-key snapshot via `update_status()` (Read stays full), then pushes a `{tracking, session}` transition delta via `notify(data, len)`. Used for urgent tracking transitions (start success, start failure, manual stop). Best-effort delivery — Read remains authoritative. |
| `notify_charging_status(power, gps, tracking, session_id)` | Refreshes the full 9-key snapshot via `update_status()` (Read stays full), then pushes a `{charging, bat_pct, bat_v}` power delta via `notify(data, len)`. Used for charging transitions (plug in, unplug, charge complete). Disjoint keys from the tracking delta, no `"type"` discriminator — client merges by key. |
| `notify_disconnect(reason)` | Pushes a NOTIFY-only `{disc}` delta via `notify(data, len)` (snapshot untouched) announcing an imminent link drop and why (`overheat`/`low_batt`/`user`/`op_stationary`/`op_offline`). Called from `change_mode()` (leaving Portable) and `shutdown()`; gated on `is_connected()`; the caller settles before teardown so it can drain. |
| `update_config(settings)` | Encode the full snapshot via `encode_config()` (19 keys, no `"type"`), `set_value()` only. Sole writer of the Config snapshot; buffer sized to the 512-byte ATT ceiling. |
| `update_config(settings)` | Encode the full snapshot via `encode_config()` (18 keys, no `"type"`), `set_value()` only. Sole writer of the Config snapshot; buffer sized to the 512-byte ATT ceiling. |
| `notify_config(prev, cur)` | Refreshes the snapshot via `update_config(cur)`, then sends the changed-fields delta (`encode_config_delta()`: `"type":"config"` + changed keys) via `notify(data, len)`. |
| `notify_command_progress(cmd)` | Inline CBOR encoding (2 keys: type + cmd), `notify(data, len)` (stored value untouched). Sent before long-running commands. |
| `notify_command_result(cmd, success, error)` | Inline CBOR encoding (3-4 keys), `notify(data, len)` (stored value untouched). |
Expand Down Expand Up @@ -1243,7 +1242,7 @@ cover:

- **CBOR encoding**: `encode_measures()` (field omission, GPS inclusion),
`encode_status()` (all 9 keys, battery clamping) and `encode_status_transition()`
(2-key delta), `encode_config()` (full 19-key snapshot, no `"type"`) and
(2-key delta), `encode_config()` (full 18-key snapshot, no `"type"`) and
`encode_config_delta()` (`"type":"config"` + changed keys only),
`notify_config(prev, cur)` (delta via `notify(data, len)`, Read stays full,
snapshot refreshed first), `notify_command_result()` /
Expand Down
1 change: 0 additions & 1 deletion products/go/docs/cloud_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ and queues a value-only `FetchConfigEventPayload`. Supported fields map into
| `temperatureUnit` | `"c"`, `"f"` | `use_fahrenheit` |
| `measurementInterval` | Integer 1 .. 3600 | `measure_interval_seconds` |
| `gpsMode` | `"off"`, `"tracking"`, `"always"` | `gps_mode` |
| `gpsInterval` | Integer 1 .. 60 | `gps_interval_seconds` |
| `frontLedBrightness` | Integer 0 .. 3 | `front_led_brightness` |
| `backLedBrightness` | Integer 0 .. 3 | `back_led_brightness` |
| `touchLedIntensity` | Integer 0 .. 2 | `touch_led_intensity` |
Expand Down
10 changes: 4 additions & 6 deletions products/go/docs/gps_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ queue at the configured interval.

## Configuration

`GpsService::Config` fields — hardware-specific values come from `board_config.h`;
interval is derived from `GoSettings::gps_interval_seconds * 1000`.
`GpsService::Config` fields — hardware-specific values come from `board_config.h`.

| Field | Default | Notes |
|---|---|---|
| `baud_rate` | `115200` | GPS module baud rate; hardware-specific, not a user setting. `GpsDriver::begin()` handles the TAU1113 baud-rate negotiation (starts at 9600, sends binary switch command, re-opens at 115200). |
| `posting_interval_ms` | `5000` | How often to post `GpsFixUpdate` to the event queue; set from `GoSettings::gps_interval_seconds` |
| `posting_interval_ms` | `5000` | How often to post `GpsFixUpdate` to the event queue |
| `task_stack_size` | `4096` | RTOS task stack in bytes; tune at integration time |
| `task_priority` | `3` | Below display worker (4); above idle |

Expand All @@ -52,9 +51,8 @@ interval is derived from `GoSettings::gps_interval_seconds * 1000`.
UartSerial gps_uart(BOARD_GPS_UART_PORT, BOARD_GPS_TX_PIN, BOARD_GPS_RX_PIN);
GpsDriver gps_driver(gps_uart);

// Build config from settings.
// Build the GPS service configuration.
GpsService::Config cfg{};
cfg.posting_interval_ms = settings.gps_interval_seconds * 1000;

GpsService gps_svc(gps_driver, event_queue, cfg);

Expand Down Expand Up @@ -82,7 +80,7 @@ if (is_fix_valid(fix.fix)) {
// use fix.position, fix.altitude_m, fix.fix, fix.timestamp
}

// Update interval when settings change.
// Update the service-local posting interval when required.
gps_svc.set_posting_interval_ms(new_interval_ms);

// Clean shutdown before deep sleep.
Expand Down
3 changes: 1 addition & 2 deletions products/go/docs/local_server.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ objects from the same subset. Device behavior fields are:
| `temperatureUnit` | `c`, `f` | Select product display temperature unit |
| `measurementInterval` | Integer 1 .. 3600 | Set the measurement interval in seconds |
| `gpsMode` | `off`, `tracking`, `always` | Disable GPS, run it only while tracking, or keep it active |
| `gpsInterval` | Integer 1 .. 60 | Set the GPS posting interval in seconds |
| `frontLedBrightness` | Integer 0 .. 3 | Set front LED brightness: off, dim, mid, or bright |
| `backLedBrightness` | Integer 0 .. 3 | Set AQI LED brightness: off, dim, mid, or bright |
| `touchLedIntensity` | Integer 0 .. 2 | Set touch LED intensity: off, dim, or bright |
Expand All @@ -158,7 +157,7 @@ Connectivity, sensor, and correction fields are:
|---|---|---|
| `cloudConnection` | Boolean | Inverse of the product `disable_cloud` setting |
| `configurationControl` | `cloud`, `local`, `both` | Arbitrate Local Server PUT and Cloud Fetch sources |
| `co2AbcDays` | Integer `-1` or 1 .. 200 | Set the automatic background calibration period for the supported CO2 sensor. `-1` disables it; positive values are converted to hours. |
| `co2AbcDays` | Integer `0` or 1 .. 200 | Set the automatic background calibration period for the supported CO2 sensor. `0` disables it; positive values are converted to hours. |
| `tvocLearningOffset` | Integer 1 .. 1000 | Set the SGP41 VOC gas-index learning-time offset in whole hours. |
| `noxLearningOffset` | Integer 1 .. 1000 | Set the SGP41 NOx gas-index learning-time offset in whole hours. |
| `corrections` | Object | Configure `pm25`, `temperature`, and `humidity` correction entries |
Expand Down
2 changes: 0 additions & 2 deletions products/go/docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ See [`go_settings.h`](../main/go_settings.h) for full signatures.
| `measure_interval_seconds` | `"mi"` | `int` | `10` | 1 .. 3600 | All sensors measured together at this cadence; no per-group on/off |
| `use_fahrenheit` | `"uf"` | `bool` | `false` | — | Temperature display unit (false=C, true=F) |
| `pm_use_usaqi` | `"pmu"` | `bool` | `false` | — | PM display format (false=µg/m³, true=USAQI) |
| `gps_interval_seconds` | `"gis"` | `int` | `5` | 1 .. 60 | How often the GPS task posts fixes to the event queue |
| `gps_mode` | `"gpm"` | `int` (stored) / `GpsMode` (in struct) | `OnWhenTracking` (1) | 0 .. 2 | GPS operating mode: AlwaysOff / OnWhenTracking / AlwaysOn |
| `operating_mode` | `"opm"` | `int` (stored) / `OperatingMode` (in struct) | `Portable` (0) | 0 .. 2 | Serialized as int; cast to `OperatingMode` on load |
| `inactivity_timeout_seconds` | `"ito"` | `int` | `5` | 5 .. 600 | Persisted and exposed over BLE; not currently used by the runtime auto-lock path |
Expand Down Expand Up @@ -128,7 +127,6 @@ All validation is implemented in an anonymous namespace in `go_settings.cpp`
| `inactivity_timeout_seconds` | `>= 5 && <= 600` |
| `use_fahrenheit` | No range check (bool) |
| `pm_use_usaqi` | No range check (bool) |
| `gps_interval_seconds` | `>= 1 && <= 60` |
| `gps_mode` | Underlying int in `0 .. 2` (matches `GpsMode` enum values) |
| `operating_mode` | Underlying int in `0 .. 2` (matches `OperatingMode` enum values) |
| `auto_lock_seconds` | `0`, `10`, `30`, or `60` |
Expand Down
7 changes: 2 additions & 5 deletions products/go/go_ble_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,14 +510,13 @@ This characteristic supports three operations:

Read the characteristic to receive the full device configuration.

#### Payload (19-key CBOR map)
#### Payload (18-key CBOR map)

| Key | Type | Description |
|---|---|---|
| `"meas_int"` | uint | Measurement interval in seconds (1–3600). All sensors measured together at this cadence. |
| `"temp_f"` | bool | `true` = Fahrenheit, `false` = Celsius |
| `"pm_aqi"` | bool | `true` = US AQI for PM, `false` = raw ug/m3 |
| `"gps_int"` | uint | GPS update interval in seconds (1–60) |
| `"gps_mode"` | text | GPS mode (see table below) |
| `"inact_to"` | uint | Inactivity timeout (seconds) |
| `"auto_lock"` | uint | Auto-lock timeout (seconds) |
Expand Down Expand Up @@ -568,7 +567,6 @@ them and persisted loading canonicalizes them.
"meas_int": 10,
"temp_f": false,
"pm_aqi": false,
"gps_int": 5,
"gps_mode": "tracking",
"inact_to": 300,
"auto_lock": 60,
Expand Down Expand Up @@ -617,7 +615,6 @@ silently ignored for backward compatibility. They do not modify any setting.
| `"meas_int"` | uint | 1–3600 seconds |
| `"temp_f"` | bool | |
| `"pm_aqi"` | bool | |
| `"gps_int"` | uint | 1–60 seconds |
| `"gps_mode"` | text | `"off"`, `"tracking"`, or `"always"` |
| `"inact_to"` | uint | |
| `"auto_lock"` | uint | |
Expand Down Expand Up @@ -1614,7 +1611,7 @@ negotiated interval; only its speed is affected.

### Required MTUs by operation

- **Config Read**: the full 19-key snapshot is typically about 239 bytes and is
- **Config Read**: the full 18-key snapshot is typically about 239 bytes and is
bounded by a 512-byte characteristic buffer. Use Read-Long / Read Blob and
collect all fragments. Config Read does not require MTU 512, but it does
require a client API that supports long reads.
Expand Down
22 changes: 9 additions & 13 deletions products/go/main/go_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,14 +491,12 @@ void GoApp::run_button_wake_path(const RtcAppState &state) {
.nox_learning_offset = settings.nox_learning_offset,
});

auto *gps_service =
new GpsService(*gps_driver, event_queue,
{
.baud_rate = GPS_BAUD,
.posting_interval_ms = settings.gps_interval_seconds * 1000,
.task_stack_size = 4096,
.task_priority = 3,
});
auto *gps_service = new GpsService(*gps_driver, event_queue,
{
.baud_rate = GPS_BAUD,
.task_stack_size = 4096,
.task_priority = 3,
});

// InputService: suppress the first ButtonPower event (the wake press)
auto *input_service = new InputService(*touch, _board.gpio_hal(), event_queue,
Expand Down Expand Up @@ -709,11 +707,9 @@ void GoApp::run_interactive(WakeCause cause, BootHandoff handoff) {
.tvoc_learning_offset = settings.tvoc_learning_offset,
.nox_learning_offset = settings.nox_learning_offset});

auto *gps_service = new GpsService(*gps_driver, event_queue,
{.baud_rate = GPS_BAUD,
.posting_interval_ms = settings.gps_interval_seconds * 1000,
.task_stack_size = 4096,
.task_priority = 3});
auto *gps_service =
new GpsService(*gps_driver, event_queue,
{.baud_rate = GPS_BAUD, .task_stack_size = 4096, .task_priority = 3});

auto *input_service = new InputService(*touch, _board.gpio_hal(), event_queue,
{.pin_cap_int = PIN_CAP_INT,
Expand Down
Loading
Loading