Decouple FoxHunt and Beacon actions

This commit is contained in:
Armel FAUVEAU committed 2026-08-30 23:12:27 +02:00
1 parent 4c3847ef34
commit 0850420fdf
18 files changed
+244 -133

No files matched your search

+5 -3
View File
@@ -234,9 +234,11 @@ enable_feature(ENABLE_FEAT_F4HWN_BEAM
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG
app/rxtx_log.c
)
enable_feature(ENABLE_FEAT_F4HWN_FOXHUNT
app/foxhunt.c
)
enable_feature(ENABLE_FEAT_F4HWN_FOXHUNT)
enable_feature(ENABLE_FEAT_F4HWN_BEACON)
if(ENABLE_FEAT_F4HWN_FOXHUNT OR ENABLE_FEAT_F4HWN_BEACON)
target_sources(App INTERFACE app/foxhunt.c)
endif()
enable_feature(ENABLE_FEAT_F4HWN_ACTION_PICKER)
if(ENABLE_FEAT_F4HWN_ACTION_PICKER AND NOT ENABLE_FEAT_F4HWN)
+59 -4
View File
@@ -51,7 +51,7 @@
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
#include "app/rxtx_log.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
#include "app/foxhunt.h"
#endif
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
@@ -119,9 +119,12 @@ void (*const action_opt_table[ACTION_OPT_LEN])(void) = {
#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG
[ACTION_OPT_RXTX_LOG] = &ACTION_RxTxLog,
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
[ACTION_OPT_FOXHUNT] = &ACTION_FoxHunt,
#endif
#if defined(ENABLE_FEAT_F4HWN_BEACON) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
[ACTION_OPT_BEACON] = &ACTION_Beacon,
#endif
};
static_assert(ARRAY_SIZE(action_opt_table) == ACTION_OPT_LEN);
@@ -130,10 +133,33 @@ static_assert(ACTION_OPT_BEAM == 19);
static_assert(ACTION_OPT_POWER_HIGH == 20);
static_assert(ACTION_OPT_REMOVE_OFFSET == 21);
static_assert(ACTION_OPT_FOXHUNT == 22);
static_assert(ACTION_OPT_BEACON == 23);
bool ACTION_IsAvailable(uint8_t action)
{
return action < ACTION_OPT_LEN && action_opt_table[action] != NULL;
if (action >= ACTION_OPT_LEN || action_opt_table[action] == NULL)
return false;
#ifdef ENABLE_FEAT_F4HWN_OVERLAY_APPS
switch (action) {
#ifdef ENABLE_FMRADIO
case ACTION_OPT_FM:
return (APP_OverlayShortcutMask() & APP_SHORTCUT_FM) != 0;
#endif
#ifndef ENABLE_FEAT_F4HWN_FOXHUNT
case ACTION_OPT_FOXHUNT:
return (APP_OverlayShortcutMask() & APP_SHORTCUT_FOXHUNT) != 0;
#endif
#ifndef ENABLE_FEAT_F4HWN_BEACON
case ACTION_OPT_BEACON:
return (APP_OverlayShortcutMask() & APP_SHORTCUT_BEACON) != 0;
#endif
default:
break;
}
#endif
return true;
}
void ACTION_Power(void)
@@ -315,8 +341,11 @@ inline static bool ACTION_IsBlockedInFM(uint8_t action)
#ifdef ENABLE_FEAT_F4HWN_BEAM
case ACTION_OPT_BEAM:
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
case ACTION_OPT_FOXHUNT:
#endif
#if defined(ENABLE_FEAT_F4HWN_BEACON) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
case ACTION_OPT_BEACON:
#endif
return true;
@@ -466,6 +495,32 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
ACTION_Execute(func);
}
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void ACTION_FoxHunt(void)
{
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
APP_RunFoxHunt();
GUI_SelectNextDisplay(DISPLAY_MAIN);
#else
if (APP_LaunchOverlayByName("FoxHunt") != APP_OK)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
#endif
}
#endif
#if defined(ENABLE_FEAT_F4HWN_BEACON) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void ACTION_Beacon(void)
{
#ifdef ENABLE_FEAT_F4HWN_BEACON
APP_RunBeacon();
GUI_SelectNextDisplay(DISPLAY_MAIN);
#else
if (APP_LaunchOverlayByName("Beacon") != APP_OK)
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
#endif
}
#endif
#ifdef ENABLE_FMRADIO
void ACTION_FM(void)
+1 -1
View File
@@ -1985,7 +1985,7 @@ void APP_TimeSlice500ms(void)
if ((gBatteryCheckCounter & 1) == 0)
{
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT)
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON)
BATTERY_Sample(true);
#else
BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++], &gBatteryCurrent);
+86 -108
View File
@@ -16,7 +16,7 @@
#include "app/foxhunt.h"
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON)
#if defined(ENABLE_UART) || defined(ENABLE_USB)
#include "app/uart.h"
@@ -86,7 +86,7 @@
#define FOXHUNT_AUDIO_BEEP 1
#define FOXHUNT_AUDIO_STATION 2
// --- Beacon (fox) sub-mode ---------------------------------------------------
// --- Beacon (fox) app --------------------------------------------------------
// Turns the radio into the hidden transmitter: each cycle keys up on the TX VFO and
// repeats the CW fox identifier (MOE..MO5, or "<call> MOE") in Morse for the TX window,
// then stays silent for the (adjustable) idle gap. The carrier stays up during the
@@ -201,8 +201,7 @@ static const uint8_t FOXHUNT_MORSE_DIGIT[10] = {
// Trailing character of each fox id: MOE / MOI / MOS / MOH / MO5 (index = FOXHUNT_FOX_*).
static const char FOXHUNT_FOX_TAIL[5] = {'E', 'I', 'S', 'H', '5'};
// Beacon sub-mode state.
static bool foxBeacon; // false = hunt (RX), true = beacon (TX)
// Beacon state.
static bool beaconPhaseTx; // true = transmit this tick, false = idle gap
static uint8_t beaconIdle; // configured silence between IDs (s)
static uint8_t beaconIdleLeft; // seconds left in the current silence
@@ -782,11 +781,6 @@ static void FOXHUNT_HandleKeys(void)
minDbm = curDbm;
trendRef = curDbm;
break;
case KEY_SIDE1:
case KEY_SIDE2:
// Same shortcut that opened Fox Hunt now toggles to the beacon.
FOXHUNT_EnterBeacon();
break;
default:
break;
}
@@ -794,22 +788,18 @@ static void FOXHUNT_HandleKeys(void)
gWasFKeyPressed = false; // any non-F key consumes (or cancels) the reverse arm
}
// (Re)enter the hunt (RX) sub-mode: fixed front-end gain, attenuator and audio mode
// re-applied, peak/trend reset. Called at start-up and when leaving the beacon. The
// audio mode is a hunt setting, so it is (re)applied here, not forced off — it must
// survive a trip through the beacon.
// Enter Fox Hunt with fixed front-end gain, the persisted attenuator/audio mode,
// and freshly rebased level tracking.
static void FOXHUNT_EnterHunt(void)
{
foxBeacon = false;
BK4819_SetAGC(false);
FOXHUNT_ApplyAtt();
FOXHUNT_SetAudio(); // (re)apply the current audio mode: off / beep / station
// Let the RSSI settle to the gain just applied, then reset the peak/min hold, trend
// reference and signal history onto a fresh reading (also re-primes the sparkline on
// return from the beacon). Same settle as FOXHUNT_AttCycle: without it a restored
// BYP/BYP+ step at start-up or on beacon return would rebase onto the old-gain reading.
// reference and signal history onto a fresh reading. Same settle as
// FOXHUNT_AttCycle: without it a restored
// BYP/BYP+ step at start-up would rebase onto the old-gain reading.
// SetAudio already waits when audio is on, but not in the default audio-off case.
SYSTEM_DelayMs(FOXHUNT_ATT_SETTLE_MS);
FOXHUNT_RebaseMeasurements();
@@ -836,9 +826,8 @@ static VfoState_t FOXHUNT_TxState(void)
// Refuse feedback shown when a burst is barred: reuse the beacon screen layout and
// the radio's own state label (VfoStateStr, e.g. "TX DISABLE" / "BAT LOW" / "VOLT
// HIGH"), same font as the main screen, for a beat; the caller then falls back to
// the hunt. The RX front-end and audio path are left untouched so the hunt keeps
// reading cleanly.
// HIGH"), same font as the main screen, for a beat; the caller then waits through
// a fresh idle gap before checking again.
static void FOXHUNT_TxDeniedNotice(VfoState_t state)
{
// Frame (clear + BEACON tag + battery + barred TX frequency).
@@ -855,12 +844,10 @@ static void FOXHUNT_TxDeniedNotice(VfoState_t state)
FOXHUNT_TickDelay();
}
// Enter the beacon (fox) sub-mode. The TX gates are enforced per burst in
// FOXHUNT_BeaconTick (which runs immediately), so entry just arms the state.
// Enter the Beacon app. TX gates are enforced before each burst.
static void FOXHUNT_EnterBeacon(void)
{
foxBeacon = true;
AUDIO_AudioPathOff(); // no RX audio while beaconing; foxAudioMode kept for the hunt
AUDIO_AudioPathOff();
gCurrentVfo = gTxVfo; // the VFO RADIO_SetTxParameters keys up
beaconPhaseTx = true;
}
@@ -879,8 +866,8 @@ static void FOXHUNT_DrawTxSeconds(void)
}
// Sleep in short slices while watching the keypad, so a burst stays interactive and the
// TX-window countdown (beaconTxMsLeft) drains in real time. EXIT leaves Fox Hunt and a
// side key aborts back to the hunt (both return true). The 1/2/3 keys edit the beacon
// TX-window countdown (beaconTxMsLeft) drains in real time. EXIT leaves Beacon. The
// 1/2/3 keys edit the beacon
// settings live, exactly as in the idle phase: the TX window (1) and idle gap (2) take
// effect at the upcoming window / gap, and a FOX change (3) at the next repeat (the
// message is rebuilt there, never under the running loop).
@@ -926,17 +913,12 @@ static bool FOXHUNT_TxDelay(uint16_t ms)
continue;
if (kbd.current == KEY_EXIT) {
foxRunning = false; // abort the burst and leave Fox Hunt
return true;
}
if (kbd.current == KEY_SIDE1 || kbd.current == KEY_SIDE2) {
foxBeacon = false; // abort the burst and switch back to hunt
foxRunning = false; // abort the burst and leave Beacon
return true;
}
if (kbd.current == KEY_MENU) {
// Cut the transmission short but stay in Beacon: aborting the burst without
// clearing foxBeacon/foxRunning drops the loop straight into a fresh idle
// gap of the configured IDLE length.
// Cut the transmission short but stay in Beacon: the loop drops straight
// into a fresh idle gap of the configured IDLE length.
return true;
}
if (kbd.current == KEY_F) { // arm / disarm the reverse step
@@ -1051,8 +1033,8 @@ static void FOXHUNT_BeaconTransmit(void)
stop = FOXHUNT_TxDelay(FOXHUNT_MORSE_UNIT_MS * 7);
}
// Key down: mute, drop the PA and restore RX for the silent gap. If the burst
// was interrupted, FOXHUNT_TxDelay already set the target mode (quit or hunt).
// Key down: mute, drop the PA and restore RX for the silent gap. If EXIT
// interrupted the burst, FOXHUNT_TxDelay has already stopped the app.
BK4819_EnterTxMute();
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
RADIO_SetupRegisters(true);
@@ -1208,11 +1190,7 @@ static void FOXHUNT_BeaconKeys(void)
switch (kbd.current) {
case KEY_EXIT:
foxRunning = false; // leave Fox Hunt entirely
break;
case KEY_SIDE1:
case KEY_SIDE2:
foxBeacon = false; // toggle back to the hunt
foxRunning = false;
break;
case KEY_MENU:
// Restart a full idle interval now (the TX-phase M drops here too).
@@ -1237,28 +1215,26 @@ static void FOXHUNT_BeaconTick(void)
// LOCK / TX LOCK and modulation are fixed for the session, but the battery
// is re-sampled over the idle gaps (FOXHUNT_IdleHousekeeping), so a beacon
// left running stops keying up once the pack falls low or over-voltage
// instead of transmitting blind. Refuse, notify, and drop back to hunt.
// instead of transmitting blind. Refuse, notify, then retry after an idle gap.
VfoState_t state = FOXHUNT_TxState();
if (state != VFO_STATE_NORMAL) {
FOXHUNT_TxDeniedNotice(state);
foxBeacon = false;
beaconPhaseTx = false;
FOXHUNT_EnterHunt();
beaconIdleLeft = beaconIdle;
beaconIdleTick = 0;
return;
}
// The burst repeats the ID for the whole TX window, redrawing the screen
// itself each repeat (remaining-window countdown); it may clear foxBeacon
// (side-key abort) or foxRunning (EXIT).
// itself each repeat (remaining-window countdown); EXIT may clear
// foxRunning and abort it.
FOXHUNT_BeaconTransmit();
beaconPhaseTx = false;
beaconIdleLeft = beaconIdle;
beaconIdleTick = 0;
if (foxRunning && !foxBeacon) // side-key switched to hunt
FOXHUNT_EnterHunt();
return; // EXIT (quit) falls through
return;
}
// Idle (silent) phase: responsive, editable countdown.
@@ -1267,12 +1243,8 @@ static void FOXHUNT_BeaconTick(void)
#endif
FOXHUNT_BeaconKeys();
if (!foxRunning) // EXIT: leave Fox Hunt
if (!foxRunning)
return;
if (!foxBeacon) { // side-key: back to the hunt
FOXHUNT_EnterHunt();
return;
}
FOXHUNT_BeaconDraw(false, beaconIdleLeft);
FOXHUNT_BlitScreen();
@@ -1340,7 +1312,7 @@ static void FOXHUNT_LoadConfig(void)
// Write the settings to flash, but only when they changed since the last write.
// Called on the 500 ms tick (so any change persists within ~0.5 s and survives a
// power-off, not just a clean EXIT) and once more on leaving Fox Hunt. The RAM
// power-off, not just a clean EXIT) and once more on leaving either app. The RAM
// compare avoids re-reading the 4 KB sector on every quiet tick.
static void FOXHUNT_SaveConfig(void)
{
@@ -1354,31 +1326,46 @@ static void FOXHUNT_SaveConfig(void)
memcpy(foxCfgSaved, cfg, sizeof(cfg));
}
void APP_RunFoxHunt(void)
static void FOXHUNT_Begin(void)
{
// Finish any pending backlight fade, then start with the screen on and a full
// BLTime window (BACKLIGHT_TurnOn also re-arms the sleep countdown).
BACKLIGHT_UpdateTickless();
BACKLIGHT_TurnOn();
// Hunt on the user-selected VFO: dual-watch / cross-band may have left the
// radio listening on the other VFO, so force RX on the selected one and retune.
// Both apps operate on the user-selected VFO. Dual watch may have left the
// receiver on the other one, so establish a deterministic starting point.
gEeprom.RX_VFO = gEeprom.TX_VFO;
gRxVfo = gTxVfo;
gCurrentVfo = gTxVfo;
RADIO_SetupRegisters(true);
// Beacon defaults (LoadConfig may override fox / tx / idle below).
// Initialise the complete legacy seven-byte configuration before loading it.
// Keeping the layout unchanged preserves existing FieldOps settings and lets
// either independently compiled app update its fields without a migration.
attStep = 0;
foxGraphMode = FOXHUNT_GRAPH_BAR;
foxAudioMode = FOXHUNT_AUDIO_OFF;
beaconIdle = FOXHUNT_BEACON_IDLE_DEF;
beaconTx = FOXHUNT_BEACON_TX_DEF;
foxFox = FOXHUNT_FOX_CALL; // identified beacon by default (legal on ham)
foxFox = FOXHUNT_FOX_CALL;
beaconPhaseTx = false;
FOXHUNT_LoadConfig();
// Read the callsign from the boot message (line 1), sanitised to what Morse can
// send; empty/erased leaves foxCall empty (the CALL id then falls back to bare MOE).
// Ignore the key that launched the modal app until it has been released.
kbd.prev = kbd.current = KEYBOARD_GetKey();
gWasFKeyPressed = false;
foxLocked = false;
fHoldMs = 0;
fLongDone = false;
foxRunning = true;
}
static void FOXHUNT_LoadCallsign(void)
{
char call[FOXHUNT_CALLSIGN_MAX + 1];
uint8_t n = 0;
// Read the callsign from the boot message (line 1), sanitised to what Morse can
// send; empty/erased leaves foxCall empty (the CALL id then falls back to bare MOE).
PY25Q16_ReadBuffer(FOXHUNT_CALLSIGN_ADDR, call, FOXHUNT_CALLSIGN_MAX);
call[FOXHUNT_CALLSIGN_MAX] = '\0';
for (uint8_t i = 0; i < FOXHUNT_CALLSIGN_MAX; i++) {
@@ -1393,44 +1380,36 @@ void APP_RunFoxHunt(void)
foxCall[n] = '\0';
}
// Start in the hunt (RX) sub-mode, S-meter staircase gauge by default, then restore
// any persisted settings (attenuator, gauge, audio, beacon idle / fox / tx).
attStep = 0;
foxGraphMode = FOXHUNT_GRAPH_BAR;
foxAudioMode = FOXHUNT_AUDIO_OFF;
FOXHUNT_LoadConfig(); // may override att / gauge / audio / idle / fox / tx
FOXHUNT_EnterHunt(); // applies the restored attStep and audio mode
// beaconMsg is (re)built by FOXHUNT_BeaconTransmit before every burst, and nothing
// reads it before the first burst, so no need to assemble it here.
static void FOXHUNT_End(void)
{
FOXHUNT_SaveConfig();
gWasFKeyPressed = false;
AUDIO_AudioPathOff();
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
}
// Prime the key state with whatever is held right now, so the side key that
// launched Fox Hunt (still down on a long-press assignment) is not taken for
// a fresh press and does not immediately toggle to the beacon.
kbd.prev = kbd.current = KEYBOARD_GetKey();
gWasFKeyPressed = false; // start with the reverse-step arm cleared
/* Both entry points stay in this translation unit so their small modal services
* can be shared when both features are enabled. The action table references only
* the enabled entry point(s); --gc-sections then drops the complete unused RX or
* Beacon call graph, including its private constants and bitmaps. */
void APP_RunFoxHunt(void)
{
FOXHUNT_Begin();
// Always enter unlocked: the keypad lock is a transient safety toggle, not a
// persisted setting, so a previous session must not leave the pad locked.
foxLocked = false;
fHoldMs = 0;
fLongDone = false;
foxRunning = true;
FOXHUNT_EnterHunt();
while (foxRunning) {
#if defined(ENABLE_UART) || defined(ENABLE_USB)
UART_ServiceCommands();
#endif
if (foxBeacon) {
FOXHUNT_BeaconTick();
continue;
}
#ifdef ENABLE_FEAT_F4HWN_K5VIEWER
// Keep the K5Viewer link alive and pick up any remote key.
K5VIEWER_ParseInput();
#endif
FOXHUNT_HandleKeys();
if (foxBeacon) // just switched to beacon: skip the RX work this tick
continue;
if (!foxRunning)
break;
curDbm = FOXHUNT_ReadDbm();
if (curDbm > peakDbm)
@@ -1468,24 +1447,23 @@ void APP_RunFoxHunt(void)
FOXHUNT_TickDelay(); // tick delay + smooth backlight fade
}
// Persist the session's settings on the way out (EXIT clears foxRunning) so
// they survive a later power cycle.
FOXHUNT_SaveConfig();
gWasFKeyPressed = false; // don't leak a pending reverse-step arm to the main screen
// Mute any pending tone, drop the PA (safety), then restore the normal VFO
// selection and RX config.
AUDIO_AudioPathOff();
BK4819_ToggleGpioOut(BK4819_GPIO1_PIN29_PA_ENABLE, false);
RADIO_SelectVfos();
RADIO_SetupRegisters(true);
FOXHUNT_End();
}
void ACTION_FoxHunt(void)
void APP_RunBeacon(void)
{
APP_RunFoxHunt();
GUI_SelectNextDisplay(DISPLAY_MAIN);
FOXHUNT_Begin();
FOXHUNT_LoadCallsign();
FOXHUNT_EnterBeacon();
while (foxRunning) {
#if defined(ENABLE_UART) || defined(ENABLE_USB)
UART_ServiceCommands();
#endif
FOXHUNT_BeaconTick();
}
#endif // ENABLE_FEAT_F4HWN_FOXHUNT
FOXHUNT_End();
}
#endif // ENABLE_FEAT_F4HWN_FOXHUNT || ENABLE_FEAT_F4HWN_BEACON
+12 -3
View File
@@ -17,7 +17,7 @@
#ifndef APP_FOXHUNT_H
#define APP_FOXHUNT_H
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON)
#include "keyboard_state.h"
@@ -46,12 +46,21 @@
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#endif
// Entry point wired as an assignable side-key action (see action.c).
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
// Assignable resident/overlay launchers (see action.c).
void ACTION_FoxHunt(void);
void ACTION_Beacon(void);
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
// Self-contained modal loop that owns the screen and keypad until EXIT.
void APP_RunFoxHunt(void);
#endif
#ifdef ENABLE_FEAT_F4HWN_BEACON
void APP_RunBeacon(void);
#endif
#endif // ENABLE_FEAT_F4HWN_FOXHUNT
#endif // APP_FOXHUNT_H
+54
View File
@@ -633,8 +633,60 @@ uint8_t APP_ValidateSlot(uint8_t slot, app_header_t *out_header)
return APP_OK;
}
static bool app_shortcuts_cached;
static uint8_t app_shortcut_mask;
static uint8_t app_shortcut_slots[3];
static int8_t app_shortcut_index(const char *name)
{
if (strncmp(name, "Broadcast FM", APP_NAME_LEN) == 0) return 0;
if (strncmp(name, "FoxHunt", APP_NAME_LEN) == 0) return 1;
if (strncmp(name, "Beacon", APP_NAME_LEN) == 0) return 2;
return -1;
}
static void app_cache_shortcuts(void)
{
if (app_shortcuts_cached)
return;
app_shortcut_mask = 0;
const uint32_t overlay_vma = (uint32_t)PY25Q16_OverlayBuffer();
for (uint8_t slot = 0; slot < APP_SLOT_COUNT; slot++) {
app_header_t h;
if (APP_ValidateSlot(slot, &h) != APP_OK || h.link_vma != overlay_vma)
continue;
const int8_t index = app_shortcut_index(h.name);
if (index >= 0) {
const uint8_t bit = (uint8_t)(1u << index);
if (!(app_shortcut_mask & bit)) {
app_shortcut_mask |= bit;
app_shortcut_slots[index] = slot;
}
}
}
app_shortcuts_cached = true;
}
uint8_t APP_OverlayShortcutMask(void)
{
app_cache_shortcuts();
return app_shortcut_mask;
}
uint8_t APP_LaunchOverlayByName(const char *name)
{
const int8_t index = app_shortcut_index(name);
if (index >= 0) {
app_cache_shortcuts();
return (app_shortcut_mask & (1u << index))
? APP_LaunchOverlay(app_shortcut_slots[index])
: APP_ERR_MAGIC;
}
app_header_t h;
for (uint8_t slot = 0; slot < APP_SLOT_COUNT; slot++) {
@@ -840,6 +892,7 @@ uint8_t APP_SlotErase(uint8_t slot)
for (uint32_t off = 0; off < APP_SLOT_STRIDE; off += APP_SECTOR_SIZE)
PY25Q16_SectorErase(base + off);
PY25Q16_InvalidateCache();
app_shortcuts_cached = false;
return APP_OK;
}
@@ -851,6 +904,7 @@ uint8_t APP_SlotWrite(uint8_t slot, uint32_t offset, const uint8_t *data, uint32
return APP_ERR_SIZE;
PY25Q16_WriteBuffer(APP_SLOT_BASE(slot) + offset, data, len, false);
PY25Q16_InvalidateCache();
app_shortcuts_cached = false;
return APP_OK;
}
+7
View File
@@ -70,6 +70,10 @@
#define APP_NAME_LEN 16
#define APP_VERSION_LEN 16
#define APP_SHORTCUT_FM (1u << 0)
#define APP_SHORTCUT_FOXHUNT (1u << 1)
#define APP_SHORTCUT_BEACON (1u << 2)
typedef struct __attribute__((packed)) {
uint32_t magic; /* APP_MAGIC */
uint16_t hdr_version; /* APP_HDR_VERSION */
@@ -106,6 +110,9 @@ uint8_t APP_LaunchOverlay(uint8_t slot);
/* Find the first valid app with this exact header name and launch it. */
uint8_t APP_LaunchOverlayByName(const char *name);
/* Cached availability of apps exposed as resident quick actions. */
uint8_t APP_OverlayShortcutMask(void);
/* Host-tool slot management (external flash only, never brick-critical). */
uint8_t APP_SlotInfo(uint8_t slot, app_header_t *out_header);
uint8_t APP_SlotErase(uint8_t slot);
+1 -1
View File
@@ -301,7 +301,7 @@ const uint8_t BITMAP_NOAA[12] =
};
#endif
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON)
const uint8_t BITMAP_FoxHuntSignal[10] =
{ // point source + the speaker bitmap's two sound waves, oriented horizontally
0b00001000,
+1 -1
View File
@@ -45,7 +45,7 @@ extern const uint8_t BITMAP_compand[6];
extern const uint8_t BITMAP_NOAA[12];
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON)
extern const uint8_t BITMAP_FoxHuntSignal[10];
extern const uint8_t BITMAP_FoxHuntSpeaker[10];
extern const uint8_t BITMAP_FoxHuntUp[11];
+3 -4
View File
@@ -54,10 +54,9 @@ static const AddrMapping_t ADDR_MAPPINGS[] = {
_MK_MAPPING(0x009000, 0x009000, 0x0090E7), // 14 VFO * 16 Bytes = 0x9000 -> 0x90E0 (the old
// 0x90D6 bound was 10 B short: it truncated the
// 470 MHz VFO1 record at 0x90D0 -> 0x90DF)
// Fox Hunt settings * 7 Bytes 0x90E0 -> 0x90E7
// (ENABLE_FEAT_F4HWN_FOXHUNT, written directly by
// app/foxhunt.c; concatenated here so aircopy clones
// them together with the VFOs)
// Fox Hunt / Beacon settings * 7 Bytes 0x90E0 -> 0x90E7
// (written directly by app/foxhunt.c; concatenated
// here so aircopy clones them with the VFOs)
_MK_MAPPING(0x00A000, 0x00A000, 0x00A170), // Settings * 16 Bytes (ex 0x004000) 0x00A000 -> 0x00A010
// Settings * 16 Bytes (ex 0x005000) 0x00A010 -> 0x00A020
+1 -1
View File
@@ -130,7 +130,7 @@ unsigned int BATTERY_VoltsToPercent(const unsigned int voltage_10mV)
return 0;
}
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT)
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON)
void BATTERY_Sample(const bool bDisplayBatteryLevel)
{
BOARD_ADC_GetBatteryInfo(&gBatteryVoltages[gBatteryVoltageIndex++], &gBatteryCurrent);
+1 -1
View File
@@ -45,7 +45,7 @@ typedef enum {
unsigned int BATTERY_VoltsToPercent(unsigned int voltage_10mV);
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT)
#if defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS) || defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON)
void BATTERY_Sample(bool bDisplayBatteryLevel);
#endif
void BATTERY_GetReadings(bool bDisplayBatteryLevel);
+2 -1
View File
@@ -126,8 +126,9 @@ enum ACTION_OPT_t {
ACTION_OPT_POWER_HIGH = 20,
ACTION_OPT_REMOVE_OFFSET = 21,
ACTION_OPT_FOXHUNT = 22,
ACTION_OPT_BEACON = 23,
ACTION_OPT_LEN = 23
ACTION_OPT_LEN = 24
};
#ifdef ENABLE_VOICE
+2 -1
View File
@@ -495,7 +495,8 @@ const t_sidefunction gSubMenu_SIDEFUNCTIONS[] =
{"BEAM", ACTION_OPT_BEAM},
{"POWER\nHIGH", ACTION_OPT_POWER_HIGH},
{"REMOVE\nOFFSET", ACTION_OPT_REMOVE_OFFSET},
{"FOX HUNT\nBEACON", ACTION_OPT_FOXHUNT},
{"FOX HUNT", ACTION_OPT_FOXHUNT},
{"BEACON", ACTION_OPT_BEACON},
};
const uint8_t gSubMenu_SIDEFUNCTIONS_size = ARRAY_SIZE(gSubMenu_SIDEFUNCTIONS);
+1 -1
View File
@@ -54,7 +54,7 @@ static void convertTime(uint8_t *line, uint8_t type)
#endif
#endif
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void UI_DrawStatusBattery(uint8_t *line, char *str)
#else
static __attribute__((always_inline)) inline void UI_DrawStatusBattery(uint8_t *line, char *str)
+1 -1
View File
@@ -19,7 +19,7 @@
#include <stdint.h>
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
#if defined(ENABLE_FEAT_F4HWN_FOXHUNT) || defined(ENABLE_FEAT_F4HWN_BEACON) || defined(ENABLE_FEAT_F4HWN_OVERLAY_APPS)
void UI_DrawStatusBattery(uint8_t *line, char *str);
#endif
void UI_DisplayStatus();
+4
View File
@@ -82,6 +82,7 @@
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": false,
"ENABLE_FEAT_F4HWN_BEAM": false,
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
"ENABLE_FEAT_F4HWN_BEACON": false,
"ENABLE_FEAT_F4HWN_ACTION_PICKER": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
@@ -132,6 +133,7 @@
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true,
"ENABLE_FEAT_F4HWN_BEAM": false,
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
"ENABLE_FEAT_F4HWN_BEACON": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG": true,
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true,
@@ -155,6 +157,7 @@
"cacheVariables": {
"ENABLE_FEAT_F4HWN_RESCUE_OPS": true,
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
"ENABLE_FEAT_F4HWN_BEACON": true,
"EDITION_STRING": "FieldOps",
"TARGET": "f4hwn.fieldops"
}
@@ -175,6 +178,7 @@
"cacheVariables": {
"ENABLE_FEAT_F4HWN_RESCUE_OPS": true,
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
"ENABLE_FEAT_F4HWN_BEACON": false,
"ENABLE_AIRCOPY": true,
"ENABLE_FEAT_F4HWN_BEAM": true,
"ENABLE_FEAT_F4HWN_GAME": false,
+3 -2
View File
@@ -187,7 +187,7 @@ Specialized presets extend Fusion for specific uses:
- selectable RF attenuation,
- silent, Geiger-style and received-audio modes,
- long-press `F` keypad lock (attenuation stays adjustable with the arrow keys).
- Integrated Morse Beacon transmitter with:
- Independent Morse Beacon transmitter with:
- `MOE`, `MOI`, `MOS`, `MOH`, `MO5` and `MO` identifiers,
- optional callsign identification,
- configurable TX and idle periods,
@@ -264,7 +264,8 @@ Specialized presets extend Fusion for specific uses:
- offset removal,
- BEAM,
- RF Log,
- Fox Hunt / Beacon.
- Fox Hunt,
- Beacon.
### Reliability and optimization