diff --git a/App/app/foxhunt.c b/App/app/foxhunt.c index 4b7be3c4..37ce1c0c 100644 --- a/App/app/foxhunt.c +++ b/App/app/foxhunt.c @@ -377,6 +377,15 @@ 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. +static void FOXHUNT_DrawFKey(void) +{ + if (gWasFKeyPressed) + memcpy(gStatusLine + 69, gFontF, sizeof(gFontF)); +} + // Battery icon plus the optional voltage/percentage text, top-right of the status // line — shared by the hunt and beacon screens. static void FOXHUNT_DrawStatusBattery(void) @@ -409,8 +418,9 @@ static void FOXHUNT_BeaconChrome(void) { UI_DisplayClear(); UI_StatusClear(); - GUI_DisplaySmallestInverse("BEACON", 2, 0, true, true, 28); + GUI_DisplaySmallestInverse("BEACON", 2, 0, true, true, 26); FOXHUNT_DrawStatusBattery(); + FOXHUNT_DrawFKey(); FOXHUNT_DrawFreqBR(gTxVfo->pTX->Frequency); } @@ -428,6 +438,7 @@ static void FOXHUNT_Draw(void) // Battery (icon + optional percentage/voltage) top-right, as on the main screens. FOXHUNT_DrawStatusBattery(); + FOXHUNT_DrawFKey(); // Gauge-mode icon (2 key), between the label and the audio icon: ascending // bars for the S-meter staircase, a sine wave for the signal history. Each @@ -498,46 +509,60 @@ static void FOXHUNT_Draw(void) // One key = one setting, each press cycling to the next value and wrapping — the same // scheme for the hunt gauges and the beacon settings, so there are no navigation arrows -// to learn. FOXHUNT_ApplyAtt is re-asserted for the attenuator; the beacon values are -// read where they take effect (TX window at the burst start, idle at the burst end, fox -// at the next repeat), so cycling any of them is safe at any time, TX included. +// to learn. dir is +1 for a plain press, -1 when the F key armed a reverse step. The +// beacon values are read where they take effect (TX window at the burst start, idle at +// the burst end, fox at the next repeat), so cycling any of them is safe at any time. -// Attenuator: 0 -> -6 -> -15 -> -27 -> 0 dB. -static void FOXHUNT_AttCycle(void) +// Wrap a 0..count-1 index one step forward (dir > 0) or backward, both ways round. +static uint8_t FOXHUNT_WrapStep(uint8_t v, uint8_t count, int8_t dir) { - attStep = (uint8_t)((attStep + 1) & 3); + return (uint8_t)((v + (dir > 0 ? 1u : (unsigned)(count - 1u))) % count); +} + +// Step a lo..hi value by step, wrapping lo<->hi at the ends. +static uint8_t FOXHUNT_RangeStep(uint8_t v, uint8_t lo, uint8_t hi, uint8_t step, int8_t dir) +{ + if (dir > 0) + return (v >= hi) ? lo : (uint8_t)(v + step); + return (v <= lo) ? hi : (uint8_t)(v - step); +} + +// Attenuator: 0 -> -6 -> -15 -> -27 dB (F reverses). +static void FOXHUNT_AttCycle(int8_t dir) +{ + attStep = FOXHUNT_WrapStep(attStep, 4, dir); FOXHUNT_ApplyAtt(); } -// Fox identifier: MOE -> MOI -> MOS -> MOH -> MO5 -> CALL -> MOE. -static void FOXHUNT_FoxCycle(void) +// Fox identifier: MOE -> MOI -> MOS -> MOH -> MO5 -> MO -> CALL (F reverses). +static void FOXHUNT_FoxCycle(int8_t dir) { - foxFox = (uint8_t)((foxFox + 1) % FOXHUNT_FOX_COUNT); + foxFox = FOXHUNT_WrapStep(foxFox, FOXHUNT_FOX_COUNT, dir); } -// TX window: 5 s up to 60 s, then back to 5 s. -static void FOXHUNT_TxCycle(void) +// TX window: 5..60 s (F reverses). +static void FOXHUNT_TxCycle(int8_t dir) { - beaconTx = (beaconTx >= FOXHUNT_BEACON_TX_MAX) - ? FOXHUNT_BEACON_TX_MIN : (uint8_t)(beaconTx + FOXHUNT_BEACON_TX_STEP); + beaconTx = FOXHUNT_RangeStep(beaconTx, FOXHUNT_BEACON_TX_MIN, FOXHUNT_BEACON_TX_MAX, + FOXHUNT_BEACON_TX_STEP, dir); } -// Idle gap: 5 s up to 240 s, then back to 5 s. -static void FOXHUNT_IdleCycle(void) +// Idle gap: 5..240 s (F reverses). +static void FOXHUNT_IdleCycle(int8_t dir) { - beaconIdle = (beaconIdle >= FOXHUNT_BEACON_IDLE_MAX) - ? FOXHUNT_BEACON_IDLE_MIN : (uint8_t)(beaconIdle + FOXHUNT_BEACON_IDLE_STEP); + beaconIdle = FOXHUNT_RangeStep(beaconIdle, FOXHUNT_BEACON_IDLE_MIN, FOXHUNT_BEACON_IDLE_MAX, + FOXHUNT_BEACON_IDLE_STEP, dir); } -// Apply a beacon number key; returns true when it changed a setting so the caller can -// refresh. The keys follow the on-screen layout: 1 = TX (top-left), 2 = IDLE (below it), -// 3 = FOX (right). Shared by both beacon phases. -static bool FOXHUNT_BeaconKey(KEY_Code_t key) +// Apply a beacon number key in the given direction; returns true when it changed a +// setting so the caller can refresh. Keys follow the on-screen layout: 1 = TX (top-left), +// 2 = IDLE (below it), 3 = FOX (right). Shared by both beacon phases. +static bool FOXHUNT_BeaconKey(KEY_Code_t key, int8_t dir) { switch (key) { - case KEY_1: FOXHUNT_TxCycle(); return true; - case KEY_2: FOXHUNT_IdleCycle(); return true; - case KEY_3: FOXHUNT_FoxCycle(); return true; + case KEY_1: FOXHUNT_TxCycle(dir); return true; + case KEY_2: FOXHUNT_IdleCycle(dir); return true; + case KEY_3: FOXHUNT_FoxCycle(dir); return true; default: return false; } } @@ -606,6 +631,12 @@ static void FOXHUNT_HandleKeys(void) BACKLIGHT_TurnOn(); // any keypress wakes the screen and re-arms the timers + if (kbd.current == KEY_F) { // arm / disarm a reverse step for the next number key + gWasFKeyPressed = !gWasFKeyPressed; + return; + } + const int8_t dir = gWasFKeyPressed ? -1 : 1; + switch (kbd.current) { case KEY_EXIT: foxRunning = false; @@ -615,15 +646,15 @@ static void FOXHUNT_HandleKeys(void) foxGraphMode ^= 1; break; case KEY_2: - // Cycle the audio: silent -> Geiger beep -> station audio -> silent. - foxAudioMode = (uint8_t)((foxAudioMode + 1) % 3); + // Cycle the audio: silent -> Geiger beep -> station audio (F reverses). + foxAudioMode = FOXHUNT_WrapStep(foxAudioMode, 3, dir); FOXHUNT_SetAudio(); if (foxAudioMode == FOXHUNT_AUDIO_BEEP) audioTick = FOXHUNT_RATE_SLOW_TICKS; // blip promptly break; case KEY_3: - // Cycle the attenuator (0 -> -6 -> -15 -> -27 dB, then back to 0). - FOXHUNT_AttCycle(); + // Cycle the attenuator (0 -> -6 -> -15 -> -27 dB; F reverses). + FOXHUNT_AttCycle(dir); break; case KEY_MENU: // Reset the peak / min hold and the trend reference (before each body scan). @@ -639,6 +670,8 @@ static void FOXHUNT_HandleKeys(void) default: break; } + + 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 @@ -781,9 +814,16 @@ static bool FOXHUNT_TxDelay(uint16_t ms) // gap of the configured IDLE length. return true; } - if (FOXHUNT_BeaconKey(kbd.current)) { + if (kbd.current == KEY_F) { // arm / disarm the reverse step + gWasFKeyPressed = !gWasFKeyPressed; + FOXHUNT_BeaconDraw(true, 0); // reflect the F indicator in the status line + FOXHUNT_BlitScreen(); + continue; + } + if (FOXHUNT_BeaconKey(kbd.current, gWasFKeyPressed ? -1 : 1)) { + gWasFKeyPressed = false; // consumed; also clears the F indicator FOXHUNT_BeaconDraw(true, 0); // show the new value immediately - ST7565_BlitFullScreen(); + FOXHUNT_BlitScreen(); } } return false; @@ -854,14 +894,27 @@ static void FOXHUNT_BeaconTransmit(void) FOXHUNT_BuildBeaconMsg(); // Redraw once per repeat (remaining-window countdown + reset progress), in the - // muted gap so it never stretches a Morse element. + // muted gap so it never stretches a Morse element. Local LCD only here — the + // K5Viewer frame is pushed after the ID (below), when the text is complete. beaconCharsSent = 0; FOXHUNT_BeaconDraw(true, 0); - FOXHUNT_BlitScreen(); + ST7565_BlitStatusLine(); + ST7565_BlitFullScreen(); for (uint8_t i = 0; !stop && beaconMsg[i]; i++) stop = FOXHUNT_MorseChar(beaconMsg[i], i + 1u); +#ifdef ENABLE_FEAT_F4HWN_K5VIEWER + // Service the K5Viewer link in the muted gap (never during a keyed element, so + // the Morse timing stays intact): ParseInput refills the keep-alive that Update + // drains — without it the link dies within ~15 repeats over a long TX window — + // and Update then mirrors the now-complete ID frame plus the live seconds. + if (!stop) { + K5VIEWER_ParseInput(); + K5VIEWER_Update(false); + } +#endif + // Word gap before the next repeat, only while the window still has room. if (!stop && beaconTxMsLeft > 0) stop = FOXHUNT_TxDelay(FOXHUNT_MORSE_UNIT_MS * 7); @@ -973,7 +1026,9 @@ static void FOXHUNT_BeaconDraw(bool txNow, uint8_t idleLeft) FOXHUNT_BeaconChrome(); if (txNow) { - // Top line: "TX" + remaining window seconds (refreshed live in FOXHUNT_TxDelay). + // On-air icon in the status bar (only while the carrier is up), then the top + // line "TX" + remaining window seconds (refreshed live in FOXHUNT_TxDelay). + memcpy(gStatusLine + 48, BITMAP_FoxHuntTx, sizeof(BITMAP_FoxHuntTx)); FOXHUNT_DrawTxSeconds(); FOXHUNT_DrawBeaconMessage(beaconCharsSent); } else { @@ -1005,6 +1060,11 @@ static void FOXHUNT_BeaconKeys(void) BACKLIGHT_TurnOn(); // any keypress wakes the screen and re-arms the timers + if (kbd.current == KEY_F) { // arm / disarm a reverse step for the next number key + gWasFKeyPressed = !gWasFKeyPressed; + return; + } + switch (kbd.current) { case KEY_EXIT: foxRunning = false; // leave Fox Hunt entirely @@ -1019,10 +1079,12 @@ static void FOXHUNT_BeaconKeys(void) beaconIdleTick = 0; break; default: - // 1 = TX window, 2 = idle gap, 3 = fox (same keys as during TX). - FOXHUNT_BeaconKey(kbd.current); + // 1 = TX window, 2 = idle gap, 3 = fox (F reverses); same keys as during TX. + FOXHUNT_BeaconKey(kbd.current, gWasFKeyPressed ? -1 : 1); break; } + + gWasFKeyPressed = false; // any non-F key consumes (or cancels) the reverse arm } // One iteration of the beacon loop: either transmit a burst, or count down the @@ -1203,6 +1265,7 @@ void APP_RunFoxHunt(void) // 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 foxRunning = true; while (foxRunning) { @@ -1261,6 +1324,8 @@ void APP_RunFoxHunt(void) // and the sleep auto power-off, both of which just clear foxRunning). 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(); diff --git a/App/bitmaps.c b/App/bitmaps.c index a6c617de..141a7026 100644 --- a/App/bitmaps.c +++ b/App/bitmaps.c @@ -392,9 +392,41 @@ const uint8_t BITMAP_FoxHuntBars[11] = const uint8_t BITMAP_FoxHuntGraph[15] = { // sine wave = signal-history gauge mode (bit0 = top row) - 0b00001000, 0b00000100, 0b00000010, 0b00000100, 0b00001000, - 0b00010000, 0b00100000, 0b00010000, 0b00001000, 0b00000100, - 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000 + 0b00001000, + 0b00000100, + 0b00000010, + 0b00000100, + 0b00001000, + 0b00010000, + 0b00100000, + 0b00010000, + 0b00001000, + 0b00000100, + 0b00000010, + 0b00000100, + 0b00001000, + 0b00010000, + 0b00100000 +}; + +const uint8_t BITMAP_FoxHuntTx[16] = +{ + 0b00011100, + 0b00100010, + 0b01000001, + 0b00011100, + 0b00100010, + 0b00000000, + 0b00001000, + 0b00011100, + 0b00011100, + 0b00001000, + 0b00000000, + 0b00100010, + 0b00011100, + 0b01000001, + 0b00100010, + 0b00011100 }; #endif diff --git a/App/bitmaps.h b/App/bitmaps.h index 7fa04f59..f64c332e 100644 --- a/App/bitmaps.h +++ b/App/bitmaps.h @@ -53,6 +53,7 @@ extern const uint8_t BITMAP_NOAA[12]; extern const uint8_t BITMAP_FoxHuntFlat[11]; extern const uint8_t BITMAP_FoxHuntBars[11]; extern const uint8_t BITMAP_FoxHuntGraph[15]; + extern const uint8_t BITMAP_FoxHuntTx[16]; #endif #ifndef ENABLE_CUSTOM_MENU_LAYOUT