From e2d21c61c2ef792571c6103eeddb82a9e99cc00f Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Thu, 9 Jul 2026 05:12:57 +0200 Subject: [PATCH] Add optional RX/TX log streaming to K5Viewer --- App/CMakeLists.txt | 5 ++ App/app/rxtx_log.c | 126 ++++++++++++++++++++++++++++++++++++++++++ App/app/rxtx_log.h | 33 ++++++++++- App/driver/keyboard.c | 34 +++++++++++- App/driver/keyboard.h | 12 +++- App/driver/vcp.c | 14 +++-- App/screenshot.c | 120 +++++++++++++++++++++++++++++++--------- CMakePresets.json | 2 + 8 files changed, 309 insertions(+), 37 deletions(-) diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index 4fc9cde4..e5060527 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -211,6 +211,11 @@ enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG app/rxtx_log.c ) enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP) +enable_feature(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER) + +if(ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER AND NOT (ENABLE_FEAT_F4HWN_RXTX_LOG AND ENABLE_FEAT_F4HWN_SCREENSHOT)) + message(FATAL_ERROR "ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER requires ENABLE_FEAT_F4HWN_RXTX_LOG (log data) and ENABLE_FEAT_F4HWN_SCREENSHOT (serial transport).") +endif() if(ENABLE_FEAT_F4HWN_BEAM AND NOT ENABLE_AIRCOPY) message(FATAL_ERROR "ENABLE_FEAT_F4HWN_BEAM requires ENABLE_AIRCOPY (it reuses g_FSK_Buffer, AIRCOPY_Obfuscate and the FSK packet plumbing).") diff --git a/App/app/rxtx_log.c b/App/app/rxtx_log.c index cb13ff0d..d8d08005 100644 --- a/App/app/rxtx_log.c +++ b/App/app/rxtx_log.c @@ -81,6 +81,13 @@ static_assert(RXTX_LOG_VIEW_ANCHOR_COUNT <= 32); // the copied prefix. static_assert(RXTX_LOG_ENTRY_COPY_SIZE == offsetof(RXTX_LogEntry_t, battVolt) + sizeof(((RXTX_LogEntry_t *)0)->battVolt)); static_assert(sizeof(RXTX_LogEntry_t) >= RXTX_LOG_ENTRY_COPY_SIZE); +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +// Both sizes are hardcoded in k5viewer.js (RF_LOG_ROW_SIZE and +// RF_LOG_PACKET_SIZE): pin them so a struct change breaks the build +// instead of the viewer. +static_assert(sizeof(RXTX_LogK5ViewerRow_t) == 25); +static_assert(RXTX_LOG_K5VIEWER_PACKET_SIZE == 1629); +#endif static RXTX_LogEntry_t gViewCache[RXTX_LOG_VIEW_CACHE_COUNT]; static uint16_t gViewCacheStart; @@ -728,6 +735,125 @@ static bool RXTX_LOG_GetFilteredEntry(uint16_t indexFromNewest, RXTX_LogEntry_t return true; } +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +static uint32_t RXTX_LOG_K5ViewerMix(uint32_t hash, uint32_t value) +{ + hash ^= value & 0xFFu; + hash *= 16777619u; + hash ^= (value >> 8) & 0xFFu; + hash *= 16777619u; + hash ^= (value >> 16) & 0xFFu; + hash *= 16777619u; + hash ^= (value >> 24) & 0xFFu; + hash *= 16777619u; + return hash; +} + +uint32_t RXTX_LOG_K5ViewerSignature(void) +{ + uint32_t hash = 2166136261u; + + hash = RXTX_LOG_K5ViewerMix(hash, gSessionActive); + hash = RXTX_LOG_K5ViewerMix(hash, gClearActive); + hash = RXTX_LOG_K5ViewerMix(hash, gLogHasTraffic); + hash = RXTX_LOG_K5ViewerMix(hash, gNextTrafficSequence); + hash = RXTX_LOG_K5ViewerMix(hash, gSessionFlags); + hash = RXTX_LOG_K5ViewerMix(hash, gSessionFrequency); + hash = RXTX_LOG_K5ViewerMix(hash, gSessionChannel); + // Mix the exported seconds, not the raw ticks: two consecutive ticks + // map to the same durationSeconds, hashing them would resend an + // identical packet every 500 ms during an active session. + hash = RXTX_LOG_K5ViewerMix(hash, (uint32_t)((gSessionTicks500ms + 1u) / 2u)); + hash = RXTX_LOG_K5ViewerMix(hash, gSessionSMeter); + hash = RXTX_LOG_K5ViewerMix(hash, gSessionBattVolt); + + return hash; +} + +static void RXTX_LOG_SetK5ViewerChannelName(RXTX_LogK5ViewerRow_t *row, uint16_t channel) +{ + memset(row->channelName, 0, sizeof(row->channelName)); + + if (channel == RXTX_LOG_CHANNEL_NONE) + return; + + char name[RXTX_LOG_K5VIEWER_NAME_LENGTH + 1u]; + SETTINGS_FetchChannelName(name, channel); + for (uint8_t i = 0; i < RXTX_LOG_K5VIEWER_NAME_LENGTH && name[i] != 0; i++) + row->channelName[i] = name[i]; +} + +static void RXTX_LOG_CopyK5ViewerRow(RXTX_LogK5ViewerRow_t *dst, const RXTX_LogFlashEntry_t *src) +{ + dst->frequency = src->frequency; + dst->trafficSeq = src->trafficSeq; + dst->durationSeconds = src->durationSeconds; + dst->channel = src->channel; + dst->flags = src->flags; + dst->meter = src->sMeter; + dst->battVolt = src->battVolt; + RXTX_LOG_SetK5ViewerChannelName(dst, src->channel); +} + +// Stream the whole packet through `send` without ever holding it in RAM: +// peak stack stays at one row plus one flash entry. The row area is always +// full-length; rowCount is not known before scanning, so the header +// announces every slot and padding rows are all-zero (the viewer already +// skips rows with frequency == 0). +void RXTX_LOG_SendK5ViewerPacket(void (*send)(const uint8_t *data, uint16_t size)) +{ + RXTX_LogK5ViewerRow_t row; + uint8_t rowsSent = 0; + + uint8_t header[4] = {RXTX_LOG_K5VIEWER_VERSION, 0, RXTX_LOG_K5VIEWER_ROW_COUNT, 0}; + if (gSessionActive) + header[1] |= RXTX_LOG_K5VIEWER_STATUS_ACTIVE; + if (gLogHasTraffic) + header[1] |= RXTX_LOG_K5VIEWER_STATUS_HAS_TRAFFIC; + if (gClearActive) + header[1] |= RXTX_LOG_K5VIEWER_STATUS_CLEARING; + send(header, sizeof(header)); + + row.frequency = gSessionFrequency; + row.trafficSeq = gNextTrafficSequence; + row.durationSeconds = (gSessionTicks500ms + 1u) / 2u; + row.channel = gSessionChannel; + row.flags = gSessionFlags; + row.meter = gSessionSMeter; + row.battVolt = gSessionBattVolt; + RXTX_LOG_SetK5ViewerChannelName(&row, gSessionChannel); + send((const uint8_t *)&row, sizeof(row)); + + if (gLogHasTraffic) { + uint16_t slot = RXTX_LOG_AddressToSlot(gNextFlashAddress); + for (uint16_t scanned = 0; + scanned < RXTX_LOG_SLOT_COUNT && rowsSent < RXTX_LOG_K5VIEWER_ROW_COUNT; + scanned++) + { + RXTX_LogFlashEntry_t flashEntry; + + slot = RXTX_LOG_PreviousSlot(slot); + PY25Q16_ReadBuffer(RXTX_LOG_SlotToAddress(slot), &flashEntry, sizeof(flashEntry)); + + if (RXTX_LOG_IsBlankFlashEntry(&flashEntry)) + break; + + if (!RXTX_LOG_IsValidFlashEntry(&flashEntry) || + !RXTX_LOG_IsTrafficFlags(flashEntry.flags)) + continue; + + RXTX_LOG_CopyK5ViewerRow(&row, &flashEntry); + send((const uint8_t *)&row, sizeof(row)); + rowsSent++; + } + } + + memset(&row, 0, sizeof(row)); + for (; rowsSent < RXTX_LOG_K5VIEWER_ROW_COUNT; rowsSent++) + send((const uint8_t *)&row, sizeof(row)); +} +#endif + static void RXTX_LOG_CaptureSession(uint8_t flags, const VFO_Info_t *vfo) { if (gClearActive) diff --git a/App/app/rxtx_log.h b/App/app/rxtx_log.h index 94d47f1f..0a9ca573 100644 --- a/App/app/rxtx_log.h +++ b/App/app/rxtx_log.h @@ -21,8 +21,8 @@ // (rxtx_log.c) so both layouts match byte-for-byte up to and including // battVolt, copied in one pass. Scan-only fields (sequence) sit past the // copied prefix in the flash layout and are not cached in RAM. -// The channel name is not stored: it is resolved from `channel` at display -// time via SETTINGS_FetchChannelName. +// The channel name is not stored in flash: it is resolved from `channel` when +// exporting the K5Viewer packet. typedef struct { uint32_t frequency; uint32_t trafficSeq; @@ -33,6 +33,35 @@ typedef struct { uint8_t battVolt; } RXTX_LogEntry_t; +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +#define RXTX_LOG_K5VIEWER_VERSION 2u +#define RXTX_LOG_K5VIEWER_ROW_COUNT 64u +#define RXTX_LOG_K5VIEWER_NAME_LENGTH 10u +#define RXTX_LOG_K5VIEWER_STATUS_ACTIVE (1u << 0) +#define RXTX_LOG_K5VIEWER_STATUS_HAS_TRAFFIC (1u << 1) +#define RXTX_LOG_K5VIEWER_STATUS_CLEARING (1u << 2) + +typedef struct __attribute__((packed)) { + uint32_t frequency; + uint32_t trafficSeq; + uint16_t durationSeconds; + uint16_t channel; + uint8_t flags; + uint8_t meter; + uint8_t battVolt; + char channelName[RXTX_LOG_K5VIEWER_NAME_LENGTH]; +} RXTX_LogK5ViewerRow_t; + +// On-wire packet layout: 4-byte header (version, status, rowCount, +// reserved), one live row, then RXTX_LOG_K5VIEWER_ROW_COUNT row slots, +// zero-padded past the last valid row. The packet is streamed row by row +// and never built whole in RAM, so only its size is defined here. +#define RXTX_LOG_K5VIEWER_PACKET_SIZE (4u + ((RXTX_LOG_K5VIEWER_ROW_COUNT + 1u) * sizeof(RXTX_LogK5ViewerRow_t))) + +uint32_t RXTX_LOG_K5ViewerSignature(void); +void RXTX_LOG_SendK5ViewerPacket(void (*send)(const uint8_t *data, uint16_t size)); +#endif + void RXTX_LOG_Init(void); void RXTX_LOG_BeginRx(const VFO_Info_t *vfo, FUNCTION_Type_t function); void RXTX_LOG_BeginTx(const VFO_Info_t *vfo); diff --git a/App/driver/keyboard.c b/App/driver/keyboard.c index 0634ac1e..b8a8d983 100644 --- a/App/driver/keyboard.c +++ b/App/driver/keyboard.c @@ -39,8 +39,14 @@ bool gWasFKeyPressed = false; // Packet types for serial key injection (K5Viewer → radio) #define SERIAL_KEY_TYPE 0x03 #define SERIAL_KEY_TYPE_LONG 0x04 +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +#define SERIAL_FEATURE_TYPE 0x05 +#endif volatile KEY_Code_t gKeyFromSerial = KEY_INVALID; +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +volatile uint8_t gSerialViewerFeatures = 0; +#endif static uint8_t gSerialKeyHoldCount = 0; static uint8_t gSerialKeyLong = 0; // 0 = short press, 1 = long press @@ -71,13 +77,35 @@ bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b) break; case STATE_KA_2: +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + if (b == 0x00) + *state = STATE_KA_3; + else if (b == SERIAL_FEATURE_TYPE) + *state = STATE_KA_FEATURE; + else + *state = STATE_IDLE; +#else *state = (b == 0x00) ? STATE_KA_3 : STATE_IDLE; +#endif break; - + case STATE_KA_3: - if (b == 0x00) connected = true; + if (b == 0x00) { +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + gSerialViewerFeatures = 0; +#endif + connected = true; + } *state = STATE_IDLE; break; + +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + case STATE_KA_FEATURE: + gSerialViewerFeatures = b; + connected = true; + *state = STATE_IDLE; + break; +#endif case STATE_KEY_1: *state = (b == 0x55) ? STATE_KEY_2 : STATE_IDLE; @@ -265,4 +293,4 @@ KEY_Code_t KEYBOARD_GetKey(void) void HideFKeyIcon(void) { gWasFKeyPressed = false; gUpdateStatus = true; -} \ No newline at end of file +} diff --git a/App/driver/keyboard.h b/App/driver/keyboard.h index 3fe092a9..7450308e 100644 --- a/App/driver/keyboard.h +++ b/App/driver/keyboard.h @@ -50,6 +50,9 @@ typedef enum { STATE_KA_1, STATE_KA_2, STATE_KA_3, +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + STATE_KA_FEATURE, +#endif STATE_KEY_1, STATE_KEY_2, STATE_KEY_3, @@ -65,6 +68,13 @@ extern bool gWasFKeyPressed; // Serial-injected key (written by UART/VCP parser, consumed by KEYBOARD_Poll). extern volatile KEY_Code_t gKeyFromSerial; +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +#define SERIAL_VIEWER_FEATURE_RF_LOG 0x01u + +// Extension flags announced by the viewer's feature keepalive. +extern volatile uint8_t gSerialViewerFeatures; +#endif + bool KEYBOARD_ProcessProtocolByte(ParseState_t *state, uint8_t b); #endif @@ -73,4 +83,4 @@ KEY_Code_t KEYBOARD_GetKey(void); void HideFKeyIcon(void); -#endif \ No newline at end of file +#endif diff --git a/App/driver/vcp.c b/App/driver/vcp.c index 021b6359..e514ea73 100644 --- a/App/driver/vcp.c +++ b/App/driver/vcp.c @@ -46,15 +46,19 @@ void VCP_Init() bool VCP_ScreenshotPing(void) { // State machine for parsing incoming packets: - // Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive - // Short key press: 0xAA 0x55 0x03 → inject short press - // Long key press: 0xAA 0x55 0x04 → inject long press + // Keepalive: 0x55 0xAA 0x00 0x00 → viewer alive, no extensions + // Feature keepalive (ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER builds only): + // 0x55 0xAA 0x05 → viewer alive, extensions enabled + // flags bit 0 = RF log stream + // Short key press: 0xAA 0x55 0x03 → inject short press + // Long key press: 0xAA 0x55 0x04 → inject long press // // State transitions: // IDLE → 0x55 → KA_1 // KA_1 → 0xAA → KA_2 (else IDLE) - // KA_2 → 0x00 → KA_3 (else IDLE) - // KA_3 → 0x00 → keepalive OK, IDLE + // KA_2 → 0x00 → KA_3 (else 0x05 → KA_FEATURE, else IDLE) + // KA_3 → 0x00 → keepalive OK, clear extensions, IDLE + // KA_FEATURE → → keepalive OK, set extensions, IDLE // // IDLE → 0xAA → KEY_1 // KEY_1 → 0x55 → KEY_2 (else IDLE) diff --git a/App/screenshot.c b/App/screenshot.c index cbd265e0..1fb09b50 100644 --- a/App/screenshot.c +++ b/App/screenshot.c @@ -21,6 +21,9 @@ #include "driver/vcp.h" #include "driver/keyboard.h" #include "driver/bk4819.h" +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +#include "app/rxtx_log.h" +#endif // SRAM optimization: minimize static allocations // - previousHash: one fingerprint per 8-byte chunk instead of a full @@ -41,6 +44,10 @@ static uint8_t forcedBlock = 0; static uint8_t keepAlive = 3; static bool hasConnectionPing = false; static bool wasConnected = false; +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +static uint32_t previousRfLogSignature = 0; +static bool rfLogSent = false; +#endif // FNV-1a over one 8-byte chunk, folded to 16 bits static uint16_t SCREENSHOT_Hash(const uint8_t *data) @@ -83,6 +90,8 @@ enum { SCREENSHOT_CHUNKS_PER_LINE = 16, SCREENSHOT_HALF_LINE_COLUMNS = LCD_WIDTH / 2, SCREENSHOT_MARKER_BASE = 0xF0, + SCREENSHOT_TYPE_DIFF = 0x02, + SCREENSHOT_TYPE_RXTX_LOG = 0x05, SCREENSHOT_FLAG_DEEP_SLEEP = 1 << 0, SCREENSHOT_FLAG_LED_RED = 1 << 1, SCREENSHOT_FLAG_LED_GREEN = 1 << 2, @@ -106,11 +115,48 @@ static uint8_t SCREENSHOT_StateFlags(void) return flags; } +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER +static bool SCREENSHOT_HasPendingRfLogUpdate(void) +{ + if ((gSerialViewerFeatures & SERIAL_VIEWER_FEATURE_RF_LOG) == 0) + return false; + + return !rfLogSent || RXTX_LOG_K5ViewerSignature() != previousRfLogSignature; +} + +static void SCREENSHOT_SendRfLog(void) +{ + // Capture the signature before streaming: a state change landing + // during the blocking send still differs afterwards and triggers a + // resend on the next cycle. + previousRfLogSignature = RXTX_LOG_K5ViewerSignature(); + rfLogSent = true; + + const uint16_t len = RXTX_LOG_K5VIEWER_PACKET_SIZE; + uint8_t header[5] = { + 0xAA, 0x55, SCREENSHOT_TYPE_RXTX_LOG, + (uint8_t)(len >> 8), + (uint8_t)(len & 0xFF) + }; + + SCREENSHOT_Send(header, 5); + RXTX_LOG_SendK5ViewerPacket(SCREENSHOT_Send); + + uint8_t end = 0x0A; + SCREENSHOT_Send(&end, 1); +} +#endif + bool SCREENSHOT_HasPendingStateChange(void) { if (gUART_LockScreenshot > 0 || keepAlive == 0 || !hasConnectionPing) return false; +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + if (SCREENSHOT_HasPendingRfLogUpdate()) + return true; +#endif + return !wasConnected || SCREENSHOT_StateFlags() != previousStateFlags; } @@ -146,6 +192,10 @@ void SCREENSHOT_Update(bool force) wasConnected = false; hasConnectionPing = false; previousStateFlags = 0xFF; +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + gSerialViewerFeatures = 0; + rfLogSent = false; +#endif return; } } else { @@ -159,6 +209,11 @@ void SCREENSHOT_Update(bool force) const uint8_t stateFlags = SCREENSHOT_StateFlags(); const bool stateChanged = (stateFlags != previousStateFlags); +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + const bool rfLogPending = SCREENSHOT_HasPendingRfLogUpdate(); +#else + const bool rfLogPending = false; +#endif // ==== FIRST PASS: Count changed chunks ==== uint16_t deltaLen = 0; @@ -179,7 +234,7 @@ void SCREENSHOT_Update(bool force) forcedBlock = (forcedBlock + 1) % 128; - if (deltaLen == 0 && !stateChanged) + if (deltaLen == 0 && !stateChanged && !rfLogPending) return; // Skip transmission if a key is currently pressed @@ -187,39 +242,52 @@ void SCREENSHOT_Update(bool force) if (gKeyReading0 != KEY_INVALID) return; - // ==== Send version marker and state flags ==== - // 0xF0 keeps a resync-safe marker before the standard AA 55 header. - uint8_t versionMarker = SCREENSHOT_MARKER_BASE | stateFlags; - SCREENSHOT_Send(&versionMarker, 1); +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + // A pending RF log packet may be the only thing to send: skip the + // frame section when it carries nothing. Without the RF log stream + // the early return above already guarantees this condition. + if (deltaLen != 0 || stateChanged) +#endif + { + // ==== Send version marker and state flags ==== + // 0xF0 keeps a resync-safe marker before the standard AA 55 header. + uint8_t versionMarker = SCREENSHOT_MARKER_BASE | stateFlags; + SCREENSHOT_Send(&versionMarker, 1); - // ==== Send header ==== - uint8_t header[5] = { - 0xAA, 0x55, 0x02, - (uint8_t)(deltaLen >> 8), - (uint8_t)(deltaLen & 0xFF) - }; + // ==== Send header ==== + uint8_t header[5] = { + 0xAA, 0x55, SCREENSHOT_TYPE_DIFF, + (uint8_t)(deltaLen >> 8), + (uint8_t)(deltaLen & 0xFF) + }; - SCREENSHOT_Send(header, 5); + SCREENSHOT_Send(header, 5); - // ==== SECOND PASS: Send only changed chunks ==== - for (uint8_t chunkIdx = 0; chunkIdx < 128; chunkIdx++) { - if (!(changedBitmap[chunkIdx >> 3] & (1 << (chunkIdx & 7)))) - continue; + // ==== SECOND PASS: Send only changed chunks ==== + for (uint8_t chunkIdx = 0; chunkIdx < 128; chunkIdx++) { + if (!(changedBitmap[chunkIdx >> 3] & (1 << (chunkIdx & 7)))) + continue; - chunk[0] = chunkIdx; - SCREENSHOT_Chunk(chunkIdx, &chunk[1]); + chunk[0] = chunkIdx; + SCREENSHOT_Chunk(chunkIdx, &chunk[1]); - SCREENSHOT_Send(chunk, 9); + SCREENSHOT_Send(chunk, 9); - // Update the fingerprint only once the chunk is actually sent, - // so chunks skipped by an early return stay marked as changed. - // Hashing the recomputed payload also keeps the fingerprint in - // sync if the display buffer changed between the two passes. - previousHash[chunkIdx] = SCREENSHOT_Hash(&chunk[1]); + // Update the fingerprint only once the chunk is actually sent, + // so chunks skipped by an early return stay marked as changed. + // Hashing the recomputed payload also keeps the fingerprint in + // sync if the display buffer changed between the two passes. + previousHash[chunkIdx] = SCREENSHOT_Hash(&chunk[1]); + } + + uint8_t end = 0x0A; + SCREENSHOT_Send(&end, 1); } - uint8_t end = 0x0A; - SCREENSHOT_Send(&end, 1); +#ifdef ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER + if (rfLogPending) + SCREENSHOT_SendRfLog(); +#endif previousStateFlags = stateFlags; wasConnected = true; diff --git a/CMakePresets.json b/CMakePresets.json index cf7a5797..43895a67 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -74,6 +74,7 @@ "ENABLE_FEAT_F4HWN_BEAM": 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_AGC_SHOW_DATA": false, @@ -207,6 +208,7 @@ "ENABLE_FEAT_F4HWN_BEAM": true, "ENABLE_FEAT_F4HWN_RXTX_LOG": true, "ENABLE_FEAT_F4HWN_RXTX_LOG_WRAP": false, + "ENABLE_FEAT_F4HWN_RXTX_LOG_K5VIEWER": false, "ENABLE_FEAT_F4HWN_QRCODE": true, "ENABLE_FEAT_F4HWN_MEM": true, "ENABLE_FEAT_F4HWN_LOGO": true,