Add BLTime support to overlay apps and Max preset

This commit is contained in:
Armel FAUVEAU committed 2026-08-31 01:33:23 +02:00
1 parent 0850420fdf
commit 99057f9dd2
17 files changed
+294 -90

No files matched your search

+70 -19
View File
@@ -234,7 +234,7 @@ static void ScreenSaverUpdateViewer(void)
#endif #endif
} }
static bool ScreenSaverCanDisplay(void) static bool ScreenSaverCanDisplay(bool modal)
{ {
if (gSetting_set_sav == SET_SAV_OFF || if (gSetting_set_sav == SET_SAV_OFF ||
gEeprom.BACKLIGHT_TIME == 0 || gEeprom.BACKLIGHT_TIME == 0 ||
@@ -257,7 +257,7 @@ static bool ScreenSaverCanDisplay(void)
return false; return false;
} }
if (gScreenToDisplay == DISPLAY_MAIN) if (modal || gScreenToDisplay == DISPLAY_MAIN)
return true; return true;
#ifdef ENABLE_FMRADIO #ifdef ENABLE_FMRADIO
@@ -268,9 +268,9 @@ static bool ScreenSaverCanDisplay(void)
return false; return false;
} }
static void ScreenSaverTryDisplay(void) static void ScreenSaverTryDisplay(bool modal)
{ {
if (!ScreenSaverCanDisplay()) if (!ScreenSaverCanDisplay(modal))
return; return;
if (gSetting_set_sav == SET_SAV_LOGO) if (gSetting_set_sav == SET_SAV_LOGO)
@@ -295,6 +295,28 @@ static void ScreenSaverExit(void)
gUpdateStatus = true; 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 #endif
bool APP_IsScreenSaverDisplayed(void) bool APP_IsScreenSaverDisplayed(void)
@@ -306,6 +328,47 @@ bool APP_IsScreenSaverDisplayed(void)
#endif #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) static void CheckForIncoming(void)
{ {
if (!g_SquelchLost) if (!g_SquelchLost)
@@ -1667,23 +1730,11 @@ void APP_TimeSlice10ms(void)
gUpdateStatus = false; gUpdateStatus = false;
} }
if (gSetting_set_sav == SET_SAV_MATRIX) { if (ScreenSaverAnimate()) {
if (++gScreenSaverTick >= 8u) {
gScreenSaverTick = 0;
ScreenSaverRenderMatrix(false);
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER #ifdef ENABLE_FEAT_F4HWN_K5VIEWER
screenSaverRendered = true; screenSaverRendered = true;
#endif #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 #endif
@@ -1895,7 +1946,7 @@ void APP_TimeSlice500ms(void)
) { ) {
BACKLIGHT_TurnOff(); BACKLIGHT_TurnOff();
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV #ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
ScreenSaverTryDisplay(); ScreenSaverTryDisplay(false);
#endif #endif
} }
@@ -2101,7 +2152,7 @@ void APP_TimeSlice500ms(void)
!gAskToSave && !gAskToSave &&
!gCssBackgroundScan) !gCssBackgroundScan)
{ {
ScreenSaverTryDisplay(); ScreenSaverTryDisplay(false);
} }
#endif #endif
+2
View File
@@ -32,5 +32,7 @@ void APP_Update(void);
void APP_TimeSlice10ms(void); void APP_TimeSlice10ms(void);
void APP_TimeSlice500ms(void); void APP_TimeSlice500ms(void);
bool APP_IsScreenSaverDisplayed(void); bool APP_IsScreenSaverDisplayed(void);
void APP_ModalBacklightTick(bool allowScreenSaver);
void APP_ModalScreenSaverExit(void);
#endif #endif
+3 -1
View File
@@ -63,6 +63,8 @@ enum {
APP_KEY_F = 15, APP_KEY_F = 15,
APP_KEY_PTT = 16, APP_KEY_PTT = 16,
APP_KEY_INVALID = 19, 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 /* 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 (*draw_battery)(void); /* UI_DrawStatusBattery into the status line */
void (*battery_sample)(void); /* periodic ADC sample so the level stays live */ void (*battery_sample)(void); /* periodic ADC sample so the level stays live */
void (*backlight_on)(void); /* BACKLIGHT_TurnOn */ 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 */ void (*audio_scope)(uint8_t line, bool active); /* shared MAIN microphone scope */
uint8_t *status_line; /* -> gStatusLine (for status-bar icons) */ uint8_t *status_line; /* -> gStatusLine (for status-bar icons) */
+30
View File
@@ -20,6 +20,8 @@
#include <string.h> #include <string.h>
#include "apps/app_menu.h" #include "apps/app_menu.h"
#include "app/app.h"
#include "driver/backlight.h"
#include "driver/st7565.h" #include "driver/st7565.h"
#include "driver/keyboard.h" #include "driver/keyboard.h"
#include "driver/system.h" #include "driver/system.h"
@@ -62,6 +64,8 @@ static void app_key_hints(void)
#define APP_NAME_BOX_END 102u #define APP_NAME_BOX_END 102u
#define APP_NAME_TEXT_X 14u #define APP_NAME_TEXT_X 14u
static void app_wait_release(void);
static void app_invert_name(uint8_t line) static void app_invert_name(uint8_t line)
{ {
gFrameBuffer[line][APP_NAME_BOX_START] ^= 0x7Fu; 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 /* APP_MenuOpen() is modal and does not return to APP_Update(). Keep
* serial key injection and the viewer connection alive while waiting. */ * serial key injection and the viewer connection alive while waiting. */
K5VIEWER_ParseInput(); 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); K5VIEWER_Update(false);
#endif #endif
KEY_Code_t key = KEYBOARD_Poll(); KEY_Code_t key = KEYBOARD_Poll();
@@ -90,8 +112,12 @@ static KEY_Code_t app_get_key(void)
SYSTEM_DelayMs(30); SYSTEM_DelayMs(30);
if (KEYBOARD_Poll() == key) if (KEYBOARD_Poll() == key)
{ {
BACKLIGHT_TurnOn();
while (KEYBOARD_Poll() != KEY_INVALID) while (KEYBOARD_Poll() != KEY_INVALID)
{
SYSTEM_DelayMs(10); SYSTEM_DelayMs(10);
APP_ModalBacklightTick(false);
}
return key; return key;
} }
} }
@@ -109,6 +135,7 @@ static void app_wait_release(void)
else else
stable = 0; stable = 0;
SYSTEM_DelayMs(10); SYSTEM_DelayMs(10);
APP_ModalBacklightTick(true);
} }
} }
@@ -188,6 +215,9 @@ _Static_assert(APP_MENU_SLOT_COUNT <= APP_SLOT_COUNT,
void APP_MenuOpen(void) void APP_MenuOpen(void)
{ {
APP_ModalScreenSaverExit();
BACKLIGHT_TurnOn();
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER #ifdef ENABLE_FEAT_F4HWN_K5VIEWER
/* Detach the modal selector from the key state that triggered F+7. The /* Detach the modal selector from the key state that triggered F+7. The
* normal K5Viewer updater suppresses frames while a key is held; without * normal K5Viewer updater suppresses frames while a key is held; without
+46 -3
View File
@@ -53,6 +53,20 @@
_Static_assert(sizeof(app_header_t) == 64, "app_header_t must be 64 bytes"); _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 ---- */ /* ---- 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) static uint8_t app_get_key(void)
{ {
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER #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. */ * injection alive while an app owns the foreground loop. */
K5VIEWER_ParseInput(); K5VIEWER_ParseInput();
#endif #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 #ifdef ENABLE_FEAT_F4HWN_K5VIEWER
@@ -72,6 +105,7 @@ static void app_blit_full(void)
K5VIEWER_Update(false); K5VIEWER_Update(false);
} }
#endif #endif
static int8_t app_nav_dir(uint8_t key) static int8_t app_nav_dir(uint8_t key)
{ {
int8_t direction; int8_t direction;
@@ -749,8 +783,8 @@ static const app_api_t app_api = {
.cfg_save = app_cfg_save, .cfg_save = app_cfg_save,
.draw_battery = app_draw_battery, .draw_battery = app_draw_battery,
.battery_sample = app_battery_sample, .battery_sample = app_battery_sample,
.backlight_on = BACKLIGHT_TurnOn, .backlight_on = app_backlight_on,
.backlight_update = BACKLIGHT_Update, .backlight_update = app_backlight_update,
.audio_scope = UI_DisplayAudioScopeOverlay, .audio_scope = UI_DisplayAudioScopeOverlay,
.status_line = gStatusLine, .status_line = gStatusLine,
.tx_state = app_tx_state, .tx_state = app_tx_state,
@@ -820,6 +854,11 @@ uint8_t APP_LaunchOverlay(uint8_t slot)
app_fm_dirty = false; app_fm_dirty = false;
#endif #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 #ifdef ENABLE_FEAT_F4HWN_K5VIEWER
/* The caller enters from a debounced key event, so the resident key state /* 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 * 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); app_entry_t entry = (app_entry_t)(((uint32_t)ws + h.entry_off) | 1u);
entry(&app_api); 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. */ /* A defensive leave also covers an app returning through an error path. */
app_trivfo_leave(); app_trivfo_leave();
+2 -1
View File
@@ -66,7 +66,8 @@
/* ---- blob header (64 bytes, little-endian; see App/apps/pack_app.py) ---- */ /* ---- blob header (64 bytes, little-endian; see App/apps/pack_app.py) ---- */
#define APP_MAGIC 0x31504146u /* "FAP1" */ #define APP_MAGIC 0x31504146u /* "FAP1" */
#define APP_HDR_VERSION 1u #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_NAME_LEN 16
#define APP_VERSION_LEN 16 #define APP_VERSION_LEN 16
+1 -1
View File
@@ -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 1 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf"
step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin"
step 3 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \ 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 trap - ERR
BYTES=$(wc -c < "${APP}.bin") BYTES=$(wc -c < "${APP}.bin")
+44 -33
View File
@@ -196,6 +196,50 @@ void app_main(const app_api_t *api)
uint8_t prevKey = APP_KEY_INVALID; uint8_t prevKey = APP_KEY_INVALID;
while (running) { 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 shape_t *s = &SHAPES[shape];
const uint8_t ia = (uint8_t)(ax >> 3), ib = (uint8_t)(ay >> 3), ic = (uint8_t)(az >> 3); 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); 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); 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->backlight_update();
A->delay_ms((uint32_t)(32u - speed * 2u)); /* slow low end, no added delay at level 16 */ A->delay_ms((uint32_t)(32u - speed * 2u)); /* slow low end, no added delay at level 16 */
} }
+1 -1
View File
@@ -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 1 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf"
step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin"
step 3 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \ 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 trap - ERR
BYTES=$(wc -c < "${APP}.bin") BYTES=$(wc -c < "${APP}.bin")
+17 -2
View File
@@ -303,6 +303,7 @@ __attribute__((section(".text.entry"),used))
void app_main(const app_api_t *api) void app_main(const app_api_t *api)
{ {
A=api; A=api;
A->backlight_on();
ch=A->fm_channels; ch=A->fm_channels;
A->fm_state(&st,false); /* inherit the resident FM state */ A->fm_state(&st,false); /* inherit the resident FM state */
if(st.band>3) st.band=0; 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(); uint8_t key=A->get_key();
bool rep=(key==APP_KEY_UP||key==APP_KEY_DOWN); 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 && if(held!=APP_KEY_INVALID && !firedLong &&
held!=APP_KEY_UP && held!=APP_KEY_DOWN){ held!=APP_KEY_UP && held!=APP_KEY_DOWN){
onShort(held); dirty=true; onShort(held); dirty=true;
} }
held=APP_KEY_INVALID; heldMs=0; firedLong=false; held=APP_KEY_INVALID; heldMs=0; firedLong=false;
} else if(key!=held){ } else if(key!=held){
A->backlight_on();
held=key; heldMs=0; firedLong=false; held=key; heldMs=0; firedLong=false;
if(rep){ onShort(key); dirty=true; } /* immediate first step */ if(rep){ onShort(key); dirty=true; } /* immediate first step */
} else { } else {
@@ -346,13 +354,20 @@ void app_main(const app_api_t *api)
/* non-blocking scan: one step each time the settle timer expires */ /* non-blocking scan: one step each time the settle timer expires */
if(scanState){ if(scanState){
/* Match resident FM: an active scan keeps the display awake. */
A->backlight_on();
if(scanTimer<=50) scanStep(); if(scanTimer<=50) scanStep();
else scanTimer=(uint16_t)(scanTimer-50); else scanTimer=(uint16_t)(scanTimer-50);
dirty=true; dirty=true;
} }
if(dirty){ show(); dirty=false; } 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) */ /* push our state back to the resident FM and persist (config + 48 channels) */
+6 -2
View File
@@ -30,7 +30,8 @@ MAGIC = cdefine("app_overlay.h", "APP_MAGIC").to_bytes(4, "little") #
HDR_VERSION = cdefine("app_overlay.h", "APP_HDR_VERSION") HDR_VERSION = cdefine("app_overlay.h", "APP_HDR_VERSION")
ABI_VERSION = cdefine("app_api.h", "APP_ABI_VERSION") ABI_VERSION = cdefine("app_api.h", "APP_ABI_VERSION")
OVERLAY_MAX = cdefine("app_overlay.h", "APP_OVERLAY_MAX") # 4 KiB overlay budget 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: def field(s: str, n: int) -> bytes:
b = s.encode("ascii", "strict")[: n - 1] 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("--entry", type=lambda x: int(x, 0), default=0)
ap.add_argument("--vma", type=lambda x: int(x, 0), required=True, 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)") 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() a = ap.parse_args()
code = open(a.infile, "rb").read() 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") sys.exit(f"code {len(code)} B exceeds overlay budget {OVERLAY_MAX} B")
crc = zlib.crc32(code) & 0xFFFFFFFF crc = zlib.crc32(code) & 0xFFFFFFFF
flags = FLAG_COMMITTED | (FLAG_SCREEN_SAVER if a.screensaver else 0)
header = struct.pack( header = struct.pack(
"<4sHHIIHH16s16sI8s", "<4sHHIIHH16s16sI8s",
MAGIC, HDR_VERSION, ABI_VERSION, 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, field(a.name, 16), field(a.ver, 16), a.vma, b"\x00" * 8,
) )
assert len(header) == 64, len(header) assert len(header) == 64, len(header)
+1 -1
View File
@@ -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 1 compile ; "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf"
step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" step 2 objcopy ; "$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin"
step 3 pack ; python3 ../pack_app.py "${APP}.bin" "${OUT}.app" \ 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 trap - ERR
BYTES=$(wc -c < "${APP}.bin") BYTES=$(wc -c < "${APP}.bin")
+33 -22
View File
@@ -92,6 +92,39 @@ void app_main(const app_api_t *api)
prevKey = APP_KEY_INVALID; prevKey = APP_KEY_INVALID;
while (running) { 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 sx = VAR[var][0], sy = VAR[var][1];
const uint8_t sd = VAR[var][2], rsh = VAR[var][3]; 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); } 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(); A->backlight_update();
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ LDFLAGS="-nostdlib -nostartfiles -T app.ld -Wl,--defsym,APP_VMA=${APP_VMA} -Wl,-
rm -f ./*.app ./*.elf ./*.bin rm -f ./*.app ./*.elf ./*.bin
"$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf" "$CC" $CFLAGS $LDFLAGS "${APP}_app.c" -lgcc -o "${APP}.elf"
"$OBJCOPY" -O binary "${APP}.elf" "${APP}.bin" "$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") BYTES=$(wc -c < "${APP}.bin")
test "$BYTES" -le 4096 test "$BYTES" -le 4096
printf ' ✅ %-13s %4d B (%d%% of 4 KiB) -> %s.app\n' \ printf ' ✅ %-13s %4d B (%d%% of 4 KiB) -> %s.app\n' \
+15
View File
@@ -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; uint8_t held=APP_KEY_INVALID, blinkTicks=0; uint16_t heldMs=0; bool longDone=false, ptt=false;
while(running){ while(running){
uint8_t key=A->get_key(); 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;i<TICK_MS/10u;i++){
A->delay_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(key==APP_KEY_PTT){
if(!ptt){ ptt=true; txDenied=A->trivfo_ptt(true)!=0; } if(!ptt){ ptt=true; txDenied=A->trivfo_ptt(true)!=0; }
} else if(ptt){ ptt=false; A->trivfo_ptt(false); txDenied=false; } } 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(); state=A->trivfo_tick();
if(state==APP_TRIVFO_RX||state==APP_TRIVFO_TX_STATE)
A->backlight_on();
if(state==APP_TRIVFO_RX){ if(state==APP_TRIVFO_RX){
if(++blinkTicks>=25u){ blinkTicks=0; channelLabelOn=!channelLabelOn; } if(++blinkTicks>=25u){ blinkTicks=0; channelLabelOn=!channelLabelOn; }
} else { blinkTicks=0; channelLabelOn=true; } } else { blinkTicks=0; channelLabelOn=true; }
+19
View File
@@ -186,6 +186,21 @@
"EDITION_STRING": "Labs", "EDITION_STRING": "Labs",
"TARGET": "f4hwn.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": [ "buildPresets": [
@@ -208,6 +223,10 @@
{ {
"name": "Labs", "name": "Labs",
"configurePreset": "Labs" "configurePreset": "Labs"
},
{
"name": "Max",
"configurePreset": "Max"
} }
] ]
} }
+3 -3
View File
@@ -16,7 +16,7 @@ set -euo pipefail
# --------------------------------------------- # ---------------------------------------------
IMAGE=uvk1-uvk5v3 IMAGE=uvk1-uvk5v3
RELEASE_PRESETS=(Fusion Transfer FieldOps Labs) RELEASE_PRESETS=(Fusion Transfer FieldOps Labs Max)
FLASH_LIMIT=$((118 * 1024)) FLASH_LIMIT=$((118 * 1024))
RAM_LIMIT=$((16 * 1024)) RAM_LIMIT=$((16 * 1024))
@@ -29,9 +29,9 @@ EXTRA_ARGS=("$@")
# --------------------------------------------- # ---------------------------------------------
# Validate preset name # 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 "❌ 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 exit 1
fi fi