mirror of
https://github.com/armel/uv-k1-k5v3-firmware-custom.git
synced 2026-10-04 12:07:24 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
435192e3bf | ||
|
|
9c86a7c349 | ||
|
|
0a61a6de12 | ||
|
|
1021ee5e18 | ||
|
|
6692b5191b | ||
|
|
c65db46bad | ||
|
|
a38f611bd5 | ||
|
|
528678a31b | ||
|
|
abc146a1b6 | ||
|
|
c7ff3eeb66 | ||
|
|
a3c72bdd93 | ||
|
|
e4408fcb55 | ||
|
|
5715cd27ef | ||
|
|
253104bb87 | ||
|
|
debf67a027 | ||
|
|
535ee80d2b | ||
|
|
559460739e | ||
|
|
0d69388e6d | ||
|
|
9bf075ea22 |
No files matched your search
@@ -214,6 +214,12 @@ enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG
|
||||
enable_feature(ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
app/foxhunt.c
|
||||
)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_ACTION_PICKER)
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_ACTION_PICKER AND NOT ENABLE_FEAT_F4HWN)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_ACTION_PICKER requires ENABLE_FEAT_F4HWN.")
|
||||
endif()
|
||||
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER)
|
||||
|
||||
@@ -227,11 +233,16 @@ endif()
|
||||
enable_feature(ENABLE_FEAT_F4HWN_QRCODE)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_LOGO)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_LOGO_SAV)
|
||||
enable_feature(ENABLE_FEAT_F4HWN_MENU_CAT)
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_LOGO_SAV AND NOT ENABLE_FEAT_F4HWN_LOGO)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_LOGO_SAV requires ENABLE_FEAT_F4HWN_LOGO.")
|
||||
endif()
|
||||
|
||||
if(ENABLE_FEAT_F4HWN_MENU_CAT AND NOT ENABLE_FEAT_F4HWN)
|
||||
message(FATAL_ERROR "ENABLE_FEAT_F4HWN_MENU_CAT requires ENABLE_FEAT_F4HWN.")
|
||||
endif()
|
||||
|
||||
# ---- DEBUGGING ----
|
||||
|
||||
enable_feature(ENABLE_AGC_SHOW_DATA)
|
||||
|
||||
+124
-35
@@ -51,6 +51,9 @@
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
#include "app/foxhunt.h"
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
#include "ui/menu.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_FMRADIO)
|
||||
static void ACTION_Scan_FM(bool bRestart);
|
||||
@@ -299,6 +302,120 @@ void ACTION_SwitchDemodul(void)
|
||||
}
|
||||
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
inline static bool ACTION_IsBlockedInFM(uint8_t action)
|
||||
{
|
||||
switch (action) {
|
||||
case ACTION_OPT_POWER:
|
||||
case ACTION_OPT_MONITOR:
|
||||
case ACTION_OPT_A_B:
|
||||
case ACTION_OPT_VFO_MR:
|
||||
case ACTION_OPT_SWITCH_DEMODUL:
|
||||
#ifdef ENABLE_VOX
|
||||
case ACTION_OPT_VOX:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
case ACTION_OPT_RXMODE:
|
||||
case ACTION_OPT_MAINONLY:
|
||||
case ACTION_OPT_WN:
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
case ACTION_OPT_RXA:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
case ACTION_OPT_POWER_HIGH:
|
||||
case ACTION_OPT_REMOVE_OFFSET:
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
case ACTION_OPT_BEAM:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
case ACTION_OPT_FOXHUNT:
|
||||
#endif
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
static void ACTION_Execute(uint8_t action)
|
||||
{
|
||||
if (action >= ACTION_OPT_LEN || action_opt_table[action] == NULL) {
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
if (gFmRadioMode && ACTION_IsBlockedInFM(action)) {
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
action_opt_table[action]();
|
||||
}
|
||||
|
||||
uint8_t gActionPickerKey;
|
||||
uint8_t gActionPickerSelection[2] = {1, 1};
|
||||
uint8_t gActionPickerTimeout_500ms;
|
||||
|
||||
bool ACTION_PickerProcessKey(KEY_Code_t key, bool isPressed, bool isHeld)
|
||||
{
|
||||
if (gActionPickerKey == 0)
|
||||
return false;
|
||||
if (isPressed)
|
||||
gActionPickerTimeout_500ms = ACTION_PICKER_TIMEOUT_500MS;
|
||||
uint8_t *selection = &gActionPickerSelection[gActionPickerKey - 1];
|
||||
|
||||
switch (key) {
|
||||
case KEY_UP:
|
||||
case KEY_DOWN:
|
||||
if (isPressed && !isHeld) {
|
||||
if (key == KEY_UP) {
|
||||
if (--*selection == 0)
|
||||
*selection = gSubMenu_SIDEFUNCTIONS_size - 1;
|
||||
}
|
||||
else if (++*selection >= gSubMenu_SIDEFUNCTIONS_size) {
|
||||
*selection = 1;
|
||||
}
|
||||
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
return true;
|
||||
|
||||
case KEY_MENU:
|
||||
if (!isPressed && !isHeld) {
|
||||
const uint8_t action = gSubMenu_SIDEFUNCTIONS[*selection].id;
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
ACTION_Execute(action);
|
||||
}
|
||||
return true;
|
||||
|
||||
case KEY_EXIT:
|
||||
case KEY_F:
|
||||
if (!isPressed) {
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
return true;
|
||||
|
||||
case KEY_PTT:
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
return false;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
{
|
||||
HideFKeyIcon();
|
||||
@@ -360,48 +477,20 @@ void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
}
|
||||
|
||||
// held or released after short press
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
ACTION_Execute(func);
|
||||
#else
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
if (gFmRadioMode) { // do not run these actions in FM radio mode
|
||||
switch (func) {
|
||||
case ACTION_OPT_POWER:
|
||||
case ACTION_OPT_MONITOR:
|
||||
case ACTION_OPT_A_B:
|
||||
case ACTION_OPT_VFO_MR:
|
||||
case ACTION_OPT_SWITCH_DEMODUL:
|
||||
#ifdef ENABLE_VOX
|
||||
case ACTION_OPT_VOX:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
case ACTION_OPT_RXMODE:
|
||||
case ACTION_OPT_MAINONLY:
|
||||
case ACTION_OPT_WN:
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
case ACTION_OPT_RXA:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
case ACTION_OPT_POWER_HIGH:
|
||||
case ACTION_OPT_REMOVE_OFFSET:
|
||||
#endif
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_BEAM
|
||||
case ACTION_OPT_BEAM:
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_FOXHUNT
|
||||
case ACTION_OPT_FOXHUNT:
|
||||
#endif
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (gFmRadioMode && ACTION_IsBlockedInFM(func)) {
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
action_opt_table[func]();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -55,6 +55,13 @@ void ACTION_SwitchDemodul(void);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
#define ACTION_PICKER_TIMEOUT_500MS 10u
|
||||
extern uint8_t gActionPickerKey;
|
||||
extern uint8_t gActionPickerSelection[2];
|
||||
extern uint8_t gActionPickerTimeout_500ms;
|
||||
bool ACTION_PickerProcessKey(KEY_Code_t key, bool isPressed, bool isHeld);
|
||||
#endif
|
||||
void ACTION_Handle(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld);
|
||||
|
||||
#endif
|
||||
+54
-4
@@ -312,6 +312,13 @@ static void CheckForIncoming(void)
|
||||
if (!g_SquelchLost)
|
||||
return; // squelch is closed
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
// FM scan in progress: ignore main-channel RX so scanning is not interrupted.
|
||||
// Normal FM listening (FM_SCAN_OFF) still yields to channel signals as before.
|
||||
if (gFmRadioMode && gFM_ScanState != FM_SCAN_OFF)
|
||||
return;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
ScreenSaverExit();
|
||||
#endif
|
||||
@@ -444,6 +451,12 @@ static void HandleIncoming(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FMRADIO
|
||||
// Defensive: do not leave FM scan for a main-channel signal.
|
||||
if (gFmRadioMode && gFM_ScanState != FM_SCAN_OFF)
|
||||
return;
|
||||
#endif
|
||||
|
||||
APP_StartListening(gMonitor ? FUNCTION_MONITOR : FUNCTION_RECEIVE);
|
||||
}
|
||||
|
||||
@@ -1245,9 +1258,11 @@ void APP_Update(void)
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed && gVoiceWriteIndex == 0)
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed && gVoiceWriteIndex == 0
|
||||
&& !UI_MAIN_ShouldHoldScanResume())
|
||||
#else
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed)
|
||||
if (!SCANNER_IsScanning() && gScanStateDir != SCAN_OFF && gScheduleScanListen && !gPttIsPressed
|
||||
&& !UI_MAIN_ShouldHoldScanResume())
|
||||
#endif
|
||||
{ // scanning
|
||||
CHFRSCANNER_ContinueScanning();
|
||||
@@ -1609,6 +1624,13 @@ void APP_TimeSlice10ms(void)
|
||||
|
||||
if (gCurrentFunction != FUNCTION_POWER_SAVE || !gRxIdleMode)
|
||||
CheckRadioInterrupts();
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0 && FUNCTION_IsRx()) {
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (gCurrentFunction == FUNCTION_TRANSMIT)
|
||||
{ // transmitting
|
||||
@@ -1808,6 +1830,15 @@ void cancelUserInputModes(void)
|
||||
void APP_TimeSlice500ms(void)
|
||||
{
|
||||
gNextTimeslice_500ms = false;
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0 && gActionPickerTimeout_500ms > 0 &&
|
||||
--gActionPickerTimeout_500ms == 0)
|
||||
{
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool exit_menu = false;
|
||||
|
||||
// Skipped authentic device check
|
||||
@@ -2027,6 +2058,10 @@ void APP_TimeSlice500ms(void)
|
||||
{
|
||||
gEeprom.KEY_LOCK = true; // lock the keyboard
|
||||
gUpdateStatus = true; // lock symbol needs showing
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
gActionPickerKey = 0;
|
||||
gUpdateDisplay = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (exit_menu) {
|
||||
@@ -2267,7 +2302,11 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
BACKLIGHT_TurnOn();
|
||||
}
|
||||
|
||||
if (Key == KEY_EXIT && bKeyHeld) { // exit key held pressed
|
||||
if (Key == KEY_EXIT && bKeyHeld
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
&& gActionPickerKey == 0
|
||||
#endif
|
||||
) { // exit key held pressed
|
||||
// clear the live DTMF decoder
|
||||
if (gDTMF_RX_live[0] != 0) {
|
||||
DTMF_clear_input_box_memory();
|
||||
@@ -2304,6 +2343,14 @@ static void ProcessKey(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
}
|
||||
|
||||
bool lowBatPopup = gLowBattery && !gLowBatteryConfirmed && gScreenToDisplay == DISPLAY_MAIN;
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0 &&
|
||||
(gEeprom.KEY_LOCK || lowBatPopup || gScreenToDisplay != DISPLAY_MAIN))
|
||||
gActionPickerKey = 0;
|
||||
if (ACTION_PickerProcessKey(Key, bKeyPressed, bKeyHeld))
|
||||
goto Skip;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN // Disable PTT if KEY_LOCK
|
||||
bool lck_condition = (gEeprom.KEY_LOCK || lowBatPopup) && gCurrentFunction != FUNCTION_TRANSMIT;
|
||||
@@ -2605,7 +2652,10 @@ Skip:
|
||||
gFlagRefreshSetting = false;
|
||||
gMenuCountdown = menu_timeout_500ms;
|
||||
|
||||
MENU_ShowCurrentSetting();
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel != MENU_LEVEL_CAT)
|
||||
#endif
|
||||
MENU_ShowCurrentSetting();
|
||||
}
|
||||
|
||||
if (gFlagPrepareTX) {
|
||||
|
||||
@@ -1133,7 +1133,9 @@ static void NextMemChannel(void)
|
||||
|
||||
if (!enabled || chan == 0xFFFF)
|
||||
{
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
const uint16_t searchStart = gNextMrChannel;
|
||||
#endif
|
||||
chan = RADIO_FindNextChannel(gNextMrChannel + gScanStateDir, gScanStateDir, true, gEeprom.SCAN_LIST_DEFAULT);
|
||||
if (chan == 0xFFFF)
|
||||
{ // no valid channel found -> wrapping back to the first channel
|
||||
|
||||
+196
-52
@@ -44,6 +44,14 @@
|
||||
#define FOXHUNT_TICK_MS 50
|
||||
#define FOXHUNT_TREND_TICKS 20 // compare the level roughly once per second
|
||||
|
||||
// Keypad lock: hold F for ~0.5 s to toggle it, mirroring the long-press F gesture of the
|
||||
// main screen. Tracked as an elapsed-time accumulator (not a tick count) so the same hold
|
||||
// fires in every loop — the 50 ms hunt/idle ticks and the 10 ms TX slices alike — letting
|
||||
// the pad be locked or unlocked at any moment, mid-burst included. While locked every key
|
||||
// is swallowed except this hold (and, in the hunt, the attenuation arrows), so a radio
|
||||
// carried by a child cannot be knocked out of the mode or its settings changed.
|
||||
#define FOXHUNT_LOCK_HOLD_MS 500
|
||||
|
||||
// Signal-history graph: a full-width scrolling trace of the level over the last
|
||||
// seconds, on the same 13-level S scale as the staircase (FOXHUNT_FillCount). It is
|
||||
// an alternate main gauge, toggled with the 2 key. The reading is EMA-smoothed each
|
||||
@@ -118,17 +126,46 @@
|
||||
#define FOXHUNT_MSG_LINE_ID 3
|
||||
#define FOXHUNT_MSG_LINE_ONE 2
|
||||
|
||||
// Attenuator via the BK4829 PGA (REG_13 bits 0..2) — the last gain stage before
|
||||
// the RSSI detector, so it scales the reading linearly. Reducing the early LNA
|
||||
// alone barely moved the reading on strong signals (mixer + PGA re-amplify).
|
||||
// pgaTab (bk4829.c) = {-33,-27,-21,-15,-9,-6,-3,0}; index 7 (= 0 dB) matches the
|
||||
// RX default (0x03DF), so step 0 leaves the gain untouched. The PGA indices per step
|
||||
// are {7,5,3,1} = 7 - 2*attStep (computed in FOXHUNT_ApplyAtt, no table needed).
|
||||
static const uint8_t FOXHUNT_ATT_DB[4] = {0, 6, 15, 27}; // 0, -6, -15, -27 dB
|
||||
// Front-end gain ladder (KEY_3), written straight to the BK4829 gain register REG_13.
|
||||
// Fields (LSB->MSB): pga[2:0], mixer[4:3], lna[7:5], lnaS[9:8]; gain tables live in
|
||||
// bk4829.c (BK4819_GetRxGain_dB). The RX default is 0x03DF (pga7/mixer3/lna6/lnaS3).
|
||||
//
|
||||
// Steps 0..3 attenuate the PGA only (the last stage) — {pga 7,5,3,1} = 0/-6/-15/-27 dB
|
||||
// on top of the default — same behaviour as before. Steps 4..5 attenuate at the *front*
|
||||
// of the chain instead: right on top of the fox the LNA saturates, so cutting after it
|
||||
// (PGA) no longer tracks the field; reducing the LNA itself brings it back into its
|
||||
// linear region and restores the gradient for the final approach.
|
||||
//
|
||||
// What "BYP" / "BYP+" actually are: a label of convenience, NOT a hardware bypass — the
|
||||
// BK4829 has no LNA-bypass switch we flip here. Both steps simply keep the PGA at minimum
|
||||
// and pull the front-end gain fields of REG_13 further down:
|
||||
// BYP (step 4): LNA reduced (lna 6->3) — front-end starts giving
|
||||
// BYP+ (step 5): LNA + LNA-short cut (lna 3->1, lnaS 3->1) — deepest front-end cut
|
||||
//
|
||||
// Total gain stays monotonic: -2/-8/-17/-29/-36/-70 dB. These values are a sane starting
|
||||
// point — expect to trim them on the air against a strong close source. NB the shown dBm
|
||||
// is not gain-compensated (bk4829.c), so the reading steps down when BYP engages; that is
|
||||
// intended (it re-centres the S-meter).
|
||||
#define FOXHUNT_ATT_COUNT 6 // 4 PGA steps + 2 front-end (BYP) steps
|
||||
#define FOXHUNT_ATT_BYP0 4 // first step at which the front-end (LNA) is reduced
|
||||
#define FOXHUNT_ATT_SETTLE_MS 40 // let the RSSI detector converge to the new gain before
|
||||
// rebasing the holds/trend/history onto it (tune on air)
|
||||
static const uint16_t FOXHUNT_ATT_REG13[FOXHUNT_ATT_COUNT] = {
|
||||
0x03DF, // 0 pga7 ATT 0 dB (RX default)
|
||||
0x03DD, // 1 pga5 ATT -6 dB
|
||||
0x03DB, // 2 pga3 ATT -15 dB
|
||||
0x03D9, // 3 pga1 ATT -27 dB
|
||||
0x0379, // 4 pga1 lna3 BYP (front-end starts giving)
|
||||
0x0139, // 5 pga1 lna1 lnaS1 BYP+ (deep front-end cut)
|
||||
};
|
||||
static const uint8_t FOXHUNT_ATT_DB[FOXHUNT_ATT_BYP0] = {0, 6, 15, 27}; // PGA-step labels
|
||||
|
||||
static KeyboardState kbd = {KEY_INVALID, KEY_INVALID, 0};
|
||||
|
||||
static bool foxRunning;
|
||||
static bool foxLocked; // keypad lock (long-press F toggles it, hunt + beacon)
|
||||
static uint16_t fHoldMs; // elapsed time the F key has been held (ms)
|
||||
static bool fLongDone; // the long-press lock toggle already fired this hold
|
||||
static uint8_t foxAudioMode;
|
||||
static uint8_t foxGraphMode; // FOXHUNT_GRAPH_BAR / _HIST, cycled by the 2 key
|
||||
static uint8_t attStep;
|
||||
@@ -189,11 +226,14 @@ static int16_t FOXHUNT_ReadDbm(void)
|
||||
return BK4819_GetRSSI_dBm() + dBmCorrTable[gRxVfo->Band];
|
||||
}
|
||||
|
||||
// Apply the selected attenuator step to the BK4829 PGA gain field.
|
||||
// Apply the selected step's BK4829 gain preset to REG_13: the PGA for the ATT steps,
|
||||
// plus the LNA/LNAs for the BYP steps. Read-modify-write masked to the known gain
|
||||
// fields (bits 0..9), so the undocumented upper bits 10..15 are preserved rather than
|
||||
// cleared. Every preset fits within 0x03FF, so only the gain fields ever change.
|
||||
static void FOXHUNT_ApplyAtt(void)
|
||||
{
|
||||
uint16_t reg = BK4819_ReadRegister(BK4819_REG_13);
|
||||
reg = (reg & ~0x7u) | (uint16_t)(7u - 2u * attStep); // PGA {7,5,3,1}, REG_13 bits 0..2
|
||||
reg = (reg & ~0x03FFu) | FOXHUNT_ATT_REG13[attStep];
|
||||
BK4819_WriteRegister(BK4819_REG_13, reg);
|
||||
}
|
||||
|
||||
@@ -382,12 +422,14 @@ static void FOXHUNT_Tag(const char *s, uint8_t x, uint8_t line)
|
||||
GUI_DisplaySmallestInverse(s, x, line, false, true, (uint8_t)(x + strlen(s) * 4));
|
||||
}
|
||||
|
||||
// Mirror the firmware's status-bar F indicator: when the F key is armed, a number key
|
||||
// steps its setting backwards. Same glyph and column as the main screens (status.c), so
|
||||
// it reads identically — Fox Hunt just renders its own status line, hence the redraw.
|
||||
// Mirror the firmware's status-bar indicator at the same column as the main screens
|
||||
// (status.c), so it reads identically: the padlock when the keypad is locked, else the
|
||||
// F glyph when the reverse-step arm is set. Lock takes priority, exactly as on status.c.
|
||||
static void FOXHUNT_DrawFKey(void)
|
||||
{
|
||||
if (gWasFKeyPressed)
|
||||
if (foxLocked)
|
||||
memcpy(gStatusLine + 69, gFontKeyLock, sizeof(gFontKeyLock));
|
||||
else if (gWasFKeyPressed)
|
||||
memcpy(gStatusLine + 69, gFontF, sizeof(gFontF));
|
||||
}
|
||||
|
||||
@@ -512,8 +554,12 @@ static void FOXHUNT_Draw(void)
|
||||
GUI_DisplaySmallest("+40", 110, 41, false, true);
|
||||
}
|
||||
|
||||
// Attenuator as an inverse label (left), tuned frequency (right).
|
||||
sprintf(str, "ATT %ddB", FOXHUNT_ATT_DB[attStep]);
|
||||
// Front-end ladder as an inverse label (left), tuned frequency (right). The PGA
|
||||
// steps show "ATT xxdB"; the two front-end steps show "BYP" / "BYP+".
|
||||
if (attStep < FOXHUNT_ATT_BYP0)
|
||||
sprintf(str, "ATT %ddB", FOXHUNT_ATT_DB[attStep]);
|
||||
else
|
||||
strcpy(str, (attStep == FOXHUNT_ATT_BYP0) ? "BYP" : "BYP+");
|
||||
FOXHUNT_Tag(str, 4, 6);
|
||||
FOXHUNT_DrawFreqBR(gRxVfo->pRX->Frequency);
|
||||
}
|
||||
@@ -538,11 +584,44 @@ static uint8_t FOXHUNT_RangeStep(uint8_t v, uint8_t lo, uint8_t hi, uint8_t step
|
||||
return (v <= lo) ? hi : (uint8_t)(v - step);
|
||||
}
|
||||
|
||||
// Attenuator: 0 -> -6 -> -15 -> -27 dB (F reverses).
|
||||
// Rebase the level-tracking state (peak/min hold, trend reference and the signal
|
||||
// history) onto a fresh reading. Used at hunt entry and after any front-end gain
|
||||
// change: the shown dBm is not gain-compensated, so without this a step change would
|
||||
// leave a stale peak, an artificial min, a one-shot false trend spike and a cliff in
|
||||
// the sparkline.
|
||||
static void FOXHUNT_RebaseMeasurements(void)
|
||||
{
|
||||
curDbm = FOXHUNT_ReadDbm();
|
||||
peakDbm = curDbm;
|
||||
minDbm = curDbm;
|
||||
trendRef = curDbm;
|
||||
trendDelta = 0;
|
||||
trendTick = 0;
|
||||
|
||||
// Prime the signal history flat at the current level so the sparkline scrolls in
|
||||
// from a sensible baseline instead of showing the gain step as a cliff.
|
||||
{
|
||||
uint8_t lvl = FOXHUNT_FillCount(curDbm);
|
||||
for (uint8_t i = 0; i < FOXHUNT_HIST_LEN; i++)
|
||||
histBuf[i] = lvl;
|
||||
}
|
||||
histHead = 0;
|
||||
histTick = 0;
|
||||
histEma = (int16_t)(curDbm * 8);
|
||||
}
|
||||
|
||||
// Front-end ladder: ATT 0 -> -6 -> -15 -> -27 dB, then BYP -> BYP+ (F reverses).
|
||||
static void FOXHUNT_AttCycle(int8_t dir)
|
||||
{
|
||||
attStep = FOXHUNT_WrapStep(attStep, 4, dir);
|
||||
attStep = FOXHUNT_WrapStep(attStep, FOXHUNT_ATT_COUNT, dir);
|
||||
FOXHUNT_ApplyAtt();
|
||||
|
||||
// Wait for the RSSI to settle to the new gain, THEN rebase the holds/trend/history.
|
||||
// Reading straight away would latch the old-gain level, and as the RSSI converges
|
||||
// over the next ticks the very artifacts the rebase avoids (stuck peak, false trend
|
||||
// spike, sparkline cliff) would reappear.
|
||||
SYSTEM_DelayMs(FOXHUNT_ATT_SETTLE_MS);
|
||||
FOXHUNT_RebaseMeasurements();
|
||||
}
|
||||
|
||||
// Fox identifier: MOE -> MOI -> MOS -> MOH -> MO5 -> MO -> CALL (F reverses).
|
||||
@@ -590,8 +669,14 @@ static void FOXHUNT_TickDelay(void)
|
||||
}
|
||||
|
||||
// Idle housekeeping the foreground scheduler normally does but this modal loop
|
||||
// bypasses: backlight timeout (BLTime) and, while hunting, the sleep auto power
|
||||
// off. Cadenced on the 500 ms system tick.
|
||||
// bypasses: the periodic battery sample, the config save and the backlight
|
||||
// timeout (BLTime). Cadenced on the 500 ms system tick.
|
||||
//
|
||||
// The SetOff auto power-off is deliberately NOT run here. Fox Hunt is an active,
|
||||
// attended mode that receives a signal continuously; the main loop never sleeps
|
||||
// while receiving (it re-arms gSleepModeCountdown_500ms on RX), so the old
|
||||
// hand-back-to-main scheme just ejected the user to the VFO instead of powering
|
||||
// off. Both hunt and beacon therefore ignore SetOff and run until EXIT.
|
||||
static void FOXHUNT_IdleHousekeeping(void)
|
||||
{
|
||||
if (!gNextTimeslice_500ms)
|
||||
@@ -613,22 +698,37 @@ static void FOXHUNT_IdleHousekeeping(void)
|
||||
// just a clean EXIT). No-op when nothing changed.
|
||||
FOXHUNT_SaveConfig();
|
||||
|
||||
// Backlight timeout: switch it off when the countdown expires (both modes).
|
||||
// Backlight timeout: at BLTime expiry drop from BLMax to BLMin (both modes).
|
||||
if (gBacklightCountdown_500ms > 0
|
||||
&& gEeprom.BACKLIGHT_TIME < 61
|
||||
&& --gBacklightCountdown_500ms == 0)
|
||||
BACKLIGHT_TurnOff();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
// Auto power-off: only while hunting (a running beacon must stay alive). Hand
|
||||
// back to the main loop just before expiry so it performs the actual sleep.
|
||||
if (!foxBeacon && gSetting_set_off != 0 && gSleepModeCountdown_500ms > 0) {
|
||||
if (gSleepModeCountdown_500ms > 1)
|
||||
gSleepModeCountdown_500ms--;
|
||||
else
|
||||
foxRunning = false;
|
||||
// Track a held F key and toggle the keypad lock once it crosses the long-press threshold.
|
||||
// Fed the time elapsed since the last call (a hunt/idle tick, or a TX slice), so it fires
|
||||
// after the same ~0.5 s hold in every loop and the pad can be (un)locked at any moment. A
|
||||
// non-F key resets the accumulator; the toggle fires once per physical hold. Returns true
|
||||
// exactly on the toggle so a caller that does not redraw every tick (the TX loop) can
|
||||
// refresh the padlock immediately.
|
||||
static bool FOXHUNT_LockHoldTrack(KEY_Code_t key, uint16_t elapsedMs)
|
||||
{
|
||||
if (key != KEY_F) {
|
||||
fHoldMs = 0;
|
||||
fLongDone = false;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (fLongDone) // already toggled once for this hold
|
||||
return false;
|
||||
fHoldMs += elapsedMs;
|
||||
if (fHoldMs < FOXHUNT_LOCK_HOLD_MS)
|
||||
return false;
|
||||
|
||||
fLongDone = true;
|
||||
foxLocked = !foxLocked;
|
||||
gWasFKeyPressed = false; // a lock toggle is not a reverse-step arm
|
||||
BACKLIGHT_TurnOn();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void FOXHUNT_HandleKeys(void)
|
||||
@@ -636,12 +736,25 @@ static void FOXHUNT_HandleKeys(void)
|
||||
kbd.prev = kbd.current;
|
||||
kbd.current = KEYBOARD_GetKey();
|
||||
|
||||
// Long-press F toggles the keypad lock; a short F press falls through to the reverse-
|
||||
// step arm on its rising edge below. One hunt tick has elapsed since the last call.
|
||||
FOXHUNT_LockHoldTrack(kbd.current, FOXHUNT_TICK_MS);
|
||||
|
||||
// Act only on the rising edge of a new key.
|
||||
if (kbd.current == KEY_INVALID || kbd.current == kbd.prev)
|
||||
return;
|
||||
|
||||
BACKLIGHT_TurnOn(); // any keypress wakes the screen and re-arms the timers
|
||||
|
||||
// While locked, swallow everything except the attenuation arrows — the lock is
|
||||
// released only by the long-press F handled above. Keeping ATT reachable is the whole
|
||||
// point: on the final approach the sensitivity still has to be pulled down by hand.
|
||||
if (foxLocked) {
|
||||
if (kbd.current == KEY_UP) FOXHUNT_AttCycle(+1); // more attenuation
|
||||
if (kbd.current == KEY_DOWN) FOXHUNT_AttCycle(-1); // less attenuation
|
||||
return;
|
||||
}
|
||||
|
||||
if (kbd.current == KEY_F) { // arm / disarm a reverse step for the next number key
|
||||
gWasFKeyPressed = !gWasFKeyPressed;
|
||||
return;
|
||||
@@ -664,9 +777,17 @@ static void FOXHUNT_HandleKeys(void)
|
||||
audioTick = FOXHUNT_RATE_SLOW_TICKS; // blip promptly
|
||||
break;
|
||||
case KEY_3:
|
||||
// Cycle the attenuator (0 -> -6 -> -15 -> -27 dB; F reverses).
|
||||
// Cycle the front-end ladder (ATT 0/-6/-15/-27 dB, then BYP/BYP+; F reverses).
|
||||
FOXHUNT_AttCycle(dir);
|
||||
break;
|
||||
case KEY_UP:
|
||||
// Attenuation up one step (also the locked-mode control).
|
||||
FOXHUNT_AttCycle(+1);
|
||||
break;
|
||||
case KEY_DOWN:
|
||||
// Attenuation down one step.
|
||||
FOXHUNT_AttCycle(-1);
|
||||
break;
|
||||
case KEY_MENU:
|
||||
// Reset the peak / min hold and the trend reference (before each body scan).
|
||||
peakDbm = curDbm;
|
||||
@@ -697,24 +818,14 @@ static void FOXHUNT_EnterHunt(void)
|
||||
FOXHUNT_ApplyAtt();
|
||||
FOXHUNT_SetAudio(); // (re)apply the current audio mode: off / beep / station
|
||||
|
||||
curDbm = FOXHUNT_ReadDbm();
|
||||
peakDbm = curDbm;
|
||||
minDbm = curDbm;
|
||||
trendRef = curDbm;
|
||||
trendDelta = 0;
|
||||
trendTick = 0;
|
||||
// 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.
|
||||
// SetAudio already waits when audio is on, but not in the default audio-off case.
|
||||
SYSTEM_DelayMs(FOXHUNT_ATT_SETTLE_MS);
|
||||
FOXHUNT_RebaseMeasurements();
|
||||
audioTick = (foxAudioMode == FOXHUNT_AUDIO_BEEP) ? FOXHUNT_RATE_SLOW_TICKS : 0;
|
||||
|
||||
// Prime the signal history flat at the current level so the sparkline scrolls
|
||||
// in from a sensible baseline (also applies on return from the beacon).
|
||||
{
|
||||
uint8_t lvl = FOXHUNT_FillCount(curDbm);
|
||||
for (uint8_t i = 0; i < FOXHUNT_HIST_LEN; i++)
|
||||
histBuf[i] = lvl;
|
||||
}
|
||||
histHead = 0;
|
||||
histTick = 0;
|
||||
histEma = (int16_t)(curDbm * 8);
|
||||
}
|
||||
|
||||
// Evaluate the TX gates for the beacon's TX VFO in the same precedence order as
|
||||
@@ -806,11 +917,28 @@ static bool FOXHUNT_TxDelay(uint16_t ms)
|
||||
|
||||
kbd.prev = kbd.current;
|
||||
kbd.current = KEYBOARD_GetKey();
|
||||
|
||||
// Track a held F across slices (fed the real slice time, so the ~0.5 s threshold
|
||||
// matches hunt/idle). This runs before the rising-edge test below — a held key
|
||||
// reads the same every slice and would otherwise be skipped — so the pad can be
|
||||
// locked or unlocked mid-burst. On a toggle, repaint at once so the padlock
|
||||
// appears without waiting for the next ID repaint.
|
||||
if (FOXHUNT_LockHoldTrack(kbd.current, slice)) {
|
||||
FOXHUNT_BeaconDraw(true, 0);
|
||||
FOXHUNT_BlitScreen();
|
||||
}
|
||||
|
||||
if (kbd.current == KEY_INVALID || kbd.current == kbd.prev)
|
||||
continue; // rising edge only
|
||||
|
||||
BACKLIGHT_TurnOn(); // keep the screen awake during TX
|
||||
|
||||
// Locked: ignore every key for the rest of the burst; the long-press F unlock is
|
||||
// handled above, before this gate, so the reverse-arm F and the setting keys stay
|
||||
// inert until the pad is unlocked.
|
||||
if (foxLocked)
|
||||
continue;
|
||||
|
||||
if (kbd.current == KEY_EXIT) {
|
||||
foxRunning = false; // abort the burst and leave Fox Hunt
|
||||
return true;
|
||||
@@ -1072,11 +1200,21 @@ static void FOXHUNT_BeaconKeys(void)
|
||||
kbd.prev = kbd.current;
|
||||
kbd.current = KEYBOARD_GetKey();
|
||||
|
||||
// Long-press F toggles the keypad lock (same gesture as the hunt screen). One idle
|
||||
// tick has elapsed since the last call.
|
||||
FOXHUNT_LockHoldTrack(kbd.current, FOXHUNT_TICK_MS);
|
||||
|
||||
if (kbd.current == KEY_INVALID || kbd.current == kbd.prev)
|
||||
return;
|
||||
|
||||
BACKLIGHT_TurnOn(); // any keypress wakes the screen and re-arms the timers
|
||||
|
||||
// Locked: swallow every key (the beacon has no per-key control to keep live, unlike
|
||||
// the hunt's attenuation arrows); release with the long-press F handled above. A burst
|
||||
// in progress enforces the same lock in FOXHUNT_TxDelay.
|
||||
if (foxLocked)
|
||||
return;
|
||||
|
||||
if (kbd.current == KEY_F) { // arm / disarm a reverse step for the next number key
|
||||
gWasFKeyPressed = !gWasFKeyPressed;
|
||||
return;
|
||||
@@ -1161,7 +1299,7 @@ static void FOXHUNT_BeaconTick(void)
|
||||
beaconPhaseTx = true; // next tick transmits
|
||||
}
|
||||
|
||||
FOXHUNT_IdleHousekeeping(); // backlight timeout (no sleep while beaconing)
|
||||
FOXHUNT_IdleHousekeeping(); // battery sample + save + backlight timeout
|
||||
FOXHUNT_TickDelay(); // tick delay + smooth backlight fade
|
||||
}
|
||||
|
||||
@@ -1199,7 +1337,7 @@ static void FOXHUNT_LoadConfig(void)
|
||||
PY25Q16_ReadBuffer(FOXHUNT_CFG_ADDR, cfg, sizeof(cfg));
|
||||
|
||||
if (cfg[0] == FOXHUNT_CFG_MAGIC) { // valid, saved config
|
||||
if (cfg[1] <= 3) attStep = cfg[1];
|
||||
if (cfg[1] < FOXHUNT_ATT_COUNT) attStep = cfg[1];
|
||||
if (cfg[2] <= FOXHUNT_GRAPH_HIST) foxGraphMode = cfg[2];
|
||||
if (cfg[3] <= FOXHUNT_AUDIO_STATION) foxAudioMode = cfg[3];
|
||||
if (cfg[4] >= FOXHUNT_BEACON_IDLE_MIN && cfg[4] <= FOXHUNT_BEACON_IDLE_MAX
|
||||
@@ -1285,6 +1423,12 @@ void APP_RunFoxHunt(void)
|
||||
kbd.prev = kbd.current = KEYBOARD_GetKey();
|
||||
gWasFKeyPressed = false; // start with the reverse-step arm cleared
|
||||
|
||||
// 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;
|
||||
while (foxRunning) {
|
||||
#if defined(ENABLE_UART) || defined(ENABLE_USB)
|
||||
@@ -1334,12 +1478,12 @@ void APP_RunFoxHunt(void)
|
||||
: BK4819_AF_MUTE);
|
||||
}
|
||||
|
||||
FOXHUNT_IdleHousekeeping(); // backlight timeout / sleep
|
||||
FOXHUNT_IdleHousekeeping(); // battery sample + save + backlight timeout
|
||||
FOXHUNT_TickDelay(); // tick delay + smooth backlight fade
|
||||
}
|
||||
|
||||
// Persist the session's settings so they survive a power cycle (covers EXIT
|
||||
// and the sleep auto power-off, both of which just clear foxRunning).
|
||||
// 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
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "settings.h"
|
||||
#include "ui/inputbox.h"
|
||||
#include "ui/main.h"
|
||||
#include "ui/menu.h"
|
||||
#include "ui/ui.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -454,6 +455,16 @@ static void MAIN_Key_DIGITS(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
gRequestDisplayScreen = DISPLAY_MAIN;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gWasFKeyPressed && (Key == KEY_SIDE1 || Key == KEY_SIDE2)) {
|
||||
gActionPickerKey = (Key == KEY_SIDE1) ? 1 : 2;
|
||||
gActionPickerTimeout_500ms = ACTION_PICKER_TIMEOUT_500MS;
|
||||
gUpdateDisplay = true;
|
||||
HideFKeyIcon();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
HideFKeyIcon();
|
||||
|
||||
processFKeyFunction(Key, true);
|
||||
@@ -835,6 +846,11 @@ static void MAIN_Key_MENU(bool bKeyPressed, bool bKeyHeld)
|
||||
|
||||
gFlagRefreshSetting = true;
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
gMenuLevel = MENU_LEVEL_CAT;
|
||||
UI_MENU_BuildCategoryScreen();
|
||||
gMenuCursor = (gMenuCatCursor < gMenuListCount) ? gMenuCatCursor : 0;
|
||||
#endif
|
||||
#ifdef ENABLE_VOICE
|
||||
gAnotherVoiceID = VOICE_ID_MENU;
|
||||
#endif
|
||||
|
||||
+74
-3
@@ -85,10 +85,10 @@ void MENU_StartCssScan(void)
|
||||
void MENU_CssScanFound(void)
|
||||
{
|
||||
if(gScanCssResultType == CODE_TYPE_DIGITAL || gScanCssResultType == CODE_TYPE_REVERSE_DIGITAL) {
|
||||
gMenuCursor = UI_MENU_GetMenuIdx(MENU_R_DCS);
|
||||
gMenuCursor = UI_MENU_GetViewPos(MENU_R_DCS);
|
||||
}
|
||||
else if(gScanCssResultType == CODE_TYPE_CONTINUOUS_TONE) {
|
||||
gMenuCursor = UI_MENU_GetMenuIdx(MENU_R_CTCS);
|
||||
gMenuCursor = UI_MENU_GetViewPos(MENU_R_CTCS);
|
||||
}
|
||||
|
||||
MENU_ShowCurrentSetting();
|
||||
@@ -1607,6 +1607,36 @@ static void MENU_Key_0_to_9(KEY_Code_t Key, bool bKeyPressed, bool bKeyHeld)
|
||||
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{ // saut-par-numero global : depuis l'ecran categories, entre dans All a l'item N
|
||||
const uint8_t allCount = UI_MENU_CategoryItemCount(CAT_ALL);
|
||||
uint16_t value;
|
||||
|
||||
if (gInputBoxIndex >= 2) {
|
||||
gInputBoxIndex = 0;
|
||||
value = (gInputBox[0] * 10) + gInputBox[1];
|
||||
} else {
|
||||
value = gInputBox[0];
|
||||
}
|
||||
|
||||
if (value > 0 && value <= allCount)
|
||||
{
|
||||
gMenuCategory = CAT_ALL;
|
||||
gMenuCatCursor = gMenuListCount - 1; // All = derniere entree de gCatOrder
|
||||
UI_MENU_BuildView();
|
||||
gMenuLevel = MENU_LEVEL_ITEMS;
|
||||
gMenuCursor = value - 1;
|
||||
gFlagRefreshSetting = true;
|
||||
}
|
||||
else if (gInputBoxIndex == 0)
|
||||
{
|
||||
gBeepToPlay = BEEP_500HZ_60MS_DOUBLE_BEEP_OPTIONAL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!gIsInSubMenu)
|
||||
{
|
||||
switch (gInputBoxIndex)
|
||||
@@ -1821,6 +1851,22 @@ Skip:
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_ITEMS)
|
||||
{ // remonter aux categories au lieu de quitter le menu
|
||||
gCatLastPos[gMenuCategory] = gMenuCursor; // memorise la position dans la categorie
|
||||
gMenuLevel = MENU_LEVEL_CAT;
|
||||
UI_MENU_BuildCategoryScreen();
|
||||
gMenuCursor = gMenuCatCursor;
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
#ifdef ENABLE_VOICE
|
||||
gAnotherVoiceID = VOICE_ID_CANCEL;
|
||||
#endif
|
||||
gPttWasReleased = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
gAnotherVoiceID = VOICE_ID_CANCEL;
|
||||
#endif
|
||||
@@ -1854,13 +1900,28 @@ static void MENU_Key_MENU(const bool bKeyPressed, const bool bKeyHeld)
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{ // niveau categories : MENU descend dans la categorie choisie
|
||||
gMenuCatCursor = gMenuCursor;
|
||||
gMenuCategory = gCatOrder[gMenuCursor];
|
||||
|
||||
UI_MENU_BuildView();
|
||||
gMenuLevel = MENU_LEVEL_ITEMS;
|
||||
gMenuCursor = (gCatLastPos[gMenuCategory] < gMenuListCount) ? gCatLastPos[gMenuCategory] : 0;
|
||||
gIsInSubMenu = false;
|
||||
gFlagRefreshSetting = true;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!gIsInSubMenu)
|
||||
{
|
||||
const int m = UI_MENU_GetCurrentMenuId();
|
||||
|
||||
#ifdef ENABLE_VOICE
|
||||
if (m != MENU_SCR)
|
||||
gAnotherVoiceID = MenuList[gMenuCursor].voice_id;
|
||||
gAnotherVoiceID = MenuList[gMenuIndices[gMenuCursor]].voice_id;
|
||||
#endif
|
||||
if (m == MENU_UPCODE
|
||||
|| m == MENU_DWCODE
|
||||
@@ -2060,6 +2121,16 @@ static void MENU_Key_UP_DOWN(bool bKeyPressed, bool bKeyHeld, int8_t Direction)
|
||||
gBeepToPlay = BEEP_1KHZ_60MS_OPTIONAL;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{ // niveau categories : deplacement simple du curseur
|
||||
gMenuCursor = NUMBER_AddWithWraparound(gMenuCursor, -Direction, 0, gMenuListCount - 1);
|
||||
gFlagRefreshSetting = true; // rearme le timeout menu (ShowCurrentSetting saute au niveau CAT)
|
||||
gRequestDisplayScreen = DISPLAY_MENU;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!gEeprom.SET_NAV && gIsInSubMenu) {
|
||||
Direction = -Direction;
|
||||
}
|
||||
|
||||
+6
-8
@@ -164,19 +164,17 @@ void Main(void)
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
gEeprom.KEY_LOCK = 0;
|
||||
SETTINGS_SaveSettings();
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
gMenuLevel = MENU_LEVEL_ITEMS;
|
||||
gMenuCategory = CAT_ALL;
|
||||
#endif
|
||||
gMenuCursor = UI_MENU_GetMenuIdx(FIRST_HIDDEN_MENU_ITEM);
|
||||
gSubMenuSelection = gSetting_F_LOCK;
|
||||
#endif
|
||||
}
|
||||
|
||||
// count the number of menu items
|
||||
gMenuListCount = 0;
|
||||
while (MenuList[gMenuListCount].name[0] != '\0') {
|
||||
if(!gF_LOCK && MenuList[gMenuListCount].menu_id == FIRST_HIDDEN_MENU_ITEM)
|
||||
break;
|
||||
|
||||
gMenuListCount++;
|
||||
}
|
||||
// build the current menu view (Etape 1: vue = All, identite)
|
||||
UI_MENU_BuildView();
|
||||
|
||||
// wait for user to release all butts before moving on
|
||||
if (GPIO_IsPttPressed() ||
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include <stdlib.h> // abs()
|
||||
|
||||
#include "app/app.h"
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
#include "app/action.h"
|
||||
#endif
|
||||
#include "app/chFrScanner.h"
|
||||
#include "app/dtmf.h"
|
||||
|
||||
@@ -240,6 +243,20 @@ void UI_MAIN_NotifyScanListChanged(void)
|
||||
gUpdateDisplay = true;
|
||||
}
|
||||
|
||||
// True while the "SCAN LIST xxx" name actually occupies the screen. Used to
|
||||
// freeze the scan so it does not race ahead of the (hidden) progress gauge,
|
||||
// which would make the bar snap forward when it reappears.
|
||||
//
|
||||
// The IS_MR_CHANNEL() test mirrors show_memory in UI_DrawScanProgress(): the
|
||||
// name is only ever drawn during a memory (channel) scan. A frequency/range
|
||||
// scan can still arm the countdown (F + UP/DOWN cycles lists on any scan), but
|
||||
// no name is shown there, so it must NOT be held - otherwise the scanner would
|
||||
// stall ~2 s with nothing on screen to explain the pause.
|
||||
bool UI_MAIN_ShouldHoldScanResume(void)
|
||||
{
|
||||
return gScanListNameCountdown_500ms > 0 && IS_MR_CHANNEL(gNextMrChannel);
|
||||
}
|
||||
|
||||
static inline void ScanProgress_SetBit(uint8_t *map, uint16_t ch)
|
||||
{
|
||||
map[ch >> 3] |= (uint8_t)(1u << (ch & 7));
|
||||
@@ -1308,6 +1325,23 @@ static void UI_PrintScanRangeCss(char *String, uint8_t LabelX, uint8_t ValueX, u
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
static void UI_PrintActionPickerLabel(uint8_t index, uint8_t line, bool big)
|
||||
{
|
||||
char label[20];
|
||||
strcpy(label, gSubMenu_SIDEFUNCTIONS[index].name);
|
||||
|
||||
char *newline = strchr(label, '\n');
|
||||
if (newline != NULL)
|
||||
*newline = ' ';
|
||||
|
||||
if (big)
|
||||
UI_PrintString(label, 0, LCD_WIDTH, line, 8);
|
||||
else
|
||||
UI_PrintStringSmallNormal(label, 0, LCD_WIDTH, line);
|
||||
}
|
||||
#endif
|
||||
|
||||
void UI_DisplayMain(void)
|
||||
{
|
||||
char String[22];
|
||||
@@ -1328,6 +1362,25 @@ void UI_DisplayMain(void)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_ACTION_PICKER
|
||||
if (gActionPickerKey != 0) {
|
||||
const uint8_t selection = gActionPickerSelection[gActionPickerKey - 1];
|
||||
uint8_t previous = selection - 1;
|
||||
uint8_t next = selection + 1;
|
||||
|
||||
if (previous == 0)
|
||||
previous = gSubMenu_SIDEFUNCTIONS_size - 1;
|
||||
if (next >= gSubMenu_SIDEFUNCTIONS_size)
|
||||
next = 1;
|
||||
|
||||
UI_PrintActionPickerLabel(previous, 1, false);
|
||||
UI_PrintActionPickerLabel(selection, 2, true);
|
||||
UI_PrintActionPickerLabel(next, 4, false);
|
||||
ST7565_BlitFullScreen();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_FEAT_F4HWN
|
||||
if (gEeprom.KEY_LOCK && gKeypadLocked > 0)
|
||||
{ // tell user how to unlock the keyboard
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define UI_MAIN_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
enum center_line_t {
|
||||
CENTER_LINE_NONE = 0,
|
||||
@@ -56,9 +57,11 @@ void UI_DisplayMain(void);
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_PROGRESS
|
||||
void UI_MAIN_NotifyScanProgressDataChanged(void);
|
||||
void UI_MAIN_NotifyScanListChanged(void);
|
||||
bool UI_MAIN_ShouldHoldScanResume(void);
|
||||
#else
|
||||
static inline void UI_MAIN_NotifyScanProgressDataChanged(void) {}
|
||||
static inline void UI_MAIN_NotifyScanListChanged(void) {}
|
||||
static inline bool UI_MAIN_ShouldHoldScanResume(void) { return false; }
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_AGC_SHOW_DATA
|
||||
|
||||
+237
-8
@@ -535,9 +535,15 @@ const uint8_t gSubMenu_SIDEFUNCTIONS_size = ARRAY_SIZE(gSubMenu_SIDEFUNCTIONS);
|
||||
|
||||
bool gIsInSubMenu;
|
||||
uint8_t gMenuCursor;
|
||||
uint8_t gMenuIndices[ARRAY_SIZE(MenuList)]; // Etape 1: table position affichee -> index MenuList (vue courante)
|
||||
|
||||
int UI_MENU_GetCurrentMenuId() {
|
||||
if(gMenuCursor < ARRAY_SIZE(MenuList))
|
||||
return MenuList[gMenuCursor].menu_id;
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
return 0xFF; // pas d'item courant au niveau categories
|
||||
#endif
|
||||
if(gMenuCursor < gMenuListCount)
|
||||
return MenuList[gMenuIndices[gMenuCursor]].menu_id;
|
||||
|
||||
return MenuList[ARRAY_SIZE(MenuList)-1].menu_id;
|
||||
}
|
||||
@@ -550,6 +556,221 @@ uint8_t UI_MENU_GetMenuIdx(uint8_t id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Position dans la vue courante (gMenuIndices) du menu_id, ou gMenuCursor si absent.
|
||||
// En vue All (identite) equivaut a UI_MENU_GetMenuIdx ; en vue categorie, donne la
|
||||
// position filtree correcte.
|
||||
uint8_t UI_MENU_GetViewPos(uint8_t id)
|
||||
{
|
||||
for (uint8_t i = 0; i < gMenuListCount; i++)
|
||||
if (MenuList[gMenuIndices[i]].menu_id == id)
|
||||
return i;
|
||||
return gMenuCursor;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
// --- Etape 2a : donnee du classement par categorie (cible Fusion) ---
|
||||
// Chaque liste = les menu_id d'une categorie, DANS l'ordre d'affichage voulu
|
||||
// (ex. SetPwr colle a Power). CAT_ALL n'a pas de liste : il reprend MenuList
|
||||
// tel quel, donc ordre et numeros d'origine preserves.
|
||||
const char *const CategoryNames[CAT_COUNT] = {
|
||||
[CAT_CHANNELS] = "Channels",
|
||||
[CAT_SCAN] = "Scan",
|
||||
[CAT_KEYS] = "Keys",
|
||||
[CAT_POWER] = "Power",
|
||||
[CAT_DISPLAY] = "Display",
|
||||
[CAT_TIMERS] = "Timers",
|
||||
[CAT_AUDIO] = "Audio",
|
||||
[CAT_RADIO] = "Radio",
|
||||
[CAT_DTMF] = "DTMF",
|
||||
[CAT_SERVICE] = "Service",
|
||||
[CAT_ALL] = "All",
|
||||
};
|
||||
|
||||
// Les menu_id de sous-features optionnelles sont gardes exactement comme dans
|
||||
// l'enum (menu.h) : sur un preset qui ne les compile pas, ils ne sont pas
|
||||
// references (sinon build KO, ex. preset Custom). Les autres MENU_SET_* sont
|
||||
// sous ENABLE_FEAT_F4HWN, garanti par la dependance CMake (App/CMakeLists.txt).
|
||||
static const uint8_t CatChannels[] = {
|
||||
MENU_STEP, MENU_TXP, MENU_SET_PWR, MENU_R_DCS, MENU_R_CTCS, MENU_T_DCS,
|
||||
MENU_T_CTCS, MENU_SFT_D, MENU_OFFSET, MENU_W_N,
|
||||
#ifdef ENABLE_FEAT_F4HWN_NARROWER
|
||||
MENU_SET_NFM,
|
||||
#endif
|
||||
MENU_BCL, MENU_COMPAND, MENU_AM, MENU_TX_LOCK, MENU_PTT_ID, MENU_LIST_CH,
|
||||
MENU_MEM_CH, MENU_DEL_CH, MENU_MEM_NAME,
|
||||
};
|
||||
static const uint8_t CatScan[] = {
|
||||
MENU_S_LIST, MENU_S_PRI, MENU_S_PRI_CH_1, MENU_S_PRI_CH_2, MENU_SC_REV,
|
||||
#ifdef ENABLE_FEAT_F4HWN_SCAN_FASTER
|
||||
MENU_SET_SCN,
|
||||
#endif
|
||||
};
|
||||
static const uint8_t CatKeys[] = {
|
||||
MENU_F1SHRT, MENU_F1LONG, MENU_F2SHRT, MENU_F2LONG, MENU_MLONG, MENU_AUTOLK, MENU_SET_LCK,
|
||||
#ifdef ENABLE_FEAT_F4HWN_RESCUE_OPS
|
||||
MENU_SET_KEY,
|
||||
#endif
|
||||
MENU_SET_PTT, MENU_1_CALL,
|
||||
};
|
||||
static const uint8_t CatPower[] = {
|
||||
MENU_SAVE, MENU_BAT_TXT,
|
||||
#ifdef ENABLE_FEAT_F4HWN_SLEEP
|
||||
MENU_SET_OFF,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_LOGO_SAV
|
||||
MENU_SET_SAV,
|
||||
#endif
|
||||
};
|
||||
static const uint8_t CatDisplay[] = { MENU_MDF, MENU_PONMSG, MENU_ABR, MENU_ABR_MIN, MENU_ABR_MAX, MENU_ABR_ON_TX_RX, MENU_SET_CTR, MENU_SET_INV, MENU_SET_MET, MENU_SET_GUI, MENU_VOL };
|
||||
static const uint8_t CatTimers[] = { MENU_TOT, MENU_SET_TOT, MENU_SET_EOT, MENU_SET_TMR };
|
||||
static const uint8_t CatAudio[] = {
|
||||
MENU_MIC, MENU_MIC_BAR, MENU_BEEP,
|
||||
#ifdef ENABLE_FEAT_F4HWN_VOL
|
||||
MENU_SET_VOL,
|
||||
#endif
|
||||
#ifdef ENABLE_FEAT_F4HWN_AUDIO
|
||||
MENU_SET_AUD,
|
||||
#endif
|
||||
};
|
||||
static const uint8_t CatRadio[] = { MENU_SQL, MENU_STE, MENU_RP_STE, MENU_ROGER, MENU_VOX, MENU_TDR };
|
||||
static const uint8_t CatDtmf[] = { MENU_UPCODE, MENU_DWCODE, MENU_D_ST, MENU_D_PRE, MENU_D_LIVE_DEC };
|
||||
static const uint8_t CatService[] = { MENU_F_LOCK, MENU_350EN, MENU_BATCAL, MENU_BATTYP, MENU_SET_NAV, MENU_RESET };
|
||||
|
||||
typedef struct { const uint8_t *ids; uint8_t len; } cat_list_t;
|
||||
|
||||
static const cat_list_t CategoryLists[CAT_COUNT] = {
|
||||
[CAT_CHANNELS] = { CatChannels, ARRAY_SIZE(CatChannels) },
|
||||
[CAT_SCAN] = { CatScan, ARRAY_SIZE(CatScan) },
|
||||
[CAT_KEYS] = { CatKeys, ARRAY_SIZE(CatKeys) },
|
||||
[CAT_POWER] = { CatPower, ARRAY_SIZE(CatPower) },
|
||||
[CAT_DISPLAY] = { CatDisplay, ARRAY_SIZE(CatDisplay) },
|
||||
[CAT_TIMERS] = { CatTimers, ARRAY_SIZE(CatTimers) },
|
||||
[CAT_AUDIO] = { CatAudio, ARRAY_SIZE(CatAudio) },
|
||||
[CAT_RADIO] = { CatRadio, ARRAY_SIZE(CatRadio) },
|
||||
[CAT_DTMF] = { CatDtmf, ARRAY_SIZE(CatDtmf) },
|
||||
[CAT_SERVICE] = { CatService, ARRAY_SIZE(CatService) },
|
||||
[CAT_ALL] = { NULL, 0 },
|
||||
};
|
||||
|
||||
uint8_t gMenuCategory = CAT_ALL;
|
||||
|
||||
// Index de 'id' dans MenuList, ou 0xFF si absent (item non compile).
|
||||
static uint8_t menu_find_idx(uint8_t id)
|
||||
{
|
||||
for (uint8_t i = 0; MenuList[i].name[0] != '\0'; i++)
|
||||
if (MenuList[i].menu_id == id)
|
||||
return i;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
uint8_t gMenuLevel = MENU_LEVEL_CAT;
|
||||
uint8_t gCatOrder[CAT_COUNT];
|
||||
uint8_t gMenuCatCursor = 0;
|
||||
uint8_t gCatLastPos[CAT_COUNT]; // derniere position du curseur item, par categorie
|
||||
|
||||
// Nombre d'items presents (compiles) dans une categorie.
|
||||
uint8_t UI_MENU_CategoryItemCount(uint8_t cat)
|
||||
{
|
||||
uint8_t n = 0;
|
||||
|
||||
if (cat == CAT_ALL)
|
||||
{
|
||||
for (uint8_t i = 0; MenuList[i].name[0] != '\0'; i++)
|
||||
{
|
||||
if (!gF_LOCK && MenuList[i].menu_id == FIRST_HIDDEN_MENU_ITEM)
|
||||
break;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
const cat_list_t *cl = &CategoryLists[cat];
|
||||
for (uint8_t k = 0; k < cl->len; k++)
|
||||
if (menu_find_idx(cl->ids[k]) != 0xFF)
|
||||
n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
// Construit l'ecran niveau categories : gCatOrder = categories visibles,
|
||||
// gMenuListCount = leur nombre. Service n'apparait que si gF_LOCK.
|
||||
void UI_MENU_BuildCategoryScreen(void)
|
||||
{
|
||||
gMenuListCount = 0;
|
||||
for (uint8_t c = 0; c < CAT_COUNT; c++)
|
||||
{
|
||||
if (c == CAT_SERVICE && !gF_LOCK)
|
||||
continue;
|
||||
gCatOrder[gMenuListCount++] = c;
|
||||
}
|
||||
}
|
||||
|
||||
// Rendu de l'ecran des categories (niveau 1).
|
||||
static void UI_MENU_DrawCategories(void)
|
||||
{
|
||||
char str[16];
|
||||
const unsigned int sep = 64; // separateur decale a droite : noms longs (ex. "Channels")
|
||||
const unsigned int x1 = sep + 2;
|
||||
const unsigned int x2 = LCD_WIDTH - 1;
|
||||
|
||||
UI_DisplayClear();
|
||||
|
||||
UI_DrawLineBuffer(gFrameBuffer, sep, 0, sep, 55, 1);
|
||||
for (uint8_t i = 0; i < sep; i += 2)
|
||||
gFrameBuffer[5][i] = 0x40;
|
||||
|
||||
const int count = gMenuListCount;
|
||||
const int cur = gMenuCursor;
|
||||
|
||||
int prev = cur - 1; if (prev < 0) prev = count - 1;
|
||||
int next = cur + 1; if (next >= count) next = 0;
|
||||
|
||||
if (count > 1)
|
||||
UI_PrintStringSmallNormal(CategoryNames[gCatOrder[prev]], 0, 0, 1);
|
||||
UI_PrintString(CategoryNames[gCatOrder[cur]], 0, 0, 2, 8);
|
||||
if (count > 1)
|
||||
UI_PrintStringSmallNormal(CategoryNames[gCatOrder[next]], 0, 0, 4);
|
||||
|
||||
sprintf(str, "%02u/%02u", 1 + cur, count);
|
||||
UI_PrintStringSmallNormal(str, 6, 0, 6);
|
||||
|
||||
sprintf(str, "%02u", UI_MENU_CategoryItemCount(gCatOrder[cur]));
|
||||
UI_PrintString(str, x1, x2, 1, 8);
|
||||
UI_PrintStringSmallNormal("items", x1, x2, 5);
|
||||
|
||||
ST7565_BlitFullScreen();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Construit la "vue" courante = table position affichee -> index MenuList.
|
||||
// Unique endroit qui fixe gMenuListCount + gMenuIndices.
|
||||
// CAT_ALL (defaut) = liste plate, identite -> ordre/numeros d'origine preserves.
|
||||
void UI_MENU_BuildView(void)
|
||||
{
|
||||
gMenuListCount = 0;
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuCategory != CAT_ALL)
|
||||
{
|
||||
const cat_list_t *cl = &CategoryLists[gMenuCategory];
|
||||
for (uint8_t k = 0; k < cl->len; k++)
|
||||
{
|
||||
uint8_t idx = menu_find_idx(cl->ids[k]);
|
||||
if (idx != 0xFF)
|
||||
gMenuIndices[gMenuListCount++] = idx;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (uint8_t i = 0; MenuList[i].name[0] != '\0'; i++)
|
||||
{
|
||||
if (!gF_LOCK && MenuList[i].menu_id == FIRST_HIDDEN_MENU_ITEM)
|
||||
break;
|
||||
|
||||
gMenuIndices[gMenuListCount++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t gSubMenuSelection;
|
||||
|
||||
// edit box
|
||||
@@ -624,6 +845,14 @@ void UI_DisplayMenu(void)
|
||||
char String[64]; // bigger cuz we can now do multi-line in one string (use '\n' char)
|
||||
char top_right_badge[16];
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
if (gMenuLevel == MENU_LEVEL_CAT)
|
||||
{
|
||||
UI_MENU_DrawCategories();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
const int m = UI_MENU_GetCurrentMenuId();
|
||||
|
||||
#ifdef ENABLE_DTMF_CALLING
|
||||
@@ -647,7 +876,7 @@ void UI_DisplayMenu(void)
|
||||
for (i = 0; i < 3; i++)
|
||||
if (gMenuCursor > 0 || i > 0)
|
||||
if ((gMenuListCount - 1) != gMenuCursor || i != 2)
|
||||
UI_PrintString(MenuList[gMenuCursor + i - 1].name, 0, 0, i * 2, 8);
|
||||
UI_PrintString(MenuList[gMenuIndices[gMenuCursor + i - 1]].name, 0, 0, i * 2, 8);
|
||||
|
||||
// invert the current menu list item pixels
|
||||
for (i = 0; i < (8 * menu_list_width); i++)
|
||||
@@ -683,17 +912,17 @@ void UI_DisplayMenu(void)
|
||||
if (prev_index < 0) {
|
||||
prev_index = menu_count - 1;
|
||||
}
|
||||
UI_PrintStringSmallNormal(MenuList[prev_index].name, 0, 0, 1);
|
||||
UI_PrintStringSmallNormal(MenuList[gMenuIndices[prev_index]].name, 0, 0, 1);
|
||||
|
||||
// current menu item - keep big n fat
|
||||
UI_PrintString(MenuList[menu_index].name, 0, 0, 2, 8);
|
||||
UI_PrintString(MenuList[gMenuIndices[menu_index]].name, 0, 0, 2, 8);
|
||||
|
||||
// trailing menu item - small text
|
||||
int next_index = menu_index + 1;
|
||||
if (next_index >= menu_count) {
|
||||
next_index = 0;
|
||||
}
|
||||
UI_PrintStringSmallNormal(MenuList[next_index].name, 0, 0, 4);
|
||||
UI_PrintStringSmallNormal(MenuList[gMenuIndices[next_index]].name, 0, 0, 4);
|
||||
|
||||
|
||||
// draw the menu index number/count
|
||||
@@ -706,12 +935,12 @@ void UI_DisplayMenu(void)
|
||||
{
|
||||
// current menu item
|
||||
// strcat(String, ":");
|
||||
UI_PrintString(MenuList[menu_index].name, 0, 0, 0, 8);
|
||||
UI_PrintString(MenuList[gMenuIndices[menu_index]].name, 0, 0, 0, 8);
|
||||
// UI_PrintStringSmallNormal(String, 0, 0, 0);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN
|
||||
sprintf(String, "%02u/%u", 1 + menu_index, menu_count);
|
||||
sprintf(String, "%02u/%02u", 1 + menu_index, menu_count);
|
||||
UI_PrintStringSmallNormal(String, 6, 0, 6);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -169,6 +169,37 @@ enum
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef ENABLE_FEAT_F4HWN_MENU_CAT
|
||||
// Categories (niveau 1) — l'ordre = ordre d'affichage de l'ecran des categories.
|
||||
enum {
|
||||
CAT_CHANNELS = 0,
|
||||
CAT_SCAN,
|
||||
CAT_KEYS,
|
||||
CAT_POWER,
|
||||
CAT_DISPLAY,
|
||||
CAT_TIMERS,
|
||||
CAT_AUDIO,
|
||||
CAT_RADIO,
|
||||
CAT_DTMF,
|
||||
CAT_SERVICE, // menu cache : n'apparait que si gF_LOCK
|
||||
CAT_ALL, // liste plate complete, ordre et numeros d'origine
|
||||
CAT_COUNT
|
||||
};
|
||||
|
||||
#define MENU_LEVEL_CAT 0
|
||||
#define MENU_LEVEL_ITEMS 1
|
||||
|
||||
extern const char *const CategoryNames[];
|
||||
extern uint8_t gMenuCategory;
|
||||
extern uint8_t gMenuLevel;
|
||||
extern uint8_t gCatOrder[];
|
||||
extern uint8_t gMenuCatCursor;
|
||||
extern uint8_t gCatLastPos[];
|
||||
|
||||
void UI_MENU_BuildCategoryScreen(void);
|
||||
uint8_t UI_MENU_CategoryItemCount(uint8_t cat);
|
||||
#endif
|
||||
|
||||
extern const uint8_t FIRST_HIDDEN_MENU_ITEM;
|
||||
extern const t_menu_item MenuList[];
|
||||
|
||||
@@ -249,6 +280,7 @@ extern const t_sidefunction gSubMenu_SIDEFUNCTIONS[];
|
||||
extern bool gIsInSubMenu;
|
||||
|
||||
extern uint8_t gMenuCursor;
|
||||
extern uint8_t gMenuIndices[];
|
||||
|
||||
extern int32_t gSubMenuSelection;
|
||||
|
||||
@@ -260,5 +292,7 @@ extern bool edit_is_uppercase;
|
||||
void UI_DisplayMenu(void);
|
||||
int UI_MENU_GetCurrentMenuId();
|
||||
uint8_t UI_MENU_GetMenuIdx(uint8_t id);
|
||||
uint8_t UI_MENU_GetViewPos(uint8_t id);
|
||||
void UI_MENU_BuildView(void);
|
||||
|
||||
#endif
|
||||
+5
-1
@@ -74,16 +74,18 @@
|
||||
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": false,
|
||||
"ENABLE_FEAT_F4HWN_BEAM": false,
|
||||
"ENABLE_FEAT_F4HWN_FOXHUNT": false,
|
||||
"ENABLE_FEAT_F4HWN_ACTION_PICKER": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": false,
|
||||
"ENABLE_FEAT_F4HWN_LOGO": false,
|
||||
"ENABLE_FEAT_F4HWN_LOGO_SAV": false,
|
||||
"ENABLE_FEAT_F4HWN_MENU_CAT": false,
|
||||
"ENABLE_AGC_SHOW_DATA": false,
|
||||
"ENABLE_UART_RW_BK_REGS": false,
|
||||
"ENABLE_SWD": false,
|
||||
"VERSION_STRING_1": "v0.22",
|
||||
"VERSION_STRING_2": "v5.8.0"
|
||||
"VERSION_STRING_2": "v5.9.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -209,6 +211,7 @@
|
||||
"ENABLE_FEAT_F4HWN_SCAN_SUBAUDIBLE": true,
|
||||
"ENABLE_FEAT_F4HWN_BEAM": true,
|
||||
"ENABLE_FEAT_F4HWN_FOXHUNT": true,
|
||||
"ENABLE_FEAT_F4HWN_ACTION_PICKER": true,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG": true,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false,
|
||||
"ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": true,
|
||||
@@ -216,6 +219,7 @@
|
||||
"ENABLE_FEAT_F4HWN_MEM": true,
|
||||
"ENABLE_FEAT_F4HWN_LOGO": true,
|
||||
"ENABLE_FEAT_F4HWN_LOGO_SAV": true,
|
||||
"ENABLE_FEAT_F4HWN_MENU_CAT": true,
|
||||
"ENABLE_SWD": true,
|
||||
"EDITION_STRING": "Fusion",
|
||||
"TARGET": "f4hwn.fusion"
|
||||
|
||||
@@ -177,7 +177,8 @@ Fusion is the reference edition of the project. It provides an all-in-one firmwa
|
||||
- scrolling signal-history graph,
|
||||
- peak, minimum and trend indicators,
|
||||
- selectable RF attenuation,
|
||||
- silent, Geiger-style and received-audio modes.
|
||||
- silent, Geiger-style and received-audio modes,
|
||||
- long-press `F` keypad lock (attenuation stays adjustable with the arrow keys).
|
||||
- Integrated Morse Beacon transmitter with:
|
||||
- `MOE`, `MOI`, `MOS`, `MOH`, `MO5` and `MO` identifiers,
|
||||
- optional callsign identification,
|
||||
@@ -185,6 +186,7 @@ Fusion is the reference edition of the project. It provides an all-in-one firmwa
|
||||
- live TX and idle countdowns,
|
||||
- persisted Beacon settings,
|
||||
- interactive control during transmission,
|
||||
- shared long-press `F` keypad lock,
|
||||
- TX-lock, modulation and battery-safety checks.
|
||||
- Fox Hunt and Beacon screens are mirrored to UV Studio's integrated K5Viewer.
|
||||
|
||||
|
||||
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -53,9 +53,10 @@ build_preset() {
|
||||
docker run --rm \
|
||||
-u $(id -u):$(id -g) \
|
||||
-it -v "$PWD":/src -w /src "$IMAGE" \
|
||||
bash -c "which arm-none-eabi-gcc && arm-none-eabi-gcc --version && \
|
||||
cmake --preset ${preset} ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"} && \
|
||||
cmake --build --preset ${preset} -j"
|
||||
bash -c 'which arm-none-eabi-gcc && arm-none-eabi-gcc --version &&
|
||||
cmake --preset "$1" "${@:2}" &&
|
||||
cmake --build --preset "$1" -j' \
|
||||
bash "${preset}" ${EXTRA_ARGS[@]+"${EXTRA_ARGS[@]}"}
|
||||
echo "✅ Done: ${preset}"
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user