diff --git a/products/go/ARCHITECTURE.md b/products/go/ARCHITECTURE.md index 190b548..98b7a35 100644 --- a/products/go/ARCHITECTURE.md +++ b/products/go/ARCHITECTURE.md @@ -249,8 +249,8 @@ Both states: Button 1 long press → Shutdown. Releasing after the long press powers off; keeping the button held re-wakes the BQ25629 through `/QON` and cold-boots the device — see [Shutdown](#shutdown). -Inactivity timeout while unlocked → auto-lock. The timeout value is -configurable via settings (minimum 5 seconds). +Inactivity while unlocked triggers auto-lock after the selected Auto Lock +timeout (10, 30, or 60 seconds). Setting Auto Lock to 0 disables it. While locked, the display periodically updates sensor values and status on the dashboard. The refresh interval is configurable and can be disabled @@ -814,7 +814,7 @@ Settings fields: - GPS mode (AlwaysOff / OnWhenTracking / AlwaysOn) - Operating mode (Portable / Stationary / Offline; default: Portable) - Auto-lock timeout (0 = disabled, 10 s / 30 s / 60 s) -- Inactivity timeout, GPS interval, device name +- GPS interval ### Display Service (E-Paper) diff --git a/products/go/docs/ble_service.md b/products/go/docs/ble_service.md index b1fc0e7..ddf2590 100644 --- a/products/go/docs/ble_service.md +++ b/products/go/docs/ble_service.md @@ -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, 18 keys) | — | <512B | Yes (Read-Long) | +| Config (read, 16 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 | @@ -376,10 +376,10 @@ config**, **set config values**, and **execute commands**. ### Read (phone reads characteristic) -Returns the full device configuration as an 18-key CBOR map. The BLE service +Returns the full device configuration as a 16-key CBOR map. The BLE service keeps this value updated whenever the orchestrator calls `update_config()`. -#### CBOR Payload (Map) — 18 Keys +#### CBOR Payload (Map) — 16 Keys | Key | CBOR Type | `GoSettings` field | Encoded with | |---|---|---|---| @@ -387,9 +387,7 @@ keeps this value updated whenever the orchestrator calls `update_config()`. | `"temp_f"` | bool | `use_fahrenheit` | `cbor_encode_boolean` | | `"pm_aqi"` | bool | `pm_use_usaqi` | `cbor_encode_boolean` | | `"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` | -| `"dev_name"` | text | `device_name` | `cbor_encode_text_stringz` | | `"op_mode"` | text | `operating_mode` | See mapping below | | `"fled"` | uint | `front_led_brightness` | `cbor_encode_uint` (0–3) | | `"bled"` | uint | `back_led_brightness` | `cbor_encode_uint` (0–3) | @@ -895,7 +893,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()` (18 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()` (16 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). | @@ -1241,7 +1239,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 18-key snapshot, no `"type"`) and + (2-key delta), `encode_config()` (full 16-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()` / diff --git a/products/go/docs/orchestrator.md b/products/go/docs/orchestrator.md index 8361829..4615338 100644 --- a/products/go/docs/orchestrator.md +++ b/products/go/docs/orchestrator.md @@ -1046,13 +1046,6 @@ through `UIManager::handle_input()` returning `UIActionResult`. The triggers — for example, BLE `start_tracking` / `stop_tracking` commands dispatch through the same `start_tracking()` / `stop_tracking()` methods. -### auto_lock_seconds vs inactivity_timeout_seconds - -The inactivity timer uses `GoSettings::auto_lock_seconds` because this is -the field controlled by the UI "Auto Lock" setting and persisted correctly -through `save_go_settings()`. The `inactivity_timeout_seconds` field exists -in `GoSettings` but is not connected to any UI control. - ### Invalid Sentinel Initialization `_raw_measures` and `_corrected_measures` are initialized to invalid sentinel diff --git a/products/go/docs/settings.md b/products/go/docs/settings.md index 79975b9..65bd34c 100644 --- a/products/go/docs/settings.md +++ b/products/go/docs/settings.md @@ -40,9 +40,7 @@ See [`go_settings.h`](../main/go_settings.h) for full signatures. | `pm_use_usaqi` | `"pmu"` | `bool` | `false` | — | PM display format (false=µg/m³, true=USAQI) | | `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 | | `auto_lock_seconds` | `"als"` | `int` | `10` | 0, 10, 30, 60 | Runtime auto-lock timeout; `0` = disabled | -| `device_name` | `"dn"` | `std::string` | `"airgradient-go"` | 1 .. 64 chars | Advertised name for BLE/WiFi | | `disable_cloud` | `"dc"` | `bool` | `false` | — | Outbound cloud transport kill switch. Suppresses POST, FETCH, and Stationary OTA checks; does not disable the local API. | | `configuration_control` | `"cc"` | `int` (stored) / `ConfigurationControl` (in struct) | `Both` (2) | 0 .. 2 | Remote configuration authority: `Cloud`, `Local`, or `Both`. Does not control measurement POST. | | `co2_abc_days` | `"cad"` | `int` | `7` | -1 or 1 .. 200 | Automatic background calibration period. `-1` disables it; positive values are applied as 24 hours per day to supported S12 and SCD41 CO2 sensors. | @@ -125,13 +123,11 @@ All validation is implemented in an anonymous namespace in `go_settings.cpp` | Field | Rule | |---|---| | `measure_interval_seconds` | `>= 1 && <= 3600` | -| `inactivity_timeout_seconds` | `>= 5 && <= 600` | | `use_fahrenheit` | No range check (bool) | | `pm_use_usaqi` | No range check (bool) | | `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` | -| `device_name` | Non-empty and `<= 64` characters | | `disable_cloud` | No range check (bool) | | `configuration_control` | Underlying int in `0 .. 2`; `Cloud` is invalid when `disable_cloud == true` | | `co2_abc_days` | `-1`, or `>= 1 && <= 200` | diff --git a/products/go/feature_overview.md b/products/go/feature_overview.md index 624334f..32c6864 100644 --- a/products/go/feature_overview.md +++ b/products/go/feature_overview.md @@ -292,7 +292,6 @@ Common configurable settings include: - AQI LED brightness. - Touch LED brightness. - Buzzer enabled or disabled. -- Device name. - Static IP and cloud preference for Stationary mode. Action-style settings are also available from the device or app, including CO2 diff --git a/products/go/go_ble_client.md b/products/go/go_ble_client.md index 6837179..5be6a03 100644 --- a/products/go/go_ble_client.md +++ b/products/go/go_ble_client.md @@ -212,7 +212,7 @@ Read-Long / Read Blob operations. Notifications are single ATT PDUs and are never fragmented by the application. The Config snapshot is therefore not a notification payload: it is a Read-Long -value and is typically about 239 bytes with correction schema version 1. Clients +value and is typically about 205 bytes with correction schema version 1. Clients must support Read-Long and must not assume that one Read Response contains the full snapshot. @@ -220,7 +220,7 @@ full snapshot. |---|---:|---:|---| | Measures | ~120 B | ~135 B | One notification when MTU is at least 138 | | Status Read | ~95 B | ~115 B | Read; notifications carry small deltas | -| Config Read | ~239 B | <512 B | Read-Long / Read Blob | +| Config Read | ~205 B | <512 B | Read-Long / Read Blob | | Config Notify | — | <180 B | One notification when MTU is at least 185 | | History control | ~40 B | ~180 B | One notification per response | | History data | 227 B | 227 B | One notification when MTU is at least 230 | @@ -510,7 +510,7 @@ This characteristic supports three operations: Read the characteristic to receive the full device configuration. -#### Payload (18-key CBOR map) +#### Payload (16-key CBOR map) | Key | Type | Description | |---|---|---| @@ -518,9 +518,7 @@ Read the characteristic to receive the full device configuration. | `"temp_f"` | bool | `true` = Fahrenheit, `false` = Celsius | | `"pm_aqi"` | bool | `true` = US AQI for PM, `false` = raw ug/m3 | | `"gps_mode"` | text | GPS mode (see table below) | -| `"inact_to"` | uint | Inactivity timeout (seconds) | | `"auto_lock"` | uint | Auto-lock timeout (seconds) | -| `"dev_name"` | text | User-defined device name | | `"op_mode"` | text | Operating mode (see table below) | | `"fled"` | uint | Front (display) LED brightness: 0=Off, 1=Dim, 2=Mid, 3=Bright | | `"bled"` | uint | Back (AQI) LED brightness: 0=Off, 1=Dim, 2=Mid, 3=Bright | @@ -568,9 +566,7 @@ them and persisted loading canonicalizes them. "temp_f": false, "pm_aqi": false, "gps_mode": "tracking", - "inact_to": 300, "auto_lock": 60, - "dev_name": "My AGo", "op_mode": "portable", "fled": 3, "bled": 3, @@ -616,9 +612,7 @@ silently ignored for backward compatibility. They do not modify any setting. | `"temp_f"` | bool | | | `"pm_aqi"` | bool | | | `"gps_mode"` | text | `"off"`, `"tracking"`, or `"always"` | -| `"inact_to"` | uint | | | `"auto_lock"` | uint | | -| `"dev_name"` | text | Max 64 characters | | `"op_mode"` | text | `"portable"`, `"stationary"`, or `"offline"` | | `"fled"` | uint | 0–3 (front LED brightness) | | `"bled"` | uint | 0–3 (back LED brightness) | @@ -809,7 +803,7 @@ which normally changes one setting at a time) yields a 2-key map: Merge the changed key(s) into your local model. Production sends no Config notification for a no-op write. The full config is always available via **Read / Read-Long** (no `"type"` key) — re-read it on connect to establish the baseline. -The snapshot is typically about 239 bytes with schema version 1, so clients must +The snapshot is typically about 205 bytes with schema version 1, so clients must collect Read-Long fragments when the negotiated MTU cannot carry the complete value. The `"type"` key distinguishes this from command notifications (all arrive on the same characteristic; Read always returns the config snapshot @@ -1611,8 +1605,8 @@ negotiated interval; only its speed is affected. ### Required MTUs by operation -- **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 +- **Config Read**: the full 16-key snapshot 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. - **Config and Status notifications**: notifications contain deltas, not the diff --git a/products/go/main/go_ble.cpp b/products/go/main/go_ble.cpp index fb6060c..4accfe0 100644 --- a/products/go/main/go_ble.cpp +++ b/products/go/main/go_ble.cpp @@ -1815,15 +1815,9 @@ static void enc_pm_aqi(CborEncoder &m, const GoSettings &s) { static void enc_gps_mode(CborEncoder &m, const GoSettings &s) { cbor_encode_text_stringz(&m, gps_mode_to_wire(s.gps_mode)); } -static void enc_inact_to(CborEncoder &m, const GoSettings &s) { - cbor_encode_uint(&m, static_cast(s.inactivity_timeout_seconds)); -} static void enc_auto_lock(CborEncoder &m, const GoSettings &s) { cbor_encode_uint(&m, static_cast(s.auto_lock_seconds)); } -static void enc_dev_name(CborEncoder &m, const GoSettings &s) { - cbor_encode_text_stringz(&m, s.device_name.c_str()); -} static void enc_op_mode(CborEncoder &m, const GoSettings &s) { cbor_encode_text_stringz(&m, operating_mode_to_wire(s.operating_mode)); } @@ -1871,15 +1865,9 @@ static bool dif_pm_aqi(const GoSettings &a, const GoSettings &b) { static bool dif_gps_mode(const GoSettings &a, const GoSettings &b) { return a.gps_mode != b.gps_mode; } -static bool dif_inact_to(const GoSettings &a, const GoSettings &b) { - return a.inactivity_timeout_seconds != b.inactivity_timeout_seconds; -} static bool dif_auto_lock(const GoSettings &a, const GoSettings &b) { return a.auto_lock_seconds != b.auto_lock_seconds; } -static bool dif_dev_name(const GoSettings &a, const GoSettings &b) { - return a.device_name != b.device_name; -} static bool dif_op_mode(const GoSettings &a, const GoSettings &b) { return a.operating_mode != b.operating_mode; } @@ -1925,9 +1913,7 @@ static const ConfigField CONFIG_FIELDS[] = { {BLE_KEY_TEMP_F, enc_temp_f, dif_temp_f}, {BLE_KEY_PM_AQI, enc_pm_aqi, dif_pm_aqi}, {BLE_KEY_GPS_MODE, enc_gps_mode, dif_gps_mode}, - {BLE_KEY_INACT_TO, enc_inact_to, dif_inact_to}, {BLE_KEY_AUTO_LOCK, enc_auto_lock, dif_auto_lock}, - {BLE_KEY_DEV_NAME, enc_dev_name, dif_dev_name}, {BLE_KEY_OP_MODE, enc_op_mode, dif_op_mode}, {BLE_KEY_FRONT_LED, enc_fled, dif_fled}, {BLE_KEY_BACK_LED, enc_bled, dif_bled}, @@ -2211,14 +2197,6 @@ BleConfigDecodeResult BleService::decode_config_write(const uint8_t *buf, size_t else if (key_is(BLE_KEY_PM_INT) || key_is(BLE_KEY_OTHER_INT) || key_is(BLE_KEY_DISP_INT)) { cbor_value_advance(&it); handled = true; - } else if (key_is(BLE_KEY_INACT_TO)) { - cbor_value_advance(&it); - result.recognized_config_key_count++; - uint64_t v = 0; - if (cbor_value_is_unsigned_integer(&it) && cbor_value_get_uint64(&it, &v) == CborNoError) { - settings.inactivity_timeout_seconds = static_cast(v); - } - handled = true; } else if (key_is(BLE_KEY_AUTO_LOCK)) { cbor_value_advance(&it); result.recognized_config_key_count++; @@ -2388,17 +2366,6 @@ BleConfigDecodeResult BleService::decode_config_write(const uint8_t *buf, size_t settings.operating_mode = str_to_operating_mode(text); } handled = true; - } else if (key_is(BLE_KEY_DEV_NAME)) { - cbor_value_advance(&it); - result.recognized_config_key_count++; - char text[65] = {}; - if (cbor_value_is_text_string(&it)) { - size_t slen = sizeof(text) - 1; - cbor_value_copy_text_string(&it, text, &slen, nullptr); - text[slen] = '\0'; - settings.device_name = text; - } - handled = true; } // --- Aiding command payload fields --- else if (key_is(BLE_KEY_LAT)) { diff --git a/products/go/main/go_ble_protocol.h b/products/go/main/go_ble_protocol.h index 22a93d3..c478db0 100644 --- a/products/go/main/go_ble_protocol.h +++ b/products/go/main/go_ble_protocol.h @@ -73,9 +73,7 @@ inline constexpr const char *BLE_KEY_DISP_INT = "disp_int"; inline constexpr const char *BLE_KEY_TEMP_F = "temp_f"; inline constexpr const char *BLE_KEY_PM_AQI = "pm_aqi"; inline constexpr const char *BLE_KEY_GPS_MODE = "gps_mode"; -inline constexpr const char *BLE_KEY_INACT_TO = "inact_to"; inline constexpr const char *BLE_KEY_AUTO_LOCK = "auto_lock"; -inline constexpr const char *BLE_KEY_DEV_NAME = "dev_name"; inline constexpr const char *BLE_KEY_OP_MODE = "op_mode"; inline constexpr const char *BLE_KEY_FRONT_LED = "fled"; inline constexpr const char *BLE_KEY_BACK_LED = "bled"; diff --git a/products/go/main/go_settings.cpp b/products/go/main/go_settings.cpp index 7ef7eed..83d55d0 100644 --- a/products/go/main/go_settings.cpp +++ b/products/go/main/go_settings.cpp @@ -9,10 +9,8 @@ static constexpr const char *TAG = "Settings"; namespace { constexpr const char *KEY_MEASURE_INTERVAL_SECONDS = "mi"; -constexpr const char *KEY_INACTIVITY_TIMEOUT_SECONDS = "ito"; constexpr const char *KEY_GPS_MODE = "gpm"; constexpr const char *KEY_OPERATING_MODE = "opm"; -constexpr const char *KEY_DEVICE_NAME = "dn"; constexpr const char *KEY_USE_FAHRENHEIT = "uf"; constexpr const char *KEY_PM_USE_USAQI = "pmu"; constexpr const char *KEY_AUTO_LOCK_SECONDS = "als"; @@ -58,8 +56,6 @@ bool is_fg_learning_stage_valid(int value) { bool is_byte_valid(int value) { return value >= 0 && value <= 255; } -bool is_inactivity_timeout_valid(int value) { return value >= 5 && value <= 600; } - bool is_operating_mode_valid(int value) { return value >= 0 && value <= 2; } bool is_configuration_control_valid(int value) { @@ -71,8 +67,6 @@ bool is_auto_lock_valid(int value) { return value == 0 || value == 10 || value == 30 || value == 60; } -bool is_device_name_valid(const std::string &value) { return !value.empty() && value.size() <= 64; } - bool is_pm25_algorithm_valid(int value) { return value >= static_cast(Pm25CorrectionAlgorithm::None) && value <= static_cast(Pm25CorrectionAlgorithm::CustomViaPm25Raw); @@ -190,13 +184,6 @@ GoSettings load_go_settings(ConfigStore &store) { settings.measure_interval_seconds = measure_interval_seconds; } - int inactivity_timeout_seconds = 0; - if (store.get_int(KEY_INACTIVITY_TIMEOUT_SECONDS, inactivity_timeout_seconds) == - ConfigStoreResult::OK && - is_inactivity_timeout_valid(inactivity_timeout_seconds)) { - settings.inactivity_timeout_seconds = inactivity_timeout_seconds; - } - int gps_mode = 0; if (store.get_int(KEY_GPS_MODE, gps_mode) == ConfigStoreResult::OK && is_gps_mode_valid(gps_mode)) { @@ -209,12 +196,6 @@ GoSettings load_go_settings(ConfigStore &store) { settings.operating_mode = static_cast(operating_mode); } - std::string device_name; - if (store.get_string(KEY_DEVICE_NAME, device_name) == ConfigStoreResult::OK && - is_device_name_valid(device_name)) { - settings.device_name = device_name; - } - bool use_fahrenheit = false; if (store.get_bool(KEY_USE_FAHRENHEIT, use_fahrenheit) == ConfigStoreResult::OK) { settings.use_fahrenheit = use_fahrenheit; @@ -310,8 +291,7 @@ bool GoSettings::equals(const GoSettings &other) const { return measure_interval_seconds == other.measure_interval_seconds && use_fahrenheit == other.use_fahrenheit && pm_use_usaqi == other.pm_use_usaqi && gps_mode == other.gps_mode && operating_mode == other.operating_mode && - inactivity_timeout_seconds == other.inactivity_timeout_seconds && - auto_lock_seconds == other.auto_lock_seconds && device_name == other.device_name && + auto_lock_seconds == other.auto_lock_seconds && front_led_brightness == other.front_led_brightness && back_led_brightness == other.back_led_brightness && touch_led_intensity == other.touch_led_intensity && @@ -333,10 +313,6 @@ bool is_go_settings_valid(const GoSettings &settings) { return false; } - if (!is_inactivity_timeout_valid(settings.inactivity_timeout_seconds)) { - return false; - } - if (!is_gps_mode_valid(static_cast(settings.gps_mode))) { return false; } @@ -366,10 +342,6 @@ bool is_go_settings_valid(const GoSettings &settings) { return false; } - if (!is_device_name_valid(settings.device_name)) { - return false; - } - if (!is_led_brightness_valid(static_cast(settings.front_led_brightness)) || !is_led_brightness_valid(static_cast(settings.back_led_brightness)) || !is_touch_led_intensity_valid(static_cast(settings.touch_led_intensity))) { @@ -393,11 +365,6 @@ bool save_go_settings(ConfigStore &store, const GoSettings &settings) { return false; } - if (store.set_int(KEY_INACTIVITY_TIMEOUT_SECONDS, settings.inactivity_timeout_seconds) != - ConfigStoreResult::OK) { - return false; - } - if (store.set_int(KEY_GPS_MODE, static_cast(settings.gps_mode)) != ConfigStoreResult::OK) { return false; } @@ -407,10 +374,6 @@ bool save_go_settings(ConfigStore &store, const GoSettings &settings) { return false; } - if (store.set_string(KEY_DEVICE_NAME, settings.device_name) != ConfigStoreResult::OK) { - return false; - } - if (store.set_bool(KEY_USE_FAHRENHEIT, settings.use_fahrenheit) != ConfigStoreResult::OK) { return false; } @@ -573,18 +536,17 @@ bool clear_factory_settings(ConfigStore &store) { void print_settings(const GoSettings &settings) { AG_LOGI(TAG, "** settings | meas_int=%d | gps_mode=%d " - "op_mode=%d | inactivity_to=%d auto_lock=%d | fahrenheit=%s usaqi=%s | " + "op_mode=%d | auto_lock=%d | fahrenheit=%s usaqi=%s | " "led: front=%d back=%d touch=%d | buzzer=%s | " - "device_name=%s | disable_cloud=%s config_control=%d co2_abc_days=%d " + "disable_cloud=%s config_control=%d co2_abc_days=%d " "tvoc_learning_offset=%d nox_learning_offset=%d static_ip=%s " "onboarding_done=%s **", settings.measure_interval_seconds, settings.gps_mode, settings.operating_mode, - settings.inactivity_timeout_seconds, settings.auto_lock_seconds, - settings.use_fahrenheit ? "true" : "false", settings.pm_use_usaqi ? "true" : "false", - static_cast(settings.front_led_brightness), + settings.auto_lock_seconds, settings.use_fahrenheit ? "true" : "false", + settings.pm_use_usaqi ? "true" : "false", static_cast(settings.front_led_brightness), static_cast(settings.back_led_brightness), static_cast(settings.touch_led_intensity), settings.buzzer_enabled ? "on" : "off", - settings.device_name.c_str(), settings.disable_cloud ? "true" : "false", + settings.disable_cloud ? "true" : "false", static_cast(settings.configuration_control), settings.co2_abc_days, settings.tvoc_learning_offset, settings.nox_learning_offset, settings.static_ip.ip != 0 ? "set" : "dhcp", settings.onboarding_done ? "true" : "false"); diff --git a/products/go/main/go_settings.h b/products/go/main/go_settings.h index 26d9fe7..dfc493e 100644 --- a/products/go/main/go_settings.h +++ b/products/go/main/go_settings.h @@ -1,8 +1,6 @@ #ifndef GO_SETTINGS_H #define GO_SETTINGS_H -#include - #include "config_store.h" #include "go_config_types.h" #include "go_types.h" @@ -23,12 +21,8 @@ struct GoSettings { // --- Device behavior --- OperatingMode operating_mode = OperatingMode::Portable; - int inactivity_timeout_seconds = 5; int auto_lock_seconds = 10; // 0 = auto-lock disabled - // --- Identity --- - std::string device_name = "airgradient-go"; - // --- LED brightness --- LedBrightness front_led_brightness = LedBrightness::Off; LedBrightness back_led_brightness = LedBrightness::Off; diff --git a/products/go/tests/ble-integration/ago_protocol.py b/products/go/tests/ble-integration/ago_protocol.py index e0b1dee..2cb4548 100644 --- a/products/go/tests/ble-integration/ago_protocol.py +++ b/products/go/tests/ble-integration/ago_protocol.py @@ -119,8 +119,8 @@ "meas_int", "temp_f", "pm_aqi", "gps_mode", - "inact_to", "auto_lock", - "dev_name", "op_mode", + "auto_lock", + "op_mode", "fled", "bled", "tled", "buz", "abc", "tlo", "nlo", "pm25_corr", "temp_corr", "hum_corr", @@ -143,9 +143,7 @@ "temp_f": (bool,), "pm_aqi": (bool,), "gps_mode": (str,), - "inact_to": (int,), "auto_lock": (int,), - "dev_name": (str,), "op_mode": (str,), "fled": (int,), "bled": (int,), diff --git a/products/go/tests/ble-integration/test_config.py b/products/go/tests/ble-integration/test_config.py index 697b6e0..1cd039d 100644 --- a/products/go/tests/ble-integration/test_config.py +++ b/products/go/tests/ble-integration/test_config.py @@ -36,7 +36,7 @@ async def config_payload(ago_client: BleakClient) -> dict: # --------------------------------------------------------------------------- class TestConfigRead: - """Verify reading the Config characteristic returns a valid 18-key map.""" + """Verify reading the Config characteristic returns a valid 16-key map.""" def test_read_config(self, config_payload: dict): """Reading Config must return valid CBOR map.""" @@ -45,7 +45,7 @@ def test_read_config(self, config_payload: dict): ) def test_all_keys_present(self, config_payload: dict): - """Config read must contain exactly the 18 expected keys.""" + """Config read must contain exactly the 16 expected keys.""" missing = proto.CONFIG_READ_KEYS - set(config_payload.keys()) extra = set(config_payload.keys()) - proto.CONFIG_READ_KEYS assert not missing, f"Missing Config keys: {missing}" diff --git a/products/go/tests/go_ble.tests.cpp b/products/go/tests/go_ble.tests.cpp index 774be12..3c17759 100644 --- a/products/go/tests/go_ble.tests.cpp +++ b/products/go/tests/go_ble.tests.cpp @@ -453,7 +453,6 @@ static uint64_t pm25_correction_flags(const uint8_t *data, size_t len) { // Conservative single-PDU budget (mirrors BLE_NOTIFY_MAX_BYTES in go_ble.cpp); // the 185-byte minimum MTU yields a 182-byte PDU, so 180 is the test bound. static constexpr size_t TEST_NOTIFY_BUDGET = 180; -static constexpr int TEST_MAX_INACTIVITY_TIMEOUT_SECONDS = 600; static constexpr int TEST_MAX_AUTO_LOCK_SECONDS = 60; // =========================================================================== @@ -836,7 +835,7 @@ TEST_CASE("BLE: encode_status clamps negative battery values to 0") { // CBOR encoding: Config // --------------------------------------------------------------------------- -TEST_CASE("BLE: encode_config produces 18 keys with compact device config") { +TEST_CASE("BLE: encode_config produces 16 keys with compact device config") { StorageService storage(*null_cache_ptr, *null_nand_ptr); BleService svc(nullptr, storage, default_ble_server); auto settings = make_default_settings(); @@ -846,7 +845,7 @@ TEST_CASE("BLE: encode_config produces 18 keys with compact device config") { REQUIRE(len > 0); auto entries = decode_cbor_map(buf, len); - CHECK(entries.size() == 18); + CHECK(entries.size() == 16); CHECK(find_entry(entries, "meas_int") != nullptr); CHECK(find_entry(entries, "pm_int") == nullptr); @@ -855,9 +854,7 @@ TEST_CASE("BLE: encode_config produces 18 keys with compact device config") { CHECK(find_entry(entries, "temp_f") != nullptr); CHECK(find_entry(entries, "pm_aqi") != nullptr); CHECK(find_entry(entries, "gps_mode") != nullptr); - CHECK(find_entry(entries, "inact_to") != nullptr); CHECK(find_entry(entries, "auto_lock") != nullptr); - CHECK(find_entry(entries, "dev_name") != nullptr); CHECK(find_entry(entries, "op_mode") != nullptr); CHECK(find_entry(entries, "fled") != nullptr); CHECK(find_entry(entries, "bled") != nullptr); @@ -879,7 +876,6 @@ TEST_CASE("BLE: encode_config values match settings") { s.measure_interval_seconds = 30; s.use_fahrenheit = true; s.gps_mode = GpsMode::AlwaysOn; - s.device_name = "test-device"; s.operating_mode = OperatingMode::Stationary; s.buzzer_enabled = true; s.co2_abc_days = CO2_ABC_DAYS_DISABLED; @@ -897,7 +893,6 @@ TEST_CASE("BLE: encode_config values match settings") { CHECK(find_entry(entries, "disp_int") == nullptr); CHECK(find_entry(entries, "temp_f")->bool_val == true); CHECK(find_entry(entries, "gps_mode")->text_val == "always"); - CHECK(find_entry(entries, "dev_name")->text_val == "test-device"); CHECK(find_entry(entries, "op_mode")->text_val == "stationary"); CHECK(find_entry(entries, "buz")->bool_val == true); CHECK(find_entry(entries, "abc")->int_val == CO2_ABC_DAYS_DISABLED); @@ -1026,7 +1021,7 @@ TEST_CASE("BLE: notify_config sends delta and keeps READ as full snapshot") { REQUIRE(config_char.notify_count == 1); auto read_entries = decode_cbor_map(config_char.last_value.data(), config_char.last_value.size()); - CHECK(read_entries.size() == 18); // full snapshot, no "type" + CHECK(read_entries.size() == 16); // full snapshot, no "type" CHECK(find_entry(read_entries, "type") == nullptr); auto notify_entries = decode_cbor_map(config_char.last_notified_value.data(), @@ -1043,33 +1038,13 @@ TEST_CASE("BLE: notify_config sends delta and keeps READ as full snapshot") { // Encoder budget + overflow guards // --------------------------------------------------------------------------- -TEST_CASE("BLE: largest single-field config delta (dev_name 64) within budget") { - StorageService storage(*null_cache_ptr, *null_nand_ptr); - BleService svc(nullptr, storage, default_ble_server); - - GoSettings prev = make_default_settings(); - GoSettings cur = prev; - cur.device_name = std::string(64, 'x'); // max-length device name - - uint8_t buf[256]; - size_t len = BleServiceTestAccess::encode_config_delta(svc, buf, sizeof(buf), prev, cur); - REQUIRE(len > 0); - CHECK(len <= TEST_NOTIFY_BUDGET); - - auto entries = decode_cbor_map(buf, len); - CHECK(entries.size() == 2); // "type" + "dev_name" - CHECK(find_entry(entries, "dev_name")->text_val == std::string(64, 'x')); -} - TEST_CASE("BLE: max-size config snapshot encodes within the 512-byte ceiling") { StorageService storage(*null_cache_ptr, *null_nand_ptr); BleService svc(nullptr, storage, default_ble_server); GoSettings s = make_default_settings(); s.measure_interval_seconds = 3600; - s.inactivity_timeout_seconds = TEST_MAX_INACTIVITY_TIMEOUT_SECONDS; s.auto_lock_seconds = TEST_MAX_AUTO_LOCK_SECONDS; - s.device_name = std::string(64, 'x'); s.gps_mode = GpsMode::OnWhenTracking; s.operating_mode = OperatingMode::Stationary; s.buzzer_enabled = true; @@ -1098,7 +1073,6 @@ TEST_CASE("BLE: encode_config returns 0 on encoder overflow") { BleService svc(nullptr, storage, default_ble_server); GoSettings s = make_default_settings(); - s.device_name = std::string(64, 'x'); uint8_t tiny[16]; // deliberately too small for the full snapshot size_t len = BleServiceTestAccess::encode_config(svc, tiny, sizeof(tiny), s); @@ -1650,9 +1624,9 @@ TEST_CASE("BLE: decode_config_write with deprecated key has no unknown keys") { CHECK(settings.measure_interval_seconds == 10); // unchanged } -TEST_CASE("BLE: decode_config_write with unknown key sets has_unknown_keys") { +TEST_CASE("BLE: decode_config_write treats removed dev_name as unknown") { uint8_t buf[64]; - size_t len = encode_set_uint(buf, sizeof(buf), "bad_key", 42); + size_t len = encode_set_text(buf, sizeof(buf), "dev_name", "test-device"); GoSettings settings; auto result = BleService::decode_config_write(buf, len, settings); diff --git a/products/go/tests/go_orchestrator.tests.cpp b/products/go/tests/go_orchestrator.tests.cpp index e5c6bad..282597a 100644 --- a/products/go/tests/go_orchestrator.tests.cpp +++ b/products/go/tests/go_orchestrator.tests.cpp @@ -1382,7 +1382,6 @@ TEST_CASE("factory_reset: resets settings to defaults without keeping tracking s A::settings(orch).operating_mode = OperatingMode::Offline; A::settings(orch).gps_mode = GpsMode::AlwaysOff; - A::settings(orch).device_name = "custom-name"; A::settings(orch).configuration_control = ConfigurationControl::Local; A::settings(orch).corrections.temperature = { LinearCorrectionAlgorithm::Custom, @@ -1404,7 +1403,6 @@ TEST_CASE("factory_reset: resets settings to defaults without keeping tracking s CHECK(A::settings(orch).operating_mode == OperatingMode::Portable); CHECK(A::settings(orch).gps_mode == GpsMode::OnWhenTracking); - CHECK(A::settings(orch).device_name == "airgradient-go"); CHECK(A::settings(orch).configuration_control == ConfigurationControl::Both); CHECK(A::settings(orch).corrections.temperature.algorithm == LinearCorrectionAlgorithm::None); CHECK(test_spy::cloud_set_fetch_enabled_count == 1); @@ -1422,7 +1420,6 @@ TEST_CASE("factory_reset: resets settings to defaults without keeping tracking s TEST_CASE("factory_reset: required reset failure does not activate default settings", "[Orchestrator][factory_reset][failure]") { TestFixture f; - f.settings.device_name = "keep-me"; f.settings.disable_cloud = true; f.settings.configuration_control = ConfigurationControl::Local; auto orch = f.make_orchestrator(); @@ -1433,7 +1430,6 @@ TEST_CASE("factory_reset: required reset failure does not activate default setti FORBID_CALL(f.mock_config, commit()); CHECK_FALSE(A::factory_reset(orch)); - CHECK(A::settings(orch).device_name == "keep-me"); CHECK(A::settings(orch).disable_cloud); CHECK(A::settings(orch).configuration_control == ConfigurationControl::Local); CHECK(test_spy::cloud_set_disable_count == 0); @@ -1663,7 +1659,7 @@ TEST_CASE("manufacturing: shutdown resets settings while preserving corrections" ALLOW_CALL(f.mock_config, erase(trompeloeil::_)).RETURN(ConfigStoreResult::OK); ALLOW_CALL(f.mock_config, commit()).RETURN(ConfigStoreResult::OK); A::set_manufacturing_mode(orch, true); - A::settings(orch).device_name = "manufacturing-name"; + A::settings(orch).auto_lock_seconds = 60; A::settings(orch).corrections.pm25 = { Pm25CorrectionAlgorithm::CustomViaPm25Raw, 1.2f, @@ -1686,7 +1682,7 @@ TEST_CASE("manufacturing: shutdown resets settings while preserving corrections" CHECK(test_spy::routes_cleared); // factory_reset ran CHECK(test_spy::ble_delete_all_bonds_called); // bond cleanup attempted CHECK(test_spy::shutdown_called); // power-off still happened - CHECK(A::settings(orch).device_name == "airgradient-go"); + CHECK(A::settings(orch).auto_lock_seconds == 10); CHECK(A::settings(orch).corrections.pm25.algorithm == Pm25CorrectionAlgorithm::CustomViaPm25Raw); CHECK(A::settings(orch).corrections.pm25.scaling_factor == 1.2f); CHECK(A::settings(orch).corrections.pm25.intercept == 0.4f); diff --git a/products/go/tests/go_settings.tests.cpp b/products/go/tests/go_settings.tests.cpp index a472acc..fbfd602 100644 --- a/products/go/tests/go_settings.tests.cpp +++ b/products/go/tests/go_settings.tests.cpp @@ -128,7 +128,7 @@ class FakeConfigStore : public ConfigStore { std::size_t _write_attempt_count = 0; }; -static constexpr std::size_t GO_SETTINGS_WRITE_COUNT = 32; +static constexpr std::size_t GO_SETTINGS_WRITE_COUNT = 30; // ============================================================================ // Defaults — load from empty store returns struct defaults @@ -139,10 +139,8 @@ TEST_CASE("load from empty store returns struct defaults", "[settings]") { GoSettings s = load_go_settings(store); REQUIRE(s.measure_interval_seconds == 10); - REQUIRE(s.inactivity_timeout_seconds == 5); REQUIRE(s.gps_mode == GpsMode::OnWhenTracking); REQUIRE(s.operating_mode == OperatingMode::Portable); - REQUIRE(s.device_name == "airgradient-go"); REQUIRE(s.use_fahrenheit == false); REQUIRE(s.pm_use_usaqi == false); REQUIRE(s.auto_lock_seconds == 10); @@ -205,10 +203,8 @@ TEST_CASE("save then load round-trips all fields", "[settings]") { GoSettings original; original.measure_interval_seconds = 60; - original.inactivity_timeout_seconds = 30; original.gps_mode = GpsMode::AlwaysOn; original.operating_mode = OperatingMode::Offline; - original.device_name = "my-device"; original.use_fahrenheit = true; original.pm_use_usaqi = true; original.auto_lock_seconds = 30; @@ -223,10 +219,8 @@ TEST_CASE("save then load round-trips all fields", "[settings]") { GoSettings loaded = load_go_settings(store); REQUIRE(loaded.measure_interval_seconds == original.measure_interval_seconds); - REQUIRE(loaded.inactivity_timeout_seconds == original.inactivity_timeout_seconds); REQUIRE(loaded.gps_mode == original.gps_mode); REQUIRE(loaded.operating_mode == original.operating_mode); - REQUIRE(loaded.device_name == original.device_name); REQUIRE(loaded.use_fahrenheit == original.use_fahrenheit); REQUIRE(loaded.pm_use_usaqi == original.pm_use_usaqi); REQUIRE(loaded.auto_lock_seconds == original.auto_lock_seconds); @@ -586,17 +580,6 @@ TEST_CASE("save rejects invalid measure_interval_seconds", "[settings][validatio REQUIRE_FALSE(save_go_settings(store, s)); } -TEST_CASE("save rejects invalid inactivity_timeout_seconds", "[settings][validation]") { - FakeConfigStore store; - GoSettings s; - - s.inactivity_timeout_seconds = 4; - REQUIRE_FALSE(save_go_settings(store, s)); - - s.inactivity_timeout_seconds = 601; - REQUIRE_FALSE(save_go_settings(store, s)); -} - TEST_CASE("save rejects invalid gps_mode", "[settings][validation]") { FakeConfigStore store; GoSettings s; @@ -628,17 +611,6 @@ TEST_CASE("save rejects invalid auto_lock_seconds", "[settings][validation]") { REQUIRE_FALSE(save_go_settings(store, s)); } -TEST_CASE("save rejects invalid device_name", "[settings][validation]") { - FakeConfigStore store; - GoSettings s; - - s.device_name = ""; - REQUIRE_FALSE(save_go_settings(store, s)); - - s.device_name = std::string(65, 'x'); - REQUIRE_FALSE(save_go_settings(store, s)); -} - TEST_CASE("invalid configuration_control is rejected before writes", "[settings][config][validation]") { FakeConfigStore store; @@ -676,12 +648,10 @@ TEST_CASE("load ignores invalid stored values", "[settings][validation]") { REQUIRE(save_go_settings(store, valid)); // Overwrite specific keys with invalid values - store.set_int("mi", 0); // below range - store.set_int("gpm", 99); // invalid enum - store.set_int("opm", -1); // invalid enum - store.set_int("als", 42); // not in allowed set - store.set_int("ito", 3); // below range - store.set_string("dn", ""); // empty name + store.set_int("mi", 0); // below range + store.set_int("gpm", 99); // invalid enum + store.set_int("opm", -1); // invalid enum + store.set_int("als", 42); // not in allowed set GoSettings loaded = load_go_settings(store); @@ -690,8 +660,6 @@ TEST_CASE("load ignores invalid stored values", "[settings][validation]") { REQUIRE(loaded.gps_mode == GpsMode::OnWhenTracking); REQUIRE(loaded.operating_mode == OperatingMode::Portable); REQUIRE(loaded.auto_lock_seconds == 10); - REQUIRE(loaded.inactivity_timeout_seconds == 5); - REQUIRE(loaded.device_name == "airgradient-go"); } // ============================================================================ diff --git a/vhub/Go.vhub.json b/vhub/Go.vhub.json index 7c92dc0..ef1a467 100644 --- a/vhub/Go.vhub.json +++ b/vhub/Go.vhub.json @@ -240,7 +240,7 @@ "sub_category": "GATT protocol", "applies_to": ["V1"], "description": "The production Portable GATT profile and command contract pass the hardware integration suite", - "expected_result": "The Portable BLE integration suite exits with zero failures or unexpected skips; route tests run against the seeded route, while the two co2_cal command tests are deselected. Debug capture contains one BLE Measures map with pm1, pm25, pm10, and pres. With a client MTU that requires a long read, BLE Config returns all 18 documented keys without truncation. Writing dev_name=go-vhub-test persists through reboot and BLE Config Read, while advertising remains AirGradient Go . The complete recorded BLE Config is restored.", + "expected_result": "The Portable BLE integration suite exits with zero failures or unexpected skips; route tests run against the seeded route, while the two co2_cal command tests are deselected. Debug capture contains one BLE Measures map with pm1, pm25, pm10, and pres. With a client MTU that requires a long read, BLE Config returns all 16 documented keys without truncation. The complete recorded BLE Config is restored.", "notes": "Seed one completed route, bond the test host, and run pytest products/go/tests/ble-integration/ -v --log-cli-level=DEBUG with --deselect for TestConfigCommand::test_command_progress_and_result_format and ::test_read_after_command_returns_config_snapshot. Record the client and negotiated MTU used for the separate Read-Long check." }, {