Fix Attaky usability issues (#423)

This commit is contained in:
Michael A. Cojocari committed 2026-09-06 16:55:41 -04:00
1 parent 8e94e25036
commit 28fb57ef4c
7 files changed
+292 -17

No files matched your search

+15 -3
View File
@@ -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
+40
View File
@@ -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
+8
View File
@@ -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
View File
@@ -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);
+42 -8
View File
@@ -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, &current, 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