mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-02 03:15:37 +00:00
Add BLTime support to overlay apps and Max preset
This commit is contained in:
1 parent
0850420fdf
commit
99057f9dd2
17 files changed
+295
-91
No files matched your search
+71
-20
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
+3
-1
@@ -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) */
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
#include <string.h>
|
||||
#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
|
||||
|
||||
+46
-3
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
+17
-2
@@ -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) */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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' \
|
||||
|
||||
@@ -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;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(!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; }
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+3
-3
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user