diff --git a/Beacon.h b/Beacon.h index 210b7dd..13821b8 100644 --- a/Beacon.h +++ b/Beacon.h @@ -19,9 +19,14 @@ #if HAS_GPS == true // Beacon interval and timing -#define BEACON_INTERVAL_MS 30000 // 30 seconds between beacons +uint32_t beacon_interval_ms = 30000; // Default 30s, configurable via Settings #define BEACON_STARTUP_DELAY_MS 10000 // Wait 10s after boot before first beacon // BEACON_NO_HOST_TIMEOUT_MS and last_host_activity defined in GPS.h +bool beacon_enabled = true; // Configurable via Settings + +// Beacon interval options (ms) — indexed by roller selection +const uint32_t beacon_interval_options[] = { 10000, 30000, 60000, 300000, 600000 }; +#define BEACON_INTERVAL_OPTIONS_COUNT 5 // Beacon radio parameters — must match the router's LoRa interface #define BEACON_FREQ 868000000 @@ -69,6 +74,7 @@ void beacon_check_host_activity() { } void beacon_update() { + if (!beacon_enabled) { beacon_gate = 0; return; } // Don't beacon if host has been active recently if (last_host_activity > 0 && (millis() - last_host_activity < BEACON_NO_HOST_TIMEOUT_MS)) { @@ -115,7 +121,7 @@ void beacon_update() { // Respect beacon interval if (last_beacon_tx > 0 && - (millis() - last_beacon_tx < BEACON_INTERVAL_MS)) { + (millis() - last_beacon_tx < beacon_interval_ms)) { beacon_gate = 5; return; } diff --git a/GPS.h b/GPS.h index caca005..d4754a2 100644 --- a/GPS.h +++ b/GPS.h @@ -111,6 +111,38 @@ void gps_setup() { gps_ready = true; } +// GPS dynamic model options — indexed by roller selection +const uint8_t gps_model_ubx[] = { 0, 2, 3, 4 }; // Portable, Stationary, Pedestrian, Automotive +#define GPS_MODEL_OPTIONS_COUNT 4 +uint8_t gps_dynamic_model = 0; // Current model index (default: Portable) + +#if BOARD_MODEL == BOARD_TWATCH_ULT +void gps_set_dynamic_model(uint8_t model_index) { + if (model_index >= GPS_MODEL_OPTIONS_COUNT) return; + uint8_t dyn = gps_model_ubx[model_index]; + gps_dynamic_model = model_index; + uint8_t msg[] = { + 0xB5, 0x62, 0x06, 0x8A, 0x09, 0x00, + 0x00, 0x01, 0x00, 0x00, + 0x21, 0x00, 0x11, 0x20, + dyn, 0x00, 0x00 + }; + uint8_t ck_a = 0, ck_b = 0; + for (int i = 2; i < (int)sizeof(msg) - 2; i++) { ck_a += msg[i]; ck_b += ck_a; } + msg[sizeof(msg) - 2] = ck_a; + msg[sizeof(msg) - 1] = ck_b; + gps_serial.write(msg, sizeof(msg)); +} +#else +void gps_set_dynamic_model(uint8_t model_index) { + if (model_index >= GPS_MODEL_OPTIONS_COUNT) return; + gps_dynamic_model = model_index; + const char *cmds[] = { "$PCAS11,0*1D\r\n", "$PCAS11,1*1C\r\n", + "$PCAS11,2*1F\r\n", "$PCAS11,3*1E\r\n" }; + gps_serial.print(cmds[model_index]); +} +#endif + void gps_update() { if (!gps_ready) return; diff --git a/Gui.h b/Gui.h index edd33ae..9126522 100644 --- a/Gui.h +++ b/Gui.h @@ -434,11 +434,127 @@ static void gui_create_msg_screen(lv_obj_t *parent) { // --------------------------------------------------------------------------- // Screen: Settings (bottom tile — swipe up from watch face) // --------------------------------------------------------------------------- +static lv_obj_t *gui_set_disp_slider = NULL; +static lv_obj_t *gui_set_disp_val = NULL; +static lv_obj_t *gui_set_bcn_roller = NULL; +static lv_obj_t *gui_set_gps_roller = NULL; +static lv_obj_t *gui_set_bcn_sw = NULL; + +static void gui_set_disp_cb(lv_event_t *e) { + lv_obj_t *slider = (lv_obj_t *)lv_event_get_target(e); + int32_t val = lv_slider_get_value(slider); + display_blanking_timeout = (uint32_t)val * 1000; + char buf[8]; snprintf(buf, sizeof(buf), "%lds", (long)val); + lv_label_set_text(gui_set_disp_val, buf); + EEPROM.write(config_addr(ADDR_CONF_DISP_TIMEOUT), (uint8_t)val); + EEPROM.commit(); +} + +static void gui_set_bcn_en_cb(lv_event_t *e) { + lv_obj_t *sw = (lv_obj_t *)lv_event_get_target(e); + beacon_enabled = lv_obj_has_state(sw, LV_STATE_CHECKED); + EEPROM.write(config_addr(ADDR_CONF_BCN_EN), beacon_enabled ? 1 : 0); + EEPROM.commit(); +} + +static void gui_set_bcn_int_cb(lv_event_t *e) { + lv_obj_t *roller = (lv_obj_t *)lv_event_get_target(e); + uint16_t idx = lv_roller_get_selected(roller); + if (idx < BEACON_INTERVAL_OPTIONS_COUNT) { + beacon_interval_ms = beacon_interval_options[idx]; + EEPROM.write(config_addr(ADDR_CONF_BCN_INT), (uint8_t)idx); + EEPROM.commit(); + } +} + +static void gui_set_gps_model_cb(lv_event_t *e) { + lv_obj_t *roller = (lv_obj_t *)lv_event_get_target(e); + uint16_t idx = lv_roller_get_selected(roller); + if (idx < GPS_MODEL_OPTIONS_COUNT) { + gps_set_dynamic_model(idx); + EEPROM.write(config_addr(ADDR_CONF_GPS_MODEL), (uint8_t)idx); + EEPROM.commit(); + } +} + static void gui_create_settings_screen(lv_obj_t *parent) { gui_style_black_container(parent); - gui_label_at(parent, &lv_font_montserrat_14, GUI_COL_DIM, "SETTINGS", GUI_PAD, 12); - lv_obj_t *lbl = gui_label(parent, &font_mid, GUI_COL_MID, "Coming soon"); - lv_obj_align(lbl, LV_ALIGN_CENTER, 0, 0); + + // Child container for settings content — do NOT set flex on the tile itself + lv_obj_t *cont = lv_obj_create(parent); + lv_obj_remove_style_all(cont); + lv_obj_set_size(cont, GUI_W, GUI_H); + lv_obj_set_style_bg_color(cont, lv_color_hex(GUI_COL_BLACK), 0); + lv_obj_set_style_bg_opa(cont, LV_OPA_COVER, 0); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLLABLE); + + // Title + gui_label_at(cont, &lv_font_montserrat_14, GUI_COL_DIM, "SETTINGS", GUI_PAD, 12); + + // --- Row 1: Display timeout (y=50) --- + gui_label_at(cont, &lv_font_montserrat_14, GUI_COL_MID, "Display timeout", GUI_PAD, 55); + gui_set_disp_slider = lv_slider_create(cont); + lv_obj_set_size(gui_set_disp_slider, 180, 12); + lv_obj_set_pos(gui_set_disp_slider, GUI_PAD, 80); + lv_slider_set_range(gui_set_disp_slider, 5, 60); + lv_slider_set_value(gui_set_disp_slider, (int32_t)(display_blanking_timeout / 1000), LV_ANIM_OFF); + lv_obj_set_style_bg_color(gui_set_disp_slider, lv_color_hex(GUI_COL_DIM), 0); + lv_obj_set_style_bg_color(gui_set_disp_slider, lv_color_hex(GUI_COL_AMBER), LV_PART_INDICATOR); + lv_obj_set_style_bg_color(gui_set_disp_slider, lv_color_hex(GUI_COL_WHITE), LV_PART_KNOB); + lv_obj_set_style_pad_all(gui_set_disp_slider, 4, LV_PART_KNOB); + lv_obj_add_event_cb(gui_set_disp_slider, gui_set_disp_cb, LV_EVENT_VALUE_CHANGED, NULL); + char disp_buf[8]; snprintf(disp_buf, sizeof(disp_buf), "%lds", (long)(display_blanking_timeout / 1000)); + gui_set_disp_val = gui_label_at(cont, &lv_font_montserrat_14, GUI_COL_WHITE, disp_buf, GUI_PAD + 200, 75); + + gui_create_rule(cont, 110); + + // --- Row 2: Beacon enable (y=120) --- + gui_label_at(cont, &lv_font_montserrat_14, GUI_COL_MID, "Beacon", GUI_PAD, 125); + gui_set_bcn_sw = lv_switch_create(cont); + lv_obj_set_pos(gui_set_bcn_sw, GUI_W - GUI_PAD - 50, 120); + lv_obj_set_size(gui_set_bcn_sw, 50, 26); + if (beacon_enabled) lv_obj_add_state(gui_set_bcn_sw, LV_STATE_CHECKED); + lv_obj_set_style_bg_color(gui_set_bcn_sw, lv_color_hex(GUI_COL_DIM), 0); + lv_obj_set_style_bg_color(gui_set_bcn_sw, lv_color_hex(GUI_COL_AMBER), LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_event_cb(gui_set_bcn_sw, gui_set_bcn_en_cb, LV_EVENT_VALUE_CHANGED, NULL); + + gui_create_rule(cont, 160); + + // --- Row 3: Beacon interval (y=170) --- + gui_label_at(cont, &lv_font_montserrat_14, GUI_COL_MID, "Beacon interval", GUI_PAD, 175); + gui_set_bcn_roller = lv_roller_create(cont); + lv_roller_set_options(gui_set_bcn_roller, "10s\n30s\n1min\n5min\n10min", LV_ROLLER_MODE_NORMAL); + lv_obj_set_pos(gui_set_bcn_roller, GUI_W - GUI_PAD - 100, 170); + lv_obj_set_size(gui_set_bcn_roller, 100, 60); + lv_obj_set_style_bg_color(gui_set_bcn_roller, lv_color_hex(GUI_COL_BLACK), 0); + lv_obj_set_style_text_color(gui_set_bcn_roller, lv_color_hex(GUI_COL_WHITE), 0); + lv_obj_set_style_text_font(gui_set_bcn_roller, &lv_font_montserrat_14, 0); + lv_obj_set_style_bg_color(gui_set_bcn_roller, lv_color_hex(GUI_COL_AMBER), LV_PART_SELECTED); + lv_obj_set_style_text_color(gui_set_bcn_roller, lv_color_hex(GUI_COL_BLACK), LV_PART_SELECTED); + // Set initial selection from current beacon_interval_ms + for (uint8_t i = 0; i < BEACON_INTERVAL_OPTIONS_COUNT; i++) { + if (beacon_interval_options[i] == beacon_interval_ms) { + lv_roller_set_selected(gui_set_bcn_roller, i, LV_ANIM_OFF); + break; + } + } + lv_obj_add_event_cb(gui_set_bcn_roller, gui_set_bcn_int_cb, LV_EVENT_VALUE_CHANGED, NULL); + + gui_create_rule(cont, 245); + + // --- Row 4: GPS dynamic model (y=255) --- + gui_label_at(cont, &lv_font_montserrat_14, GUI_COL_MID, "GPS model", GUI_PAD, 260); + gui_set_gps_roller = lv_roller_create(cont); + lv_roller_set_options(gui_set_gps_roller, "Portable\nStationary\nPedestrian\nAutomotive", LV_ROLLER_MODE_NORMAL); + lv_obj_set_pos(gui_set_gps_roller, GUI_W - GUI_PAD - 140, 255); + lv_obj_set_size(gui_set_gps_roller, 140, 60); + lv_obj_set_style_bg_color(gui_set_gps_roller, lv_color_hex(GUI_COL_BLACK), 0); + lv_obj_set_style_text_color(gui_set_gps_roller, lv_color_hex(GUI_COL_WHITE), 0); + lv_obj_set_style_text_font(gui_set_gps_roller, &lv_font_montserrat_14, 0); + lv_obj_set_style_bg_color(gui_set_gps_roller, lv_color_hex(GUI_COL_AMBER), LV_PART_SELECTED); + lv_obj_set_style_text_color(gui_set_gps_roller, lv_color_hex(GUI_COL_BLACK), LV_PART_SELECTED); + lv_roller_set_selected(gui_set_gps_roller, gps_dynamic_model, LV_ANIM_OFF); + lv_obj_add_event_cb(gui_set_gps_roller, gui_set_gps_model_cb, LV_EVENT_VALUE_CHANGED, NULL); } // --------------------------------------------------------------------------- diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino index 2d30b37..3aa34a6 100644 --- a/RNode_Firmware.ino +++ b/RNode_Firmware.ino @@ -423,6 +423,19 @@ void setup() { lxmf_init_identity(); // Initialize IFAC authentication (load from NVS if provisioned) ifac_init(); + // Load user settings from config EEPROM + uint8_t s_disp = EEPROM.read(config_addr(ADDR_CONF_DISP_TIMEOUT)); + if (s_disp != 0xFF && s_disp >= 5 && s_disp <= 60) + display_blanking_timeout = (uint32_t)s_disp * 1000; + uint8_t s_bcn_int = EEPROM.read(config_addr(ADDR_CONF_BCN_INT)); + if (s_bcn_int != 0xFF && s_bcn_int < BEACON_INTERVAL_OPTIONS_COUNT) + beacon_interval_ms = beacon_interval_options[s_bcn_int]; + uint8_t s_gps_model = EEPROM.read(config_addr(ADDR_CONF_GPS_MODEL)); + if (s_gps_model != 0xFF && s_gps_model < GPS_MODEL_OPTIONS_COUNT) + gps_set_dynamic_model(s_gps_model); + uint8_t s_bcn_en = EEPROM.read(config_addr(ADDR_CONF_BCN_EN)); + if (s_bcn_en != 0xFF) + beacon_enabled = (s_bcn_en != 0); #endif if (console_active) { @@ -2309,7 +2322,7 @@ void twatch_enter_deep_sleep(bool beacon_timer) { // 6. Configure wakeup sources esp_sleep_enable_ext1_wakeup(1ULL << PMU_IRQ, ESP_EXT1_WAKEUP_ANY_LOW); if (beacon_timer) { - esp_sleep_enable_timer_wakeup((uint64_t)BEACON_INTERVAL_MS * 1000ULL); + esp_sleep_enable_timer_wakeup((uint64_t)beacon_interval_ms * 1000ULL); } // 7. Enter deep sleep (does not return) diff --git a/ROM.h b/ROM.h index 0fbe2db..8f5f6e5 100644 --- a/ROM.h +++ b/ROM.h @@ -66,6 +66,12 @@ #define ADDR_BCN_KEY 0x51 // Collector X25519 public key — 32 bytes (0x51-0x70) #define ADDR_BCN_IHASH 0x71 // Collector identity hash — 16 bytes (0x71-0x80) #define ADDR_BCN_DHASH 0x81 // Collector dest hash — 16 bytes (0x81-0x90) + + // User settings — stored in config region via config_addr() + #define ADDR_CONF_DISP_TIMEOUT 0x91 // Display blank timeout in seconds — 1 byte + #define ADDR_CONF_BCN_INT 0x92 // Beacon interval index — 1 byte + #define ADDR_CONF_GPS_MODEL 0x93 // GPS dynamic model index — 1 byte + #define ADDR_CONF_BCN_EN 0x94 // Beacon enable (0=off, 1=on) — 1 byte ////////////////////////////////// #endif