diff --git a/App/app/app.c b/App/app/app.c index fd64c7fa..a6946c0e 100644 --- a/App/app/app.c +++ b/App/app/app.c @@ -234,7 +234,7 @@ static void ScreenSaverUpdateViewer(void) #endif } -static bool ScreenSaverCanDisplay(void) +static bool ScreenSaverCanDisplay(bool modal) { if (gSetting_set_sav == SET_SAV_OFF || gEeprom.BACKLIGHT_TIME == 0 || @@ -257,7 +257,7 @@ static bool ScreenSaverCanDisplay(void) return false; } - if (gScreenToDisplay == DISPLAY_MAIN) + if (modal || gScreenToDisplay == DISPLAY_MAIN) return true; #ifdef ENABLE_FMRADIO @@ -268,9 +268,9 @@ static bool ScreenSaverCanDisplay(void) return false; } -static void ScreenSaverTryDisplay(void) +static void ScreenSaverTryDisplay(bool modal) { - if (!ScreenSaverCanDisplay()) + if (!ScreenSaverCanDisplay(modal)) return; if (gSetting_set_sav == SET_SAV_LOGO) @@ -295,6 +295,28 @@ static void ScreenSaverExit(void) gUpdateStatus = true; } } + +static bool ScreenSaverAnimate(void) +{ + if (!gScreenSaverDisplayed) + return false; + + if (gSetting_set_sav == SET_SAV_MATRIX) { + if (++gScreenSaverTick >= 8u) { + gScreenSaverTick = 0; + ScreenSaverRenderMatrix(false); + return true; + } + } else if (gSetting_set_sav == SET_SAV_LOGO_PLUS) { + if (++gScreenSaverTick >= 16u) { + gScreenSaverTick = 0; + ScreenSaverRenderLogoPlus(false); + return true; + } + } + + return false; +} #endif bool APP_IsScreenSaverDisplayed(void) @@ -306,6 +328,47 @@ bool APP_IsScreenSaverDisplayed(void) #endif } +/* Modal foreground loops (resident tools and overlay apps) bypass APP_Update() + * and therefore also bypass the normal 10 ms fade and 500 ms BLTime service. + * Keep that service resident so every overlay app gets identical timing without + * extending the app ABI. Only selected modal screens opt into the saver; the + * others still fade from BLMax to BLMin when BLTime expires. */ +void APP_ModalBacklightTick(bool allowScreenSaver) +{ + if (gNextTimeslice) { + gNextTimeslice = false; + BACKLIGHT_Update(); + +#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV + if (ScreenSaverAnimate()) + ScreenSaverUpdateViewer(); +#endif + } + + if (!gNextTimeslice_500ms) + return; + gNextTimeslice_500ms = false; + + if (gBacklightCountdown_500ms > 0 && + gEeprom.BACKLIGHT_TIME < 61 && + --gBacklightCountdown_500ms == 0) + BACKLIGHT_TurnOff(); + +#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV + if (allowScreenSaver && gBacklightCountdown_500ms == 0) + ScreenSaverTryDisplay(true); +#else + (void)allowScreenSaver; +#endif +} + +void APP_ModalScreenSaverExit(void) +{ +#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV + ScreenSaverExit(); +#endif +} + static void CheckForIncoming(void) { if (!g_SquelchLost) @@ -1667,22 +1730,10 @@ void APP_TimeSlice10ms(void) gUpdateStatus = false; } - if (gSetting_set_sav == SET_SAV_MATRIX) { - if (++gScreenSaverTick >= 8u) { - gScreenSaverTick = 0; - ScreenSaverRenderMatrix(false); + if (ScreenSaverAnimate()) { #ifdef ENABLE_FEAT_F4HWN_K5VIEWER - screenSaverRendered = true; + screenSaverRendered = true; #endif - } - } else if (gSetting_set_sav == SET_SAV_LOGO_PLUS) { - if (++gScreenSaverTick >= 16u) { - gScreenSaverTick = 0; - ScreenSaverRenderLogoPlus(false); -#ifdef ENABLE_FEAT_F4HWN_K5VIEWER - screenSaverRendered = true; -#endif - } } } #endif @@ -1895,7 +1946,7 @@ void APP_TimeSlice500ms(void) ) { BACKLIGHT_TurnOff(); #ifdef ENABLE_FEAT_F4HWN_LOGO_SAV - ScreenSaverTryDisplay(); + ScreenSaverTryDisplay(false); #endif } @@ -2101,7 +2152,7 @@ void APP_TimeSlice500ms(void) !gAskToSave && !gCssBackgroundScan) { - ScreenSaverTryDisplay(); + ScreenSaverTryDisplay(false); } #endif diff --git a/App/app/app.h b/App/app/app.h index 602dcc1c..63b520aa 100644 --- a/App/app/app.h +++ b/App/app/app.h @@ -32,5 +32,7 @@ void APP_Update(void); void APP_TimeSlice10ms(void); void APP_TimeSlice500ms(void); bool APP_IsScreenSaverDisplayed(void); +void APP_ModalBacklightTick(bool allowScreenSaver); +void APP_ModalScreenSaverExit(void); #endif diff --git a/App/apps/app_api.h b/App/apps/app_api.h index 7ed8b148..e3a1899b 100644 --- a/App/apps/app_api.h +++ b/App/apps/app_api.h @@ -63,6 +63,8 @@ enum { APP_KEY_F = 15, APP_KEY_PTT = 16, APP_KEY_INVALID = 19, + APP_KEY_SAVER = 0xFD, /* virtual: saver active, skip this app frame */ + APP_KEY_WAKE = 0xFE, /* virtual: saver dismissed, redraw without action */ }; /* The framebuffer is the resident gFrameBuffer[FRAME_LINES][128]; the app draws @@ -175,7 +177,7 @@ typedef struct app_api { void (*draw_battery)(void); /* UI_DrawStatusBattery into the status line */ void (*battery_sample)(void); /* periodic ADC sample so the level stays live */ void (*backlight_on)(void); /* BACKLIGHT_TurnOn */ - void (*backlight_update)(void); /* BACKLIGHT_Update (fade step) */ + void (*backlight_update)(void); /* resident fade + BLTime service */ void (*audio_scope)(uint8_t line, bool active); /* shared MAIN microphone scope */ uint8_t *status_line; /* -> gStatusLine (for status-bar icons) */ diff --git a/App/apps/app_menu.c b/App/apps/app_menu.c index 8ea8f942..f1b14fa7 100644 --- a/App/apps/app_menu.c +++ b/App/apps/app_menu.c @@ -20,6 +20,8 @@ #include #include "apps/app_menu.h" +#include "app/app.h" +#include "driver/backlight.h" #include "driver/st7565.h" #include "driver/keyboard.h" #include "driver/system.h" @@ -62,6 +64,8 @@ static void app_key_hints(void) #define APP_NAME_BOX_END 102u #define APP_NAME_TEXT_X 14u +static void app_wait_release(void); + static void app_invert_name(uint8_t line) { gFrameBuffer[line][APP_NAME_BOX_START] ^= 0x7Fu; @@ -82,6 +86,24 @@ static KEY_Code_t app_get_key(void) /* APP_MenuOpen() is modal and does not return to APP_Update(). Keep * serial key injection and the viewer connection alive while waiting. */ K5VIEWER_ParseInput(); +#endif + APP_ModalBacklightTick(true); + + if (APP_IsScreenSaverDisplayed()) + { + if (KEYBOARD_GetKey() != KEY_INVALID) + { + APP_ModalScreenSaverExit(); + BACKLIGHT_TurnOn(); + app_wait_release(); + return KEY_INVALID; + } + + SYSTEM_DelayMs(10); + continue; + } + +#ifdef ENABLE_FEAT_F4HWN_K5VIEWER K5VIEWER_Update(false); #endif KEY_Code_t key = KEYBOARD_Poll(); @@ -90,8 +112,12 @@ static KEY_Code_t app_get_key(void) SYSTEM_DelayMs(30); if (KEYBOARD_Poll() == key) { + BACKLIGHT_TurnOn(); while (KEYBOARD_Poll() != KEY_INVALID) + { SYSTEM_DelayMs(10); + APP_ModalBacklightTick(false); + } return key; } } @@ -109,6 +135,7 @@ static void app_wait_release(void) else stable = 0; SYSTEM_DelayMs(10); + APP_ModalBacklightTick(true); } } @@ -188,6 +215,9 @@ _Static_assert(APP_MENU_SLOT_COUNT <= APP_SLOT_COUNT, void APP_MenuOpen(void) { + APP_ModalScreenSaverExit(); + BACKLIGHT_TurnOn(); + #ifdef ENABLE_FEAT_F4HWN_K5VIEWER /* Detach the modal selector from the key state that triggered F+7. The * normal K5Viewer updater suppresses frames while a key is held; without diff --git a/App/apps/app_overlay.c b/App/apps/app_overlay.c index 6b90d454..787214f9 100644 --- a/App/apps/app_overlay.c +++ b/App/apps/app_overlay.c @@ -53,6 +53,20 @@ _Static_assert(sizeof(app_header_t) == 64, "app_header_t must be 64 bytes"); /* ---- ABI wrappers: the few resident calls that are not a direct signature match ---- */ +static bool app_allow_screen_saver; +static bool app_screen_saver_wake; + +static void app_backlight_on(void) +{ + APP_ModalScreenSaverExit(); + BACKLIGHT_TurnOn(); +} + +static void app_backlight_update(void) +{ + APP_ModalBacklightTick(app_allow_screen_saver); +} + static uint8_t app_get_key(void) { #ifdef ENABLE_FEAT_F4HWN_K5VIEWER @@ -60,7 +74,26 @@ static uint8_t app_get_key(void) * injection alive while an app owns the foreground loop. */ K5VIEWER_ParseInput(); #endif - return (uint8_t)KEYBOARD_GetKey(); + const KEY_Code_t key = KEYBOARD_GetKey(); + + if (app_screen_saver_wake) { + if (key == KEY_INVALID) + app_screen_saver_wake = false; + return APP_KEY_INVALID; + } + + if (!APP_IsScreenSaverDisplayed()) + return (uint8_t)key; + + if (key == KEY_INVALID) + return APP_KEY_SAVER; + + app_backlight_on(); + if (key == KEY_PTT) + return APP_KEY_PTT; + + app_screen_saver_wake = true; + return APP_KEY_WAKE; } #ifdef ENABLE_FEAT_F4HWN_K5VIEWER @@ -72,6 +105,7 @@ static void app_blit_full(void) K5VIEWER_Update(false); } #endif + static int8_t app_nav_dir(uint8_t key) { int8_t direction; @@ -749,8 +783,8 @@ static const app_api_t app_api = { .cfg_save = app_cfg_save, .draw_battery = app_draw_battery, .battery_sample = app_battery_sample, - .backlight_on = BACKLIGHT_TurnOn, - .backlight_update = BACKLIGHT_Update, + .backlight_on = app_backlight_on, + .backlight_update = app_backlight_update, .audio_scope = UI_DisplayAudioScopeOverlay, .status_line = gStatusLine, .tx_state = app_tx_state, @@ -820,6 +854,11 @@ uint8_t APP_LaunchOverlay(uint8_t slot) app_fm_dirty = false; #endif + app_allow_screen_saver = (h.flags & APP_FLAG_SCREEN_SAVER) != 0; + app_screen_saver_wake = false; + APP_ModalScreenSaverExit(); + BACKLIGHT_TurnOn(); + #ifdef ENABLE_FEAT_F4HWN_K5VIEWER /* The caller enters from a debounced key event, so the resident key state * still contains that trigger while the modal app is running. Clear it so @@ -845,6 +884,10 @@ uint8_t APP_LaunchOverlay(uint8_t slot) app_entry_t entry = (app_entry_t)(((uint32_t)ws + h.entry_off) | 1u); entry(&app_api); + APP_ModalScreenSaverExit(); + app_allow_screen_saver = false; + app_screen_saver_wake = false; + /* A defensive leave also covers an app returning through an error path. */ app_trivfo_leave(); diff --git a/App/apps/app_overlay.h b/App/apps/app_overlay.h index e2c2e778..3b0881df 100644 --- a/App/apps/app_overlay.h +++ b/App/apps/app_overlay.h @@ -66,7 +66,8 @@ /* ---- blob header (64 bytes, little-endian; see App/apps/pack_app.py) ---- */ #define APP_MAGIC 0x31504146u /* "FAP1" */ #define APP_HDR_VERSION 1u -#define APP_FLAG_COMMITTED (1u << 0) +#define APP_FLAG_COMMITTED 0x0001u +#define APP_FLAG_SCREEN_SAVER 0x0002u #define APP_NAME_LEN 16 #define APP_VERSION_LEN 16 diff --git a/App/apps/cube3d/build.sh b/App/apps/cube3d/build.sh index 8175ba03..3e17025a 100755 --- a/App/apps/cube3d/build.sh +++ b/App/apps/cube3d/build.sh @@ -29,7 +29,7 @@ trap 'printf "\r ❌ %-13s build failed \n" "$APP_NAME"' ERR step 1 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf" step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" step 3 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \ - --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" >/dev/null + --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" --screensaver >/dev/null trap - ERR BYTES=$(wc -c < "${APP}.bin") diff --git a/App/apps/cube3d/cube3d_app.c b/App/apps/cube3d/cube3d_app.c index 2b5a799f..b588d102 100644 --- a/App/apps/cube3d/cube3d_app.c +++ b/App/apps/cube3d/cube3d_app.c @@ -196,6 +196,50 @@ void app_main(const app_api_t *api) uint8_t prevKey = APP_KEY_INVALID; while (running) { + uint8_t key = A->get_key(); + if (key == APP_KEY_SAVER) { + prevKey = APP_KEY_INVALID; + A->delay_ms(10); + A->backlight_update(); + continue; + } + if (key == APP_KEY_WAKE) + key = APP_KEY_INVALID; + if (key != prevKey && key != APP_KEY_INVALID) { + A->backlight_on(); + switch (key) { + case APP_KEY_EXIT: + running = false; + break; + case APP_KEY_UP: + case APP_KEY_DOWN: { + const int8_t dir = A->nav_dir(key); + if (dir > 0 && speed < 16u) speed++; + if (dir < 0 && speed > 1u) speed--; + break; + } + case APP_KEY_MENU: + paused = !paused; + break; + case APP_KEY_STAR: + shape = (uint8_t)((shape + 1u) % NSHAPE); + break; + case APP_KEY_F: + wire = !wire; + break; + case APP_KEY_1: case APP_KEY_2: case APP_KEY_3: case APP_KEY_4: + case APP_KEY_5: case APP_KEY_6: case APP_KEY_7: case APP_KEY_8: + if ((uint8_t)(key - APP_KEY_1) < NSHAPE) + shape = (uint8_t)(key - APP_KEY_1); + break; + default: + break; + } + } + prevKey = key; + if (!running) + break; + const shape_t *s = &SHAPES[shape]; const uint8_t ia = (uint8_t)(ax >> 3), ib = (uint8_t)(ay >> 3), ic = (uint8_t)(az >> 3); const int cx = sin8((uint8_t)(ia + 64u)), sxr = sin8(ia); @@ -249,39 +293,6 @@ void app_main(const app_api_t *api) az += (uint16_t)((rate + 1u) / 2u); } - const uint8_t key = A->get_key(); - if (key != prevKey && key != APP_KEY_INVALID) { - A->backlight_on(); - switch (key) { - case APP_KEY_EXIT: - running = false; - break; - case APP_KEY_UP: - case APP_KEY_DOWN: { - const int8_t dir = A->nav_dir(key); - if (dir > 0 && speed < 16u) speed++; - if (dir < 0 && speed > 1u) speed--; - break; - } - case APP_KEY_MENU: - paused = !paused; - break; - case APP_KEY_STAR: - shape = (uint8_t)((shape + 1u) % NSHAPE); - break; - case APP_KEY_F: - wire = !wire; - break; - case APP_KEY_1: case APP_KEY_2: case APP_KEY_3: case APP_KEY_4: - case APP_KEY_5: case APP_KEY_6: case APP_KEY_7: case APP_KEY_8: - if ((uint8_t)(key - APP_KEY_1) < NSHAPE) - shape = (uint8_t)(key - APP_KEY_1); - break; - default: - break; - } - } - prevKey = key; A->backlight_update(); A->delay_ms((uint32_t)(32u - speed * 2u)); /* slow low end, no added delay at level 16 */ } diff --git a/App/apps/fm/build.sh b/App/apps/fm/build.sh index 6fde97c0..a35b18da 100755 --- a/App/apps/fm/build.sh +++ b/App/apps/fm/build.sh @@ -29,7 +29,7 @@ trap 'printf "\r ❌ %-13s build failed \n" "$APP_NAME"' ERR step 1 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf" step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" step 3 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \ - --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" >/dev/null + --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" --screensaver >/dev/null trap - ERR BYTES=$(wc -c < "${APP}.bin") diff --git a/App/apps/fm/fm_app.c b/App/apps/fm/fm_app.c index dac7aa08..9212af0c 100644 --- a/App/apps/fm/fm_app.c +++ b/App/apps/fm/fm_app.c @@ -303,6 +303,7 @@ __attribute__((section(".text.entry"),used)) void app_main(const app_api_t *api) { A=api; + A->backlight_on(); ch=A->fm_channels; A->fm_state(&st,false); /* inherit the resident FM state */ if(st.band>3) st.band=0; @@ -326,13 +327,20 @@ void app_main(const app_api_t *api) uint8_t key=A->get_key(); bool rep=(key==APP_KEY_UP||key==APP_KEY_DOWN); - if(key==APP_KEY_INVALID){ + if(key==APP_KEY_SAVER){ + held=APP_KEY_INVALID; heldMs=0; firedLong=false; + A->delay_ms(10); A->backlight_update(); + continue; + } else if(key==APP_KEY_WAKE||key==APP_KEY_PTT){ + held=APP_KEY_INVALID; heldMs=0; firedLong=false; dirty=true; + } else if(key==APP_KEY_INVALID){ if(held!=APP_KEY_INVALID && !firedLong && held!=APP_KEY_UP && held!=APP_KEY_DOWN){ onShort(held); dirty=true; } held=APP_KEY_INVALID; heldMs=0; firedLong=false; } else if(key!=held){ + A->backlight_on(); held=key; heldMs=0; firedLong=false; if(rep){ onShort(key); dirty=true; } /* immediate first step */ } else { @@ -346,13 +354,20 @@ void app_main(const app_api_t *api) /* non-blocking scan: one step each time the settle timer expires */ if(scanState){ + /* Match resident FM: an active scan keeps the display awake. */ + A->backlight_on(); if(scanTimer<=50) scanStep(); else scanTimer=(uint16_t)(scanTimer-50); dirty=true; } if(dirty){ show(); dirty=false; } - A->delay_ms(50); + /* Keep the resident 10 ms fade cadence while retaining this app's + * existing 50 ms key/scan state-machine tick. */ + for(uint8_t i=0;i<5u;i++){ + A->delay_ms(10); + A->backlight_update(); + } } /* push our state back to the resident FM and persist (config + 48 channels) */ diff --git a/App/apps/pack_app.py b/App/apps/pack_app.py index cd3b0781..4ae9575f 100644 --- a/App/apps/pack_app.py +++ b/App/apps/pack_app.py @@ -30,7 +30,8 @@ MAGIC = cdefine("app_overlay.h", "APP_MAGIC").to_bytes(4, "little") # HDR_VERSION = cdefine("app_overlay.h", "APP_HDR_VERSION") ABI_VERSION = cdefine("app_api.h", "APP_ABI_VERSION") OVERLAY_MAX = cdefine("app_overlay.h", "APP_OVERLAY_MAX") # 4 KiB overlay budget -FLAG_COMMITTED = 1 # APP_FLAG_COMMITTED, bit 0 +FLAG_COMMITTED = cdefine("app_overlay.h", "APP_FLAG_COMMITTED") +FLAG_SCREEN_SAVER = cdefine("app_overlay.h", "APP_FLAG_SCREEN_SAVER") def field(s: str, n: int) -> bytes: b = s.encode("ascii", "strict")[: n - 1] @@ -45,6 +46,8 @@ def main(): ap.add_argument("--entry", type=lambda x: int(x, 0), default=0) ap.add_argument("--vma", type=lambda x: int(x, 0), required=True, help="RAM VMA the app was linked at (must match the firmware overlay)") + ap.add_argument("--screensaver", action="store_true", + help="allow the resident BLTime screen saver while this app is idle") a = ap.parse_args() code = open(a.infile, "rb").read() @@ -54,10 +57,11 @@ def main(): sys.exit(f"code {len(code)} B exceeds overlay budget {OVERLAY_MAX} B") crc = zlib.crc32(code) & 0xFFFFFFFF + flags = FLAG_COMMITTED | (FLAG_SCREEN_SAVER if a.screensaver else 0) header = struct.pack( "<4sHHIIHH16s16sI8s", MAGIC, HDR_VERSION, ABI_VERSION, - len(code), crc, a.entry, FLAG_COMMITTED, + len(code), crc, a.entry, flags, field(a.name, 16), field(a.ver, 16), a.vma, b"\x00" * 8, ) assert len(header) == 64, len(header) diff --git a/App/apps/plasma/build.sh b/App/apps/plasma/build.sh index 2bf77d50..7066c42e 100755 --- a/App/apps/plasma/build.sh +++ b/App/apps/plasma/build.sh @@ -29,7 +29,7 @@ trap 'printf "\r ❌ %-13s build failed \n" "$APP_NAME"' ERR step 1 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf" step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" step 3 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \ - --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" >/dev/null + --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" --screensaver >/dev/null trap - ERR BYTES=$(wc -c < "${APP}.bin") diff --git a/App/apps/plasma/plasma_app.c b/App/apps/plasma/plasma_app.c index ccc9e826..32c036c2 100644 --- a/App/apps/plasma/plasma_app.c +++ b/App/apps/plasma/plasma_app.c @@ -92,6 +92,39 @@ void app_main(const app_api_t *api) prevKey = APP_KEY_INVALID; while (running) { + uint8_t key = A->get_key(); + if (key == APP_KEY_SAVER) { + prevKey = APP_KEY_INVALID; + A->delay_ms(10); + A->backlight_update(); + continue; + } + if (key == APP_KEY_WAKE) + key = APP_KEY_INVALID; + if (key != prevKey && key != APP_KEY_INVALID) { + A->backlight_on(); + switch (key) { + case APP_KEY_EXIT: running = false; break; + case APP_KEY_UP: + case APP_KEY_DOWN: { + const int8_t direction = A->nav_dir(key); + if (direction > 0 && speed < 8u) speed++; + if (direction < 0 && speed > 1u) speed--; + break; + } + case APP_KEY_MENU: paused = !paused; break; + case APP_KEY_STAR: bands = !bands; break; + case APP_KEY_F: autoc = !autoc; break; + case APP_KEY_1: case APP_KEY_2: case APP_KEY_3: + case APP_KEY_4: case APP_KEY_5: + var = (uint8_t)(key - APP_KEY_1); autoc = false; break; + default: break; + } + } + prevKey = key; + if (!running) + break; + const uint8_t sx = VAR[var][0], sy = VAR[var][1]; const uint8_t sd = VAR[var][2], rsh = VAR[var][3]; @@ -142,28 +175,6 @@ void app_main(const app_api_t *api) if (autoc && ++autoCtr >= 400u) { autoCtr = 0; var = (uint8_t)((var + 1u) % NVAR); } } - const uint8_t key = A->get_key(); - if (key != prevKey && key != APP_KEY_INVALID) { - A->backlight_on(); - switch (key) { - case APP_KEY_EXIT: running = false; break; - case APP_KEY_UP: - case APP_KEY_DOWN: { - const int8_t direction = A->nav_dir(key); - if (direction > 0 && speed < 8u) speed++; - if (direction < 0 && speed > 1u) speed--; - break; - } - case APP_KEY_MENU: paused = !paused; break; - case APP_KEY_STAR: bands = !bands; break; - case APP_KEY_F: autoc = !autoc; break; - case APP_KEY_1: case APP_KEY_2: case APP_KEY_3: - case APP_KEY_4: case APP_KEY_5: - var = (uint8_t)(key - APP_KEY_1); autoc = false; break; - default: break; - } - } - prevKey = key; A->backlight_update(); } } diff --git a/App/apps/trivfo/build.sh b/App/apps/trivfo/build.sh index dc6cc7a7..5e78a6e5 100755 --- a/App/apps/trivfo/build.sh +++ b/App/apps/trivfo/build.sh @@ -13,7 +13,7 @@ LDFLAGS="-nostdlib -nostartfiles -T app.ld -Wl,--defsym,APP_VMA=${APP_VMA} -Wl,- rm -f ./*.app ./*.elf ./*.bin "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf" "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" -python3 ../pack_app.py "${APP}.bin" "${OUT}.app" --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" >/dev/null +python3 ../pack_app.py "${APP}.bin" "${OUT}.app" --name "$APP_NAME" --ver "$APP_VER" --vma "${APP_VMA}" --screensaver >/dev/null BYTES=$(wc -c < "${APP}.bin") test "$BYTES" -le 4096 printf ' ✅ %-13s %4d B (%d%% of 4 KiB) -> %s.app\n' \ diff --git a/App/apps/trivfo/trivfo_app.c b/App/apps/trivfo/trivfo_app.c index 668ea3a3..9bca5e30 100644 --- a/App/apps/trivfo/trivfo_app.c +++ b/App/apps/trivfo/trivfo_app.c @@ -205,6 +205,19 @@ void app_main(const app_api_t *api){ uint8_t held=APP_KEY_INVALID, blinkTicks=0; uint16_t heldMs=0; bool longDone=false, ptt=false; while(running){ uint8_t key=A->get_key(); + if(key==APP_KEY_SAVER){ + state=A->trivfo_tick(); + if(state==APP_TRIVFO_RX){ + A->backlight_on(); + draw(); + } + for(uint8_t i=0;idelay_ms(10); A->backlight_update(); + } + continue; + } + if(key!=APP_KEY_INVALID&&key!=APP_KEY_WAKE) A->backlight_on(); + if(key==APP_KEY_WAKE) key=APP_KEY_INVALID; if(key==APP_KEY_PTT){ if(!ptt){ ptt=true; txDenied=A->trivfo_ptt(true)!=0; } } else if(ptt){ ptt=false; A->trivfo_ptt(false); txDenied=false; } @@ -233,6 +246,8 @@ void app_main(const app_api_t *api){ } state=A->trivfo_tick(); + if(state==APP_TRIVFO_RX||state==APP_TRIVFO_TX_STATE) + A->backlight_on(); if(state==APP_TRIVFO_RX){ if(++blinkTicks>=25u){ blinkTicks=0; channelLabelOn=!channelLabelOn; } } else { blinkTicks=0; channelLabelOn=true; } diff --git a/CMakePresets.json b/CMakePresets.json index 5c422fd3..9ce2fd38 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -186,6 +186,21 @@ "EDITION_STRING": "Labs", "TARGET": "f4hwn.labs" } + }, + { + "name": "Max", + "inherits": "Fusion", + "cacheVariables": { + "ENABLE_FEAT_F4HWN_RESCUE_OPS": true, + "ENABLE_FEAT_F4HWN_FOXHUNT": true, + "ENABLE_FEAT_F4HWN_BEACON": true, + "ENABLE_AIRCOPY": true, + "ENABLE_FEAT_F4HWN_BEAM": true, + "ENABLE_FEAT_F4HWN_GAME": true, + "ENABLE_FEAT_F4HWN_OVERLAY_APPS": false, + "EDITION_STRING": "Max", + "TARGET": "f4hwn.max" + } } ], "buildPresets": [ @@ -208,6 +223,10 @@ { "name": "Labs", "configurePreset": "Labs" + }, + { + "name": "Max", + "configurePreset": "Max" } ] } diff --git a/compile-firmware.sh b/compile-firmware.sh index 61c45e09..a2653570 100755 --- a/compile-firmware.sh +++ b/compile-firmware.sh @@ -16,7 +16,7 @@ set -euo pipefail # --------------------------------------------- IMAGE=uvk1-uvk5v3 -RELEASE_PRESETS=(Fusion Transfer FieldOps Labs) +RELEASE_PRESETS=(Fusion Transfer FieldOps Labs Max) FLASH_LIMIT=$((118 * 1024)) RAM_LIMIT=$((16 * 1024)) @@ -29,9 +29,9 @@ EXTRA_ARGS=("$@") # --------------------------------------------- # Validate preset name # --------------------------------------------- -if [[ ! "$PRESET" =~ ^(Custom|Fusion|Transfer|FieldOps|Labs|All)$ ]]; then +if [[ ! "$PRESET" =~ ^(Custom|Fusion|Transfer|FieldOps|Labs|Max|All)$ ]]; then echo "❌ Unknown preset: '$PRESET'" - echo "Valid presets are: Custom, Fusion, Transfer, FieldOps, Labs, All" + echo "Valid presets are: Custom, Fusion, Transfer, FieldOps, Labs, Max, All" exit 1 fi