Fix Attaky usability issues (#423)
This commit is contained in:
1 parent
8e94e25036
commit
28fb57ef4c
7 files changed
+292
-17
No files matched your search
@@ -9,7 +9,7 @@
|
||||
namespace TouchPrefsSchema {
|
||||
|
||||
static constexpr uint16_t MAGIC = 0x5743; // 'WC' (WadaCfg)
|
||||
static constexpr uint8_t CURRENT_VERSION = 57; // v49: fem_lna default flip on the V4-R8; v50: map tile z/x/y line off by default (no new fields); v51: console_mode (boot into the text console; new trailing field, default OFF); v52: console_monitor (show incoming messages in the console, default ON); v54: boot_wifi_time + boot_wifi_open (cold-boot saved-Wi-Fi time sync, #383; both OFF); v55: loud_alerts (resonant-pitch chime, #388; OFF); v56: theme_mode (Day/Night firmware theme, #296; default Night); v57: gps_fuzz_m (displace the advertised position, #399; 0 = off)
|
||||
static constexpr uint8_t CURRENT_VERSION = 58; // v49: fem_lna default flip on the V4-R8; v50: map tile z/x/y line off by default (no new fields); v51: console_mode (boot into the text console; new trailing field, default OFF); v52: console_monitor (show incoming messages in the console, default ON); v54: boot_wifi_time + boot_wifi_open (cold-boot saved-Wi-Fi time sync, #383; both OFF); v55: loud_alerts (resonant-pitch chime, #388; OFF); v56: theme_mode (Day/Night firmware theme, #296; default Night); v57: gps_fuzz_m (displace the advertised position, #399; 0 = off); v58: Attaky notification blink enable + room/DM color indexes (#423)
|
||||
static constexpr uint8_t BROKEN_MID_INSERT_VERSION = 44;
|
||||
|
||||
// Persisted byte layout. New fields must be appended at the end: older blobs
|
||||
@@ -109,6 +109,9 @@ struct __attribute__((packed)) Config {
|
||||
// advert: a fresh random offset each time would let anyone averaging a night
|
||||
// of adverts recover the true centre, which is the opposite of the intent.
|
||||
uint16_t gps_fuzz_m;
|
||||
uint8_t attaky_notify_enabled; // v58: blink both keyboard indicators for incoming messages
|
||||
uint8_t attaky_notify_room_color; // v58: seven-color palette index for rooms/channels
|
||||
uint8_t attaky_notify_dm_color; // v58: seven-color palette index for direct messages
|
||||
};
|
||||
|
||||
static constexpr size_t HEADER_SIZE = offsetof(Config, bright);
|
||||
@@ -121,10 +124,10 @@ static_assert(offsetof(Config, web_mirror) == offsetof(Config, rx_queue) + sizeo
|
||||
// rejection is silent: cfgFlush just returns false and every preference stops
|
||||
// persisting, with nothing in the log to say why. The struct grows most releases,
|
||||
// so guard the ceiling here rather than discover it as "settings do not save" on
|
||||
// whichever board is using the file backend. 114 bytes today.
|
||||
// whichever board is using the file backend. 117 bytes today.
|
||||
static_assert(sizeof(Config) <= 2048,
|
||||
"Config exceeds the SdNvsPrefs value cap; prefs would silently stop saving");
|
||||
static_assert(offsetof(Config, gps_fuzz_m) + sizeof(Config::gps_fuzz_m) == sizeof(Config),
|
||||
static_assert(offsetof(Config, attaky_notify_dm_color) + sizeof(Config::attaky_notify_dm_color) == sizeof(Config),
|
||||
"new preference fields must remain trailing");
|
||||
// lang_file must stay immediately before the tail, or a v48-era blob overlays
|
||||
// onto the wrong bytes. Checking both ends means the next person to append is
|
||||
@@ -150,6 +153,15 @@ static_assert(offsetof(Config, theme_mode) ==
|
||||
static_assert(offsetof(Config, gps_fuzz_m) ==
|
||||
offsetof(Config, theme_mode) + sizeof(Config::theme_mode),
|
||||
"gps_fuzz_m must follow theme_mode");
|
||||
static_assert(offsetof(Config, attaky_notify_enabled) ==
|
||||
offsetof(Config, gps_fuzz_m) + sizeof(Config::gps_fuzz_m),
|
||||
"attaky_notify_enabled must follow gps_fuzz_m");
|
||||
static_assert(offsetof(Config, attaky_notify_room_color) ==
|
||||
offsetof(Config, attaky_notify_enabled) + sizeof(Config::attaky_notify_enabled),
|
||||
"attaky_notify_room_color must follow attaky_notify_enabled");
|
||||
static_assert(offsetof(Config, attaky_notify_dm_color) ==
|
||||
offsetof(Config, attaky_notify_room_color) + sizeof(Config::attaky_notify_room_color),
|
||||
"attaky_notify_dm_color must follow attaky_notify_room_color");
|
||||
|
||||
// Overlay a persisted blob on caller-provided defaults. Beta 57 wrote v44 with
|
||||
// retry_echo inserted before web_mirror, shifting every later value. That blob
|
||||
|
||||
@@ -144,6 +144,9 @@ static void cfgSetDefaults(TouchCfg& c) {
|
||||
c.loud_alerts = 0; // OFF: the standard chime pitch unless asked for
|
||||
c.theme_mode = 0; // Night: preserves the existing firmware appearance
|
||||
c.gps_fuzz_m = 0; // OFF: advertise the real position unless asked otherwise
|
||||
c.attaky_notify_enabled = 0; // OFF: incoming messages do not blink the keyboard indicators
|
||||
c.attaky_notify_room_color = 0; // red
|
||||
c.attaky_notify_dm_color = 1; // green
|
||||
c.compact_chat = 0; // OFF: bubble chat layout (opt-in IRC-style dense rows)
|
||||
c.clock_floor = 0; // no persisted send-timestamp floor yet
|
||||
c.rx_queue = 1; // ON: buffered receive (test-channel default; opt-out toggle in Radio & Mesh)
|
||||
@@ -259,6 +262,11 @@ static void cfgLoadOrMigrate() {
|
||||
if (stored_version < 55) { s_cfg.loud_alerts = 0; }
|
||||
if (stored_version < 56) { s_cfg.theme_mode = 0; } // Night: unchanged appearance
|
||||
if (stored_version < 57) { s_cfg.gps_fuzz_m = 0; } // OFF: real position
|
||||
if (stored_version < 58) {
|
||||
s_cfg.attaky_notify_enabled = 0;
|
||||
s_cfg.attaky_notify_room_color = 0;
|
||||
s_cfg.attaky_notify_dm_color = 1;
|
||||
}
|
||||
if (stored_version < 31) s_cfg.compact_chat = 0; // new trailing field: compact chat rows off by default
|
||||
if (stored_version < 32) s_cfg.clock_floor = 0; // new trailing field: no send-timestamp floor persisted yet (#89)
|
||||
if (stored_version < 33) s_cfg.rx_queue = 1; // buffered LoRa receive ON for the test channel (opt-out toggle in Radio & Mesh)
|
||||
@@ -1130,6 +1138,38 @@ bool touchPrefsSetMsgFlash(bool on) {
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
bool touchPrefsGetAttakyNotifyEnabled() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.attaky_notify_enabled != 0;
|
||||
}
|
||||
bool touchPrefsSetAttakyNotifyEnabled(bool on) {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.attaky_notify_enabled = on ? 1 : 0;
|
||||
return cfgFlush();
|
||||
}
|
||||
uint8_t touchPrefsGetAttakyNotifyRoomColor() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.attaky_notify_room_color < TOUCH_ATTAKY_NOTIFY_COLOR_COUNT
|
||||
? s_cfg.attaky_notify_room_color : 0;
|
||||
}
|
||||
bool touchPrefsSetAttakyNotifyRoomColor(uint8_t color) {
|
||||
if (color >= TOUCH_ATTAKY_NOTIFY_COLOR_COUNT) return false;
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.attaky_notify_room_color = color;
|
||||
return cfgFlush();
|
||||
}
|
||||
uint8_t touchPrefsGetAttakyNotifyDmColor() {
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
return s_cfg.attaky_notify_dm_color < TOUCH_ATTAKY_NOTIFY_COLOR_COUNT
|
||||
? s_cfg.attaky_notify_dm_color : 1;
|
||||
}
|
||||
bool touchPrefsSetAttakyNotifyDmColor(uint8_t color) {
|
||||
if (color >= TOUCH_ATTAKY_NOTIFY_COLOR_COUNT) return false;
|
||||
if (!s_begun) touchPrefsBegin();
|
||||
s_cfg.attaky_notify_dm_color = color;
|
||||
return cfgFlush();
|
||||
}
|
||||
|
||||
// Console mode is a BOOT mode, so this is read before the UI is built. It fails
|
||||
// safe by construction: the only value that means console is exactly 1, so a
|
||||
// corrupt or unreadable pref boots the graphical UI, which is the mode everyone
|
||||
|
||||
@@ -196,6 +196,14 @@ bool touchPrefsSetFemLna(bool on);
|
||||
bool touchPrefsGetMsgFlash();
|
||||
bool touchPrefsSetMsgFlash(bool on);
|
||||
|
||||
static constexpr uint8_t TOUCH_ATTAKY_NOTIFY_COLOR_COUNT = 7;
|
||||
bool touchPrefsGetAttakyNotifyEnabled();
|
||||
bool touchPrefsSetAttakyNotifyEnabled(bool on);
|
||||
uint8_t touchPrefsGetAttakyNotifyRoomColor();
|
||||
bool touchPrefsSetAttakyNotifyRoomColor(uint8_t color);
|
||||
uint8_t touchPrefsGetAttakyNotifyDmColor();
|
||||
bool touchPrefsSetAttakyNotifyDmColor(uint8_t color);
|
||||
|
||||
/* Advertise on boot (#76): fire one flood self-advert ~6s after boot so peers with auto-add on
|
||||
* refresh our pubkey (useful after a reflash wiped storage). Opt-in, default off. All boards. */
|
||||
bool touchPrefsGetConsoleMode(); // boot into the LVGL-free console (CONSOLE_MODE.md)
|
||||
|
||||
+154
-3
@@ -5498,7 +5498,7 @@ enum {
|
||||
CAT_SENSORS, // expansion kit + Show-Sensors-tab toggle (V4-with-kit)
|
||||
CAT_DISPLAY, // screen timeout, UI size, bubbles, theme, orientation
|
||||
CAT_KEYBOARD, // secondary layouts + accent popups
|
||||
CAT_SOUND, // notification sound
|
||||
CAT_SOUND, // notification sound, or Attaky keyboard-indicator blink
|
||||
CAT_QUICKREPLIES, // quick-reply macros
|
||||
CAT_LOCK, // lock-screen wallpaper + text colour (T-Deck only)
|
||||
CAT_GENERAL, // reboot, run setup, storage/SD, misc device prefs (was "System")
|
||||
@@ -5534,7 +5534,11 @@ static const SettingsCatDef kSettingsCats[CAT_COUNT] = {
|
||||
{ "Sensors", LV_SYMBOL_EYE_OPEN },
|
||||
{ "Display", LV_SYMBOL_IMAGE },
|
||||
{ "Keyboard", LV_SYMBOL_KEYBOARD },
|
||||
#if defined(ATTAKY_MESH_SERIES)
|
||||
{ "Notifications", LV_SYMBOL_AUDIO },
|
||||
#else
|
||||
{ "Sound", LV_SYMBOL_AUDIO },
|
||||
#endif
|
||||
{ "Quick replies", LV_SYMBOL_ENVELOPE },
|
||||
{ "Lock screen", LV_SYMBOL_EYE_CLOSE },
|
||||
{ "General", LV_SYMBOL_SETTINGS },
|
||||
@@ -9162,6 +9166,96 @@ static void volumeStepCb(lv_event_t* e) {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ATTAKY_MESH_SERIES)
|
||||
struct AttakyNotifyColor {
|
||||
uint32_t swatch_rgb;
|
||||
uint8_t output_mask;
|
||||
};
|
||||
static const AttakyNotifyColor kAttakyNotifyColors[] = {
|
||||
{ 0xFF0000, ATTAKY_NOTIFY_RED },
|
||||
{ 0x00FF00, ATTAKY_NOTIFY_GREEN },
|
||||
{ 0x0000FF, ATTAKY_NOTIFY_BLUE },
|
||||
{ 0xFFFF00, ATTAKY_NOTIFY_RED | ATTAKY_NOTIFY_GREEN },
|
||||
{ 0x00FFFF, ATTAKY_NOTIFY_GREEN | ATTAKY_NOTIFY_BLUE },
|
||||
{ 0xFF00FF, ATTAKY_NOTIFY_RED | ATTAKY_NOTIFY_BLUE },
|
||||
{ 0xFFFFFF, ATTAKY_NOTIFY_RED | ATTAKY_NOTIFY_GREEN | ATTAKY_NOTIFY_BLUE },
|
||||
};
|
||||
static_assert(sizeof(kAttakyNotifyColors) / sizeof(kAttakyNotifyColors[0]) ==
|
||||
TOUCH_ATTAKY_NOTIFY_COLOR_COUNT,
|
||||
"Attaky notification palette and persisted indexes must match");
|
||||
|
||||
static lv_obj_t* s_attaky_notify_swatches[2][TOUCH_ATTAKY_NOTIFY_COLOR_COUNT] = {};
|
||||
static uint8_t s_attaky_notify_mask = ATTAKY_NOTIFY_OFF;
|
||||
static uint8_t s_attaky_notify_transitions = 0;
|
||||
static bool s_attaky_notify_lit = false;
|
||||
static uint32_t s_attaky_notify_next_ms = 0;
|
||||
|
||||
static void attakyNotificationStop() {
|
||||
s_attaky_notify_mask = ATTAKY_NOTIFY_OFF;
|
||||
s_attaky_notify_transitions = 0;
|
||||
s_attaky_notify_lit = false;
|
||||
s_attaky_notify_next_ms = 0;
|
||||
if (!attakyKeyboardSetNotificationColor(ATTAKY_NOTIFY_OFF)) {
|
||||
s_attaky_notify_transitions = 1;
|
||||
s_attaky_notify_lit = true;
|
||||
s_attaky_notify_next_ms = millis() + 50;
|
||||
}
|
||||
}
|
||||
|
||||
static void attakyNotificationStart(uint8_t color_index) {
|
||||
if (!touchPrefsGetAttakyNotifyEnabled()) return;
|
||||
if (color_index >= TOUCH_ATTAKY_NOTIFY_COLOR_COUNT) color_index = 0;
|
||||
s_attaky_notify_mask = kAttakyNotifyColors[color_index].output_mask;
|
||||
s_attaky_notify_transitions = 6;
|
||||
s_attaky_notify_lit = false;
|
||||
s_attaky_notify_next_ms = millis();
|
||||
}
|
||||
|
||||
static void attakyNotificationTick(uint32_t now) {
|
||||
if (s_attaky_notify_transitions == 0 ||
|
||||
(int32_t)(now - s_attaky_notify_next_ms) < 0) return;
|
||||
const bool next_lit = !s_attaky_notify_lit;
|
||||
const uint8_t output = next_lit ? s_attaky_notify_mask : ATTAKY_NOTIFY_OFF;
|
||||
if (!attakyKeyboardSetNotificationColor(output)) {
|
||||
s_attaky_notify_next_ms = now + 50;
|
||||
return;
|
||||
}
|
||||
s_attaky_notify_lit = next_lit;
|
||||
--s_attaky_notify_transitions;
|
||||
s_attaky_notify_next_ms = s_attaky_notify_transitions ? now + 220 : 0;
|
||||
}
|
||||
|
||||
static void attakyNotifySwatchesRefresh(int row) {
|
||||
const uint8_t selected = row == 0
|
||||
? touchPrefsGetAttakyNotifyRoomColor()
|
||||
: touchPrefsGetAttakyNotifyDmColor();
|
||||
for (uint8_t i = 0; i < TOUCH_ATTAKY_NOTIFY_COLOR_COUNT; ++i) {
|
||||
lv_obj_t* swatch = s_attaky_notify_swatches[row][i];
|
||||
if (!swatch || !lv_obj_is_valid(swatch)) continue;
|
||||
lv_obj_set_style_border_width(swatch, i == selected ? 2 : 1, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_color(swatch,
|
||||
lv_color_hex(i == selected ? 0xFFFFFF : COLOR_BORDER), LV_PART_MAIN);
|
||||
}
|
||||
}
|
||||
|
||||
static void toggleAttakyNotificationsCb(lv_event_t* e) {
|
||||
if (lv_event_get_code(e) != LV_EVENT_VALUE_CHANGED) return;
|
||||
const bool on = lv_obj_has_state(lv_event_get_target(e), LV_STATE_CHECKED);
|
||||
touchPrefsSetAttakyNotifyEnabled(on);
|
||||
if (!on) attakyNotificationStop();
|
||||
}
|
||||
|
||||
static void attakyNotifyColorChosenCb(lv_event_t* e) {
|
||||
if (lv_event_get_code(e) != LV_EVENT_CLICKED) return;
|
||||
const uintptr_t choice = (uintptr_t)lv_event_get_user_data(e);
|
||||
const int row = (choice & 0x100u) ? 1 : 0;
|
||||
const uint8_t color = (uint8_t)(choice & 0xFFu);
|
||||
if (row == 0) touchPrefsSetAttakyNotifyRoomColor(color);
|
||||
else touchPrefsSetAttakyNotifyDmColor(color);
|
||||
attakyNotifySwatchesRefresh(row);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void settingsFieldFocusCb(lv_event_t* e) {
|
||||
const lv_event_code_t code = lv_event_get_code(e);
|
||||
// Drop LV_EVENT_PRESSED: it fires on touch-down before LVGL can tell
|
||||
@@ -9774,13 +9868,14 @@ static lv_obj_t* createSettingsModal(const char* title, SettingsModalKind kind)
|
||||
lv_obj_set_height(content, LV_SIZE_CONTENT);
|
||||
lv_obj_set_pos(content, 0, 0);
|
||||
lv_obj_set_style_pad_all(content, 0, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_right(content, 6, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(content, 0, LV_PART_MAIN);
|
||||
lv_obj_clear_flag(content, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
resetSettingsModalState();
|
||||
g_set_modal.root = root;
|
||||
g_set_modal.kind = kind;
|
||||
s_settings_content_w = (sw - 8) - 12; // modal body width minus its 6px padding each side
|
||||
s_settings_content_w = (sw - 8) - 12 - 6; // body content width minus the right control gutter
|
||||
#if CAP_KEYBOARD && CAP_KEYPAD_NAV
|
||||
// Physical keyboard: once the caller has finished adding this modal's fields
|
||||
// (deferred to the next frame), focus its first text field so the user can
|
||||
@@ -13437,7 +13532,49 @@ static void buildDeviceSettings(int sec) {
|
||||
// The "Show Sensors tab" toggle is appended here too (moved from Display).
|
||||
}
|
||||
|
||||
if (sec == DSEC_SOUND) { // --- Sound ---
|
||||
if (sec == DSEC_SOUND) { // --- Sound / Attaky notifications ---
|
||||
#if defined(ATTAKY_MESH_SERIES)
|
||||
{
|
||||
int rh = settingsRowLabel(body, y, 6, TR("Incoming message blink"), COLOR_SUB, nullptr, 56);
|
||||
lv_obj_t* sw = lv_switch_create(body);
|
||||
lv_obj_align(sw, LV_ALIGN_TOP_RIGHT, 0, y);
|
||||
if (touchPrefsGetAttakyNotifyEnabled()) lv_obj_add_state(sw, LV_STATE_CHECKED);
|
||||
lv_obj_add_event_cb(sw, toggleAttakyNotificationsCb, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
y += LV_MAX(34, rh + 12);
|
||||
}
|
||||
auto addNotifyColorRow = [&](const char* label, int row) {
|
||||
y += settingsRowLabel(body, y, 0, TR(label), COLOR_SUB, &g_font_12, 0) + 4;
|
||||
const lv_coord_t gap = 3;
|
||||
const lv_coord_t row_w = s_settings_content_w - 2;
|
||||
const lv_coord_t swatch_w = (row_w - gap * (TOUCH_ATTAKY_NOTIFY_COLOR_COUNT - 1)) /
|
||||
TOUCH_ATTAKY_NOTIFY_COLOR_COUNT;
|
||||
const lv_coord_t swatch_h = SC(26);
|
||||
for (uint8_t i = 0; i < TOUCH_ATTAKY_NOTIFY_COLOR_COUNT; ++i) {
|
||||
lv_obj_t* swatch = lv_btn_create(body);
|
||||
s_attaky_notify_swatches[row][i] = swatch;
|
||||
lv_obj_set_size(swatch, swatch_w, swatch_h);
|
||||
lv_obj_set_pos(swatch, 2 + i * (swatch_w + gap), y);
|
||||
lv_obj_set_style_radius(swatch, 4, LV_PART_MAIN);
|
||||
for (lv_style_selector_t state : { (lv_style_selector_t)LV_PART_MAIN,
|
||||
(lv_style_selector_t)(LV_PART_MAIN | LV_STATE_FOCUSED),
|
||||
(lv_style_selector_t)(LV_PART_MAIN | LV_STATE_FOCUS_KEY),
|
||||
(lv_style_selector_t)(LV_PART_MAIN | LV_STATE_PRESSED) }) {
|
||||
lv_obj_set_style_bg_color(swatch, lv_color_hex(kAttakyNotifyColors[i].swatch_rgb), state);
|
||||
lv_obj_set_style_bg_opa(swatch, LV_OPA_COVER, state);
|
||||
}
|
||||
lv_obj_set_style_outline_color(swatch, lv_color_hex(COLOR_ACCENT), LV_PART_MAIN | LV_STATE_FOCUS_KEY);
|
||||
lv_obj_set_style_outline_width(swatch, 2, LV_PART_MAIN | LV_STATE_FOCUS_KEY);
|
||||
lv_obj_set_style_outline_opa(swatch, LV_OPA_COVER, LV_PART_MAIN | LV_STATE_FOCUS_KEY);
|
||||
lv_obj_set_style_outline_pad(swatch, 2, LV_PART_MAIN | LV_STATE_FOCUS_KEY);
|
||||
const uintptr_t choice = (row ? 0x100u : 0u) | i;
|
||||
lv_obj_add_event_cb(swatch, attakyNotifyColorChosenCb, LV_EVENT_CLICKED, (void*)choice);
|
||||
}
|
||||
attakyNotifySwatchesRefresh(row);
|
||||
y += swatch_h + 10;
|
||||
};
|
||||
addNotifyColorRow("Room / channel color", 0);
|
||||
addNotifyColorRow("Direct message color", 1);
|
||||
#endif
|
||||
// Sound toggle — T-Deck I2S speaker, Heltec V4 expansion-kit piezo buzzer, or
|
||||
// the Tanmatsu's ES8156 codec / speaker amp.
|
||||
#if defined(HAS_UI_SOUND) || defined(HAS_TANMATSU)
|
||||
@@ -56123,6 +56260,15 @@ void UITask::newMsgImpl(uint8_t path_len, const char* from_name, const char* tex
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(ATTAKY_MESH_SERIES)
|
||||
if (g_last_event == UIEventType::contactMessage) {
|
||||
attakyNotificationStart(touchPrefsGetAttakyNotifyDmColor());
|
||||
} else if (g_last_event == UIEventType::channelMessage ||
|
||||
g_last_event == UIEventType::roomMessage) {
|
||||
attakyNotificationStart(touchPrefsGetAttakyNotifyRoomColor());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CAP_LUA_SDK_EXT
|
||||
// Hand a running Lua app the incoming message (app.on_message), AFTER the block
|
||||
// and spam filters so an app sees exactly the messages the user does. Permission
|
||||
@@ -57771,6 +57917,7 @@ void UITask::loop() {
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_ATTAKY_MESH_KEYBOARD)
|
||||
attakyNotificationTick(now);
|
||||
{
|
||||
lv_obj_t* akb_ta = g_lv.keyboard ? lv_keyboard_get_textarea(g_lv.keyboard) : nullptr;
|
||||
attakyKeyboardPoll(akb_ta != nullptr);
|
||||
@@ -57789,6 +57936,10 @@ void UITask::loop() {
|
||||
break; // keyboard rebound above; akb_ta is stale from here
|
||||
}
|
||||
else if (s_kb_panel) lv_textarea_add_char(akb_ta, '\n'); // enter-sends off: compose multi-line
|
||||
else if (s_term_input_ta && s_kb_bind_ta == s_term_input_ta) {
|
||||
terminalSubmit(); // terminal: submit once, clear, and keep the field ready
|
||||
break;
|
||||
}
|
||||
else lv_event_send(g_lv.keyboard, LV_EVENT_READY, nullptr); // settings field: confirm
|
||||
}
|
||||
else if (key == 0x08 || key == 0x7F) lv_textarea_del_char(akb_ta);
|
||||
|
||||
@@ -27,6 +27,9 @@ static Config safeDefaults() {
|
||||
c.boot_wifi_open = 0;
|
||||
c.theme_mode = 0;
|
||||
c.gps_fuzz_m = 0;
|
||||
c.attaky_notify_enabled = 0;
|
||||
c.attaky_notify_room_color = 0;
|
||||
c.attaky_notify_dm_color = 1;
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -85,6 +88,9 @@ int main() {
|
||||
assert(migrated.loud_alerts == 0);
|
||||
assert(migrated.theme_mode == 0);
|
||||
assert(migrated.gps_fuzz_m == 0);
|
||||
assert(migrated.attaky_notify_enabled == 0);
|
||||
assert(migrated.attaky_notify_room_color == 0);
|
||||
assert(migrated.attaky_notify_dm_color == 1);
|
||||
|
||||
// Reproduce beta 57 exactly: retry was inserted before the old suffix, then
|
||||
// the v43 bytes were copied and the full shifted v44 structure was written.
|
||||
@@ -116,7 +122,9 @@ int main() {
|
||||
constexpr size_t v53_size = offsetof(Config, boot_wifi_time);
|
||||
static_assert(v53_size + sizeof(Config::boot_wifi_time) + sizeof(Config::boot_wifi_open)
|
||||
+ sizeof(Config::loud_alerts) + sizeof(Config::theme_mode)
|
||||
+ sizeof(Config::gps_fuzz_m) == sizeof(Config),
|
||||
+ sizeof(Config::gps_fuzz_m) + sizeof(Config::attaky_notify_enabled)
|
||||
+ sizeof(Config::attaky_notify_room_color)
|
||||
+ sizeof(Config::attaky_notify_dm_color) == sizeof(Config),
|
||||
"v53 is the current layout minus every byte appended since");
|
||||
|
||||
Config v53 = safeDefaults();
|
||||
@@ -155,6 +163,9 @@ int main() {
|
||||
current.loud_alerts = 1;
|
||||
current.theme_mode = 1;
|
||||
current.gps_fuzz_m = 150;
|
||||
current.attaky_notify_enabled = 1;
|
||||
current.attaky_notify_room_color = 6;
|
||||
current.attaky_notify_dm_color = 4;
|
||||
migrated = safeDefaults();
|
||||
assert(TouchPrefsSchema::overlayStored(migrated, ¤t, sizeof(current), &stored_version));
|
||||
assert(stored_version == TouchPrefsSchema::CURRENT_VERSION);
|
||||
@@ -177,15 +188,16 @@ int main() {
|
||||
assert(migrated.app_hide == safeDefaults().app_hide);
|
||||
assert(migrated.theme_mode == 0);
|
||||
|
||||
// v56 appends only theme_mode. It was written as v54 on the contributing
|
||||
// v56 appended only theme_mode. It was written as v54 on the contributing
|
||||
// branch, but v54 and v55 were taken by boot_wifi_* and loud_alerts before it
|
||||
// merged, so the field sits behind those and the blob one version short of
|
||||
// current is the one that must leave it at the Night default.
|
||||
// merged, so the field sits behind those and a historical v55 blob must leave
|
||||
// it at the Night default.
|
||||
constexpr size_t v55_size = offsetof(Config, theme_mode);
|
||||
static_assert(offsetof(Config, gps_fuzz_m) + sizeof(Config::gps_fuzz_m) == sizeof(Config),
|
||||
"gps_fuzz_m must remain last");
|
||||
static_assert(v55_size + sizeof(Config::theme_mode) + sizeof(Config::gps_fuzz_m) == sizeof(Config),
|
||||
"v55 is the current layout minus theme_mode and gps_fuzz_m");
|
||||
static_assert(v55_size + sizeof(Config::theme_mode) + sizeof(Config::gps_fuzz_m)
|
||||
+ sizeof(Config::attaky_notify_enabled)
|
||||
+ sizeof(Config::attaky_notify_room_color)
|
||||
+ sizeof(Config::attaky_notify_dm_color) == sizeof(Config),
|
||||
"v55 is the current layout minus every byte appended since");
|
||||
Config v55 = safeDefaults();
|
||||
v55.ver = 55;
|
||||
v55.kb_force_legacy = 1;
|
||||
@@ -198,6 +210,28 @@ int main() {
|
||||
assert(migrated.loud_alerts == 1);
|
||||
assert(migrated.theme_mode == 0);
|
||||
assert(migrated.gps_fuzz_m == 0);
|
||||
assert(migrated.attaky_notify_enabled == 0);
|
||||
assert(migrated.attaky_notify_room_color == 0);
|
||||
assert(migrated.attaky_notify_dm_color == 1);
|
||||
|
||||
constexpr size_t v57_size = offsetof(Config, attaky_notify_enabled);
|
||||
static_assert(v57_size + sizeof(Config::attaky_notify_enabled)
|
||||
+ sizeof(Config::attaky_notify_room_color)
|
||||
+ sizeof(Config::attaky_notify_dm_color) == sizeof(Config),
|
||||
"v57 is the current layout minus the Attaky notification fields");
|
||||
Config v57 = safeDefaults();
|
||||
v57.ver = 57;
|
||||
v57.gps_fuzz_m = 250;
|
||||
v57.attaky_notify_enabled = 1; // outside the stored v57 extent
|
||||
v57.attaky_notify_room_color = 6;
|
||||
v57.attaky_notify_dm_color = 5;
|
||||
migrated = safeDefaults();
|
||||
assert(TouchPrefsSchema::overlayStored(migrated, &v57, v57_size, &stored_version));
|
||||
assert(stored_version == 57);
|
||||
assert(migrated.gps_fuzz_m == 250);
|
||||
assert(migrated.attaky_notify_enabled == 0);
|
||||
assert(migrated.attaky_notify_room_color == 0);
|
||||
assert(migrated.attaky_notify_dm_color == 1);
|
||||
|
||||
Config invalid = safeDefaults();
|
||||
uint8_t garbage[sizeof(Config)] = {};
|
||||
|
||||
@@ -46,6 +46,7 @@ static const uint8_t SHIFT_HALF = 0, SHIFT_ROW = 3, SHIFT_COL = 0;
|
||||
|
||||
static bool s_inited = false;
|
||||
static bool s_present[2] = { false, false };
|
||||
static uint8_t s_notify_color = ATTAKY_NOTIFY_OFF;
|
||||
static uint8_t s_prev[2][5] = { { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
|
||||
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } };
|
||||
|
||||
@@ -72,6 +73,11 @@ static uint8_t awRead(uint8_t addr, uint8_t reg) {
|
||||
return (uint8_t)Wire.read();
|
||||
}
|
||||
|
||||
static uint8_t p1Value(uint8_t row_bits) {
|
||||
const uint8_t led_bits = (uint8_t)((~s_notify_color & 0x07u) << 5);
|
||||
return (uint8_t)((row_bits & 0x1Fu) | led_bits);
|
||||
}
|
||||
|
||||
static bool initHalf(uint8_t addr) {
|
||||
bool ok = true;
|
||||
ok &= awWrite(addr, AW_REG_SWRST, 0x00);
|
||||
@@ -81,7 +87,7 @@ static bool initHalf(uint8_t addr) {
|
||||
ok &= awWrite(addr, AW_REG_CFG_P0, 0xFF);
|
||||
ok &= awWrite(addr, AW_REG_CFG_P1, 0x00);
|
||||
ok &= awWrite(addr, AW_REG_OUT_P0, 0x00);
|
||||
ok &= awWrite(addr, AW_REG_OUT_P1, P1_IDLE);
|
||||
ok &= awWrite(addr, AW_REG_OUT_P1, p1Value(P1_IDLE));
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -97,11 +103,11 @@ static void ensureInit() {
|
||||
|
||||
static void scanHalf(uint8_t addr, uint8_t cols[5]) {
|
||||
for (uint8_t row = 0; row < 5; row++) {
|
||||
awWrite(addr, AW_REG_OUT_P1, (uint8_t)(ROW_DRIVE[row] | 0xE0));
|
||||
awWrite(addr, AW_REG_OUT_P1, p1Value(ROW_DRIVE[row]));
|
||||
delayMicroseconds(50);
|
||||
cols[row] = (uint8_t)(awRead(addr, AW_REG_IN_P0) & 0x1F);
|
||||
}
|
||||
awWrite(addr, AW_REG_OUT_P1, P1_IDLE);
|
||||
awWrite(addr, AW_REG_OUT_P1, p1Value(P1_IDLE));
|
||||
}
|
||||
|
||||
void attakyKeyboardPoll(bool active) {
|
||||
@@ -155,4 +161,20 @@ int attakyKeyboardReadKey() {
|
||||
|
||||
bool attakyKeyboardPresent() { return s_present[0] || s_present[1]; }
|
||||
|
||||
bool attakyKeyboardSetNotificationColor(uint8_t color_mask) {
|
||||
s_notify_color = color_mask & 0x07u;
|
||||
ensureInit();
|
||||
if (!attakyI2cLock(20)) return false;
|
||||
|
||||
bool wrote = false;
|
||||
bool ok = true;
|
||||
for (int h = 0; h < 2; ++h) {
|
||||
if (!s_present[h]) continue;
|
||||
wrote = true;
|
||||
if (!awWrite(KB_ADDR[h], AW_REG_OUT_P1, p1Value(P1_IDLE))) ok = false;
|
||||
}
|
||||
attakyI2cUnlock();
|
||||
return wrote && ok;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,8 +5,16 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum : uint8_t {
|
||||
ATTAKY_NOTIFY_OFF = 0,
|
||||
ATTAKY_NOTIFY_BLUE = 1u << 0,
|
||||
ATTAKY_NOTIFY_GREEN = 1u << 1,
|
||||
ATTAKY_NOTIFY_RED = 1u << 2,
|
||||
};
|
||||
|
||||
void attakyKeyboardPoll(bool active);
|
||||
int attakyKeyboardReadKey();
|
||||
bool attakyKeyboardPresent();
|
||||
bool attakyKeyboardSetNotificationColor(uint8_t color_mask);
|
||||
|
||||
#endif
|
||||
Reference in new issue
Block a user