#include "platform.h"
#include "subghz_phy_app.h"
#include "radio.h"
#include "usart_if.h"
#include "usart.h"
#include "main.h"
#include "timer.h"

#include "config/config_consts.h"
#include "config/config_types.h"
#include "config/config_defaults.h"
#include "config/config_store.h"

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdbool.h>


typedef enum
{
  APP_MODE_DATA = 0,
  APP_MODE_CONFIG
} AppMode_t;

typedef struct
{
  uint8_t data[RADIO_MAX_PAYLOAD_SIZE];
  uint8_t len;
} TxPacket_t;

typedef struct
{
  uint8_t active;
  uint8_t count;
  uint8_t bytes[3];
  uint32_t start_tick;
  uint32_t last_tick;
} EscapeDetector_t;

static RadioEvents_t RadioEvents;
static BridgeConfig_t g_cfg;
static volatile uint8_t g_radio_tx_done = 0;
static volatile uint8_t g_radio_tx_timeout = 0;
static volatile uint8_t g_radio_rx_done = 0;
static volatile uint8_t g_radio_rx_timeout = 0;
static volatile uint8_t g_radio_rx_error = 0;
static volatile int16_t g_last_rx_rssi = 0;
static volatile int8_t g_last_rx_cfo = 0;

static TimerEvent_t g_led_tx_timer;
static TimerEvent_t g_led_rx_timer;
static TimerEvent_t g_led_err_timer;

static volatile uint8_t g_radio_busy = 0;
static volatile uint8_t g_radio_needs_rx_restart = 0;

static uint8_t g_rx_payload[RADIO_MAX_PAYLOAD_SIZE];
static uint16_t g_rx_payload_len = 0;

static TxPacket_t g_tx_queue[TX_QUEUE_DEPTH];
static uint8_t g_tx_q_head = 0;
static uint8_t g_tx_q_tail = 0;
static uint8_t g_tx_q_count = 0;

static uint8_t g_uart_build_buf[UART_DATA_BUFFER_SIZE];
static uint16_t g_uart_build_len = 0;
static uint32_t g_uart_last_data_tick = 0;

static EscapeDetector_t g_escape = {0};
static AppMode_t g_mode = APP_MODE_DATA;

static char g_cfg_line[CONFIG_LINE_SIZE];
static uint16_t g_cfg_line_len = 0;

static uint32_t g_stat_uart_packets_tx = 0;
static uint32_t g_stat_uart_bytes_tx = 0;
static uint32_t g_stat_radio_packets_rx = 0;
static uint32_t g_stat_radio_bytes_rx = 0;
static uint32_t g_stat_queue_overflow = 0;

static void OnTxDone(void);
static void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t cfo);
static void OnTxTimeout(void);
static void OnRxTimeout(void);
static void OnRxError(void);

static void App_LedTxPulse(void);
static void App_LedRxPulse(void);
static void App_LedErrPulse(void);

static void App_LedTxOff(void *context);
static void App_LedRxOff(void *context);
static void App_LedErrOff(void *context);

static void UartRxByteCallback(uint8_t *rxChar, uint16_t size, uint8_t error);
static void App_ProcessRadioEvents(void);
static void App_ProcessUartPacketizer(void);
static void App_ProcessEscape(void);
static void App_StartNextTxIfPossible(void);
static void App_RadioEnterRx(void);
static void App_RadioApplyConfig(void);
static void App_RadioConfigureRx(void);
static void App_RadioConfigureTx(void);
static void App_EnterConfigMode(void);
static void App_ExitConfigMode(void);
static void App_ResetDataPath(void);
static void App_DataModeFeedByte(uint8_t ch, uint32_t now);
static void App_DataModeFlushBuilder(void);
static uint8_t App_QueuePush(const uint8_t *data, uint16_t len);
static void App_QueuePop(void);
static void App_ConfigFeedByte(uint8_t ch);
static void App_ConfigExecuteLine(char *line);
static void App_PrintConfigPrompt(void);
static void App_PrintHelp(void);
static void App_PrintStatus(void);
static void App_Printf(const char *fmt, ...);
static void App_Write(const uint8_t *data, uint16_t len);
static void App_ReconfigureUart(uint32_t baudrate);
static uint8_t App_ParseHexSyncWord(const char *text, uint8_t out[3]);
static char *App_SkipSpaces(char *s);

void SubghzApp_Init(void)
{
  if (!Config_Load(&g_cfg))
  {
    Config_LoadDefaults(&g_cfg);
  }

  RadioEvents.TxDone = OnTxDone;
  RadioEvents.RxDone = OnRxDone;
  RadioEvents.TxTimeout = OnTxTimeout;
  RadioEvents.RxTimeout = OnRxTimeout;
  RadioEvents.RxError = OnRxError;

  Radio.Init(&RadioEvents);

  TimerInit(&g_led_tx_timer, App_LedTxOff);
  TimerInit(&g_led_rx_timer, App_LedRxOff);
  TimerInit(&g_led_err_timer, App_LedErrOff);

  App_RadioApplyConfig();
  App_ReconfigureUart(g_cfg.uart_baudrate);
  App_RadioEnterRx();

  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET);

  g_uart_last_data_tick = HAL_GetTick();
  (void)vcom_ReceiveInit(UartRxByteCallback);

  App_Printf("\r\nSTM32WL UART<->SUBGHZ bridge started\r\n");
  App_Printf("DATA mode, escape sequence: silence 800 ms + +++ + silence 800 ms\r\n");
}

void SubghzApp_Process(void)
{
  App_ProcessEscape();
  App_ProcessUartPacketizer();
  App_ProcessRadioEvents();
  App_StartNextTxIfPossible();
}

static void App_ProcessRadioEvents(void)
{
  if (g_radio_tx_done != 0U)
  {
    g_radio_tx_done = 0U;
    g_radio_busy = 0U;
    g_stat_uart_packets_tx++;
    App_QueuePop();
    g_radio_needs_rx_restart = 1U;
  }

  if (g_radio_tx_timeout != 0U)
  {
    g_radio_tx_timeout = 0U;
    g_radio_busy = 0U;
    App_LedErrPulse();
    App_QueuePop();
    App_Printf("\r\n[WARN] radio tx timeout\r\n");
    g_radio_needs_rx_restart = 1U;
  }

  if (g_radio_rx_done != 0U)
  {
    g_radio_rx_done = 0U;
    App_LedRxPulse();
    g_stat_radio_packets_rx++;
    g_stat_radio_bytes_rx += g_rx_payload_len;

    if (g_mode == APP_MODE_DATA)
    {
      App_Write(g_rx_payload, g_rx_payload_len);
    }

    g_radio_needs_rx_restart = 1U;
  }

  if ((g_radio_rx_timeout != 0U) || (g_radio_rx_error != 0U))
  {
    g_radio_rx_timeout = 0U;
    g_radio_rx_error = 0U;
    App_LedErrPulse();
    g_radio_needs_rx_restart = 1U;
  }

  if ((g_radio_needs_rx_restart != 0U) && (g_radio_busy == 0U) && (g_tx_q_count == 0U))
  {
    g_radio_needs_rx_restart = 0U;
    App_RadioEnterRx();
  }
}

static void App_ProcessUartPacketizer(void)
{
  uint32_t now = HAL_GetTick();

  if (g_mode != APP_MODE_DATA)
  {
    return;
  }

  if ((g_uart_build_len > 0U) &&
      ((now - g_uart_last_data_tick) >= g_cfg.uart_packet_timeout_ms) &&
      (g_escape.active == 0U))
  {
    App_DataModeFlushBuilder();
  }
}

static void App_ProcessEscape(void)
{
  uint32_t now = HAL_GetTick();
  uint8_t i;

  if ((g_mode != APP_MODE_DATA) || (g_escape.active == 0U))
  {
    return;
  }

  if ((g_escape.count == 3U) && ((now - g_escape.last_tick) >= CONFIG_ESCAPE_GUARD_MS))
  {
    g_escape.active = 0U;
    g_escape.count = 0U;
    App_EnterConfigMode();
    return;
  }

  if ((g_escape.count < 3U) && ((now - g_escape.last_tick) >= CONFIG_ESCAPE_GUARD_MS))
  {
    for (i = 0U; i < g_escape.count; i++)
    {
      App_DataModeFeedByte(g_escape.bytes[i], now);
    }
    g_escape.active = 0U;
    g_escape.count = 0U;
  }
}

static void App_StartNextTxIfPossible(void)
{
  if ((g_mode != APP_MODE_DATA) || (g_radio_busy != 0U) || (g_tx_q_count == 0U))
  {
    return;
  }

  App_RadioConfigureTx();
  g_radio_busy = 1U;
  App_LedTxPulse();
  (void)Radio.Send(g_tx_queue[g_tx_q_head].data, g_tx_queue[g_tx_q_head].len);
}
static void App_ApplyConfig(void)
{
  App_RadioApplyConfig();
  App_RadioConfigureRx();
  App_RadioConfigureTx();
  if (!Config_Save(&g_cfg)) {
    App_Printf("Error while saving cnf\r\n");
  }
}


static void App_RadioApplyConfig(void)
{
  Radio.SetChannel(g_cfg.rf_frequency);
  g_radio_needs_rx_restart = 1U;
}

static void App_RadioConfigureRx(void)
{
  RxConfigGeneric_t rx = {0};

  Radio.SetChannel(g_cfg.rf_frequency);

  rx.fsk.ModulationShaping = RADIO_FSK_MOD_SHAPING_G_BT_05;
  rx.fsk.Bandwidth = g_cfg.fsk_bandwidth;
  rx.fsk.BitRate = g_cfg.fsk_bitrate;
  rx.fsk.PreambleLen = g_cfg.fsk_preamble_len;
  rx.fsk.SyncWordLength = RADIO_SYNCWORD_LEN;
  rx.fsk.PreambleMinDetect = RADIO_FSK_PREAMBLE_DETECTOR_08_BITS;
  rx.fsk.SyncWord = g_cfg.syncword;
  rx.fsk.whiteSeed = RADIO_WHITENING_SEED;
  rx.fsk.LengthMode = RADIO_FSK_PACKET_VARIABLE_LENGTH;
  rx.fsk.CrcLength = RADIO_FSK_CRC_2_BYTES_IBM;
  rx.fsk.CrcPolynomial = RADIO_CRC_POLY;
  rx.fsk.CrcSeed = RADIO_CRC_SEED;
  rx.fsk.Whitening = RADIO_FSK_DC_FREEWHITENING;
  rx.fsk.MaxPayloadLength = RADIO_MAX_PAYLOAD_SIZE;
  rx.fsk.StopTimerOnPreambleDetect = 0;
  rx.fsk.AddrComp = RADIO_FSK_ADDRESSCOMP_FILT_OFF;

  Radio.Standby();
  if (0UL != Radio.RadioSetRxGenericConfig(GENERIC_FSK, &rx, RX_CONTINUOUS_ON, 0U))
  {
    Error_Handler();
  }
}

static void App_RadioConfigureTx(void)
{
  TxConfigGeneric_t tx = {0};

  Radio.SetChannel(g_cfg.rf_frequency);

  tx.fsk.ModulationShaping = RADIO_FSK_MOD_SHAPING_G_BT_05;
  tx.fsk.FrequencyDeviation = g_cfg.fsk_fdev;
  tx.fsk.BitRate = g_cfg.fsk_bitrate;
  tx.fsk.PreambleLen = g_cfg.fsk_preamble_len;
  tx.fsk.SyncWordLength = RADIO_SYNCWORD_LEN;
  tx.fsk.SyncWord = g_cfg.syncword;
  tx.fsk.whiteSeed = RADIO_WHITENING_SEED;
  tx.fsk.HeaderType = RADIO_FSK_PACKET_VARIABLE_LENGTH;
  tx.fsk.CrcLength = RADIO_FSK_CRC_2_BYTES_IBM;
  tx.fsk.CrcPolynomial = RADIO_CRC_POLY;
  tx.fsk.CrcSeed = RADIO_CRC_SEED;
  tx.fsk.Whitening = RADIO_FSK_DC_FREEWHITENING;

  Radio.Standby();
  if (0UL != Radio.RadioSetTxGenericConfig(GENERIC_FSK, &tx, g_cfg.tx_power, TX_TIMEOUT_VALUE_MS))
  {
    Error_Handler();
  }
}

static void App_RadioEnterRx(void)
{
  App_RadioConfigureRx();
  Radio.Rx(RX_TIMEOUT_VALUE_MS);
}

static void App_EnterConfigMode(void)
{
  App_ResetDataPath();
  g_mode = APP_MODE_CONFIG;
  App_Printf("\r\n\r\n[CONFIG MODE]\r\n");
  App_Printf("type 'help' for commands\r\n");
  if (!Config_Load(&g_cfg)) 
  {
    App_Printf("Error while loading cnf\r\n");
    App_Printf("Cnf was reset to defaults\r\n");
    Config_LoadDefaults(&g_cfg);

  }
  App_PrintConfigPrompt();
}

static void App_ExitConfigMode(void)
{
  g_cfg_line_len = 0U;
  App_ResetDataPath();
  g_mode = APP_MODE_DATA;
  App_Printf("\r\n[DATA MODE]\r\n");
  g_radio_needs_rx_restart = 1U;
}

static void App_ResetDataPath(void)
{
  g_uart_build_len = 0U;
  g_escape.active = 0U;
  g_escape.count = 0U;
  g_tx_q_head = 0U;
  g_tx_q_tail = 0U;
  g_tx_q_count = 0U;
}

static void App_DataModeFeedByte(uint8_t ch, uint32_t now)
{
  if (g_uart_build_len < UART_DATA_BUFFER_SIZE)
  {
    g_uart_build_buf[g_uart_build_len++] = ch;
    g_uart_last_data_tick = now;
    g_stat_uart_bytes_tx++;
  }
  else
  {
    App_DataModeFlushBuilder();
    if (g_uart_build_len < UART_DATA_BUFFER_SIZE)
    {
      g_uart_build_buf[g_uart_build_len++] = ch;
      g_uart_last_data_tick = now;
      g_stat_uart_bytes_tx++;
    }
  }
}

static void App_DataModeFlushBuilder(void)
{
  if (g_uart_build_len == 0U)
  {
    return;
  }

  if (App_QueuePush(g_uart_build_buf, g_uart_build_len) == 0U)
  {
    g_stat_queue_overflow++;
  }
  g_uart_build_len = 0U;
}

static uint8_t App_QueuePush(const uint8_t *data, uint16_t len)
{
  if ((len == 0U) || (len > RADIO_MAX_PAYLOAD_SIZE) || (g_tx_q_count >= TX_QUEUE_DEPTH))
  {
    return 0U;
  }

  memcpy(g_tx_queue[g_tx_q_tail].data, data, len);
  g_tx_queue[g_tx_q_tail].len = (uint8_t)len;
  g_tx_q_tail = (uint8_t)((g_tx_q_tail + 1U) % TX_QUEUE_DEPTH);
  g_tx_q_count++;
  return 1U;
}

static void App_QueuePop(void)
{
  if (g_tx_q_count == 0U)
  {
    return;
  }

  g_tx_q_head = (uint8_t)((g_tx_q_head + 1U) % TX_QUEUE_DEPTH);
  g_tx_q_count--;
}

static void UartRxByteCallback(uint8_t *rxChar, uint16_t size, uint8_t error)
{
  uint8_t ch;
  uint32_t now;
  uint8_t i;

  if ((error != 0U) || (size == 0U) || (rxChar == NULL))
  {
    return;
  }

  ch = rxChar[0];
  now = HAL_GetTick();

  if (g_mode == APP_MODE_CONFIG)
  {
    App_ConfigFeedByte(ch);
    return;
  }

  if (g_escape.active == 0U)
  {
    if (((now - g_uart_last_data_tick) >= CONFIG_ESCAPE_GUARD_MS) && (ch == '+'))
    {
      g_escape.active = 1U;
      g_escape.count = 1U;
      g_escape.bytes[0] = ch;
      g_escape.start_tick = now;
      g_escape.last_tick = now;
      return;
    }

    App_DataModeFeedByte(ch, now);
    return;
  }

  if ((ch == '+') && (g_escape.count < 3U))
  {
    g_escape.bytes[g_escape.count++] = ch;
    g_escape.last_tick = now;
    return;
  }

  for (i = 0U; i < g_escape.count; i++)
  {
    App_DataModeFeedByte(g_escape.bytes[i], now);
  }
  g_escape.active = 0U;
  g_escape.count = 0U;

  if (((now - g_uart_last_data_tick) >= CONFIG_ESCAPE_GUARD_MS) && (ch == '+'))
  {
    g_escape.active = 1U;
    g_escape.count = 1U;
    g_escape.bytes[0] = ch;
    g_escape.start_tick = now;
    g_escape.last_tick = now;
    return;
  }

  App_DataModeFeedByte(ch, now);
}

static void App_ConfigFeedByte(uint8_t ch)
{
  if ((ch == '\r') || (ch == '\n'))
  {
    if (g_cfg_line_len > 0U)
    {
      g_cfg_line[g_cfg_line_len] = '\0';
      App_Printf("\r\n");
      App_ConfigExecuteLine(g_cfg_line);
      g_cfg_line_len = 0U;
    }
    App_PrintConfigPrompt();
    return;
  }

  if ((ch == 0x08U) || (ch == 0x7FU))
  {
    if (g_cfg_line_len > 0U)
    {
      g_cfg_line_len--;
      App_Write((const uint8_t *)"\b \b", 3U);
    }
    return;
  }

  if ((isprint(ch) != 0) && (g_cfg_line_len < (CONFIG_LINE_SIZE - 1U)))
  {
    g_cfg_line[g_cfg_line_len++] = (char)ch;
    App_Write(&ch, 1U);
  }
}

static void App_ConfigExecuteLine(char *line)
{
  char *arg;
  uint32_t u32;
  uint8_t sync[3];

  line = App_SkipSpaces(line);
  if (*line == '\0')
  {
    return;
  }

  if ((strcmp(line, "help") == 0) || (strcmp(line, "?") == 0))
  {
    App_PrintHelp();
    return;
  }

  if ((strcmp(line, "show") == 0) || (strcmp(line, "status") == 0))
  {
    App_PrintStatus();
    return;
  }

  if (strcmp(line, "exit") == 0)
  {
    App_ExitConfigMode();
    return;
  }
  if  (strcmp(line, "save") == 0) 
  {
    App_ApplyConfig();
    return;
  }
  if (strcmp(line, "defaults") == 0)
  {
    Config_LoadDefaults(&g_cfg);
    App_Printf("defaults restored\r\n");
    return;
  }

  if (strncmp(line, "freq ", 5) == 0)
  {
    u32 = strtoul(&line[5], NULL, 10);
    if (u32 < 150000000UL || u32 > 960000000UL)
    {
      App_Printf("bad frequency\r\n");
      return;
    }
    g_cfg.rf_frequency = u32;
    App_Printf("freq=%lu\r\n", (unsigned long)g_cfg.rf_frequency);
    return;
  }

  if (strncmp(line, "power ", 6) == 0)
  {
    long pwr = strtol(&line[6], NULL, 10);
    if ((pwr < -9L) || (pwr > 22L))
    {
      App_Printf("bad power\r\n");
      return;
    }
    g_cfg.tx_power = (int8_t)pwr;
    App_Printf("power=%d\r\n", g_cfg.tx_power);
    return;
  }

  if (strncmp(line, "bitrate ", 8) == 0)
  {
    u32 = strtoul(&line[8], NULL, 10);
    if ((u32 < 600UL) || (u32 > 300000UL))
    {
      App_Printf("bad bitrate\r\n");
      return;
    }
    g_cfg.fsk_bitrate = u32;
    App_Printf("bitrate=%lu\r\n", (unsigned long)g_cfg.fsk_bitrate);
    return;
  }

  if (strncmp(line, "bandwidth ", 10) == 0)
  {
    u32 = strtoul(&line[10], NULL, 10);
    if ((u32 < 2600UL) || (u32 > 250000UL))
    {
      App_Printf("bad bandwidth\r\n");
      return;
    }
    g_cfg.fsk_bandwidth = u32;
    App_Printf("bandwidth=%lu\r\n", (unsigned long)g_cfg.fsk_bandwidth);
    return;
  }

  if (strncmp(line, "fdev ", 5) == 0)
  {
    u32 = strtoul(&line[5], NULL, 10);
    if (u32 > 200000UL)
    {
      App_Printf("bad fdev\r\n");
      return;
    }
    g_cfg.fsk_fdev = u32;
    App_Printf("fdev=%lu\r\n", (unsigned long)g_cfg.fsk_fdev);
    return;
  }

  if (strncmp(line, "preamble ", 9) == 0)
  {
    u32 = strtoul(&line[9], NULL, 10);
    if ((u32 < 2UL) || (u32 > 65535UL))
    {
      App_Printf("bad preamble\r\n");
      return;
    }
    g_cfg.fsk_preamble_len = (uint16_t)u32;
    App_Printf("preamble=%u\r\n", g_cfg.fsk_preamble_len);
    return;
  }

  if (strncmp(line, "timeout ", 8) == 0)
  {
    u32 = strtoul(&line[8], NULL, 10);
    if ((u32 < 1UL) || (u32 > 1000UL))
    {
      App_Printf("bad timeout\r\n");
      return;
    }
    g_cfg.uart_packet_timeout_ms = (uint16_t)u32;
    App_Printf("timeout=%u\r\n", g_cfg.uart_packet_timeout_ms);
    return;
  }

  if (strncmp(line, "uart ", 5) == 0)
  {
    u32 = strtoul(&line[5], NULL, 10);
    if ((u32 < 1200UL) || (u32 > 921600UL))
    {
      App_Printf("bad uart baudrate\r\n");
      return;
    }
    g_cfg.uart_baudrate = u32;
    App_Printf("switching uart to %lu baud\r\n", (unsigned long)g_cfg.uart_baudrate);
    App_ReconfigureUart(g_cfg.uart_baudrate);
    return;
  }

  if (strncmp(line, "sync ", 5) == 0)
  {
    arg = App_SkipSpaces(&line[5]);
    if (App_ParseHexSyncWord(arg, sync) == 0U)
    {
      App_Printf("bad sync, use 6 hex chars, e.g. C194C1\r\n");
      return;
    }
    memcpy(g_cfg.syncword, sync, sizeof(sync));
    App_Printf("sync=%02X%02X%02X\r\n", g_cfg.syncword[0], g_cfg.syncword[1], g_cfg.syncword[2]);
    return;
  }

  App_Printf("unknown command: %s\r\n", line);
}

static void App_PrintConfigPrompt(void)
{
  if (g_mode == APP_MODE_CONFIG)
  {
    App_Printf("cfg> ");
  }
}

static void App_PrintHelp(void)
{
  App_Printf("commands:\r\n");
  App_Printf("  help                 - this help\r\n");
  App_Printf("  show                 - current config and counters\r\n");
  App_Printf("  freq <hz>            - rf frequency\r\n");
  App_Printf("  power <dbm>          - tx power (-9..22)\r\n");
  App_Printf("  bitrate <bps>        - fsk bitrate\r\n");
  App_Printf("  bandwidth <hz>       - fsk rx bandwidth\r\n");
  App_Printf("  fdev <hz>            - fsk frequency deviation\r\n");
  App_Printf("  preamble <bytes>     - fsk preamble length\r\n");
  App_Printf("  sync <hex6>          - 3-byte syncword, example C194C1\r\n");
  App_Printf("  timeout <ms>         - uart silence before rf packet send\r\n");
  App_Printf("  uart <baud>          - change uart baudrate\r\n");
  App_Printf("  save                 - save and apply changes\r\n");
  App_Printf("  defaults             - restore default config\r\n");
  App_Printf("  exit                 - return to transparent bridge mode\r\n");
}

static void App_PrintStatus(void)
{
  App_Printf("mode=%s\r\n", (g_mode == APP_MODE_CONFIG) ? "config" : "data");
  App_Printf("freq=%lu Hz\r\n", (unsigned long)g_cfg.rf_frequency);
  App_Printf("power=%d dBm\r\n", g_cfg.tx_power);
  App_Printf("bitrate=%lu bps\r\n", (unsigned long)g_cfg.fsk_bitrate);
  App_Printf("bandwidth=%lu Hz\r\n", (unsigned long)g_cfg.fsk_bandwidth);
  App_Printf("fdev=%lu Hz\r\n", (unsigned long)g_cfg.fsk_fdev);
  App_Printf("preamble=%u bytes\r\n", g_cfg.fsk_preamble_len);
  App_Printf("sync=%02X%02X%02X\r\n", g_cfg.syncword[0], g_cfg.syncword[1], g_cfg.syncword[2]);
  App_Printf("uart_baud=%lu\r\n", (unsigned long)g_cfg.uart_baudrate);
  App_Printf("uart_pkt_timeout=%u ms\r\n", g_cfg.uart_packet_timeout_ms);
  App_Printf("tx_queue=%u/%u\r\n", g_tx_q_count, TX_QUEUE_DEPTH);
  App_Printf("last_rx_rssi=%d dBm\r\n", (int)g_last_rx_rssi);
  App_Printf("last_rx_cfo=%d\r\n", (int)g_last_rx_cfo);
  App_Printf("stat_uart_packets_tx=%lu\r\n", (unsigned long)g_stat_uart_packets_tx);
  App_Printf("stat_uart_bytes_tx=%lu\r\n", (unsigned long)g_stat_uart_bytes_tx);
  App_Printf("stat_radio_packets_rx=%lu\r\n", (unsigned long)g_stat_radio_packets_rx);
  App_Printf("stat_radio_bytes_rx=%lu\r\n", (unsigned long)g_stat_radio_bytes_rx);
  App_Printf("stat_queue_overflow=%lu\r\n", (unsigned long)g_stat_queue_overflow);
}

static void App_Printf(const char *fmt, ...)
{
  char buffer[192];
  va_list ap;
  int len;

  va_start(ap, fmt);
  len = vsnprintf(buffer, sizeof(buffer), fmt, ap);
  va_end(ap);

  if (len <= 0)
  {
    return;
  }

  if ((size_t)len >= sizeof(buffer))
  {
    len = (int)(sizeof(buffer) - 1U);
  }

  App_Write((const uint8_t *)buffer, (uint16_t)len);
}

static void App_Write(const uint8_t *data, uint16_t len)
{
  if ((data == NULL) || (len == 0U))
  {
    return;
  }

  (void)HAL_UART_Transmit(&huart2, (uint8_t *)data, len, 1000U);
}

static void App_ReconfigureUart(uint32_t baudrate)
{
  huart2.Init.BaudRate = baudrate;

  (void)HAL_UART_AbortReceive(&huart2);

  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_EnableFifoMode(&huart2) != HAL_OK)
  {
    Error_Handler();
  }

  (void)vcom_ReceiveInit(UartRxByteCallback);
}

static uint8_t App_ParseHexSyncWord(const char *text, uint8_t out[3])
{
  char buf[7];
  char *endptr;
  unsigned long value;
  size_t i;
  size_t n = 0U;

  if ((text == NULL) || (out == NULL))
  {
    return 0U;
  }

  while ((*text != '\0') && (n < 6U))
  {
    if (isxdigit((unsigned char)*text) != 0)
    {
      buf[n++] = *text;
    }
    text++;
  }

  if (n != 6U)
  {
    return 0U;
  }

  for (i = 0U; i < n; i++)
  {
    if (isxdigit((unsigned char)buf[i]) == 0)
    {
      return 0U;
    }
  }

  buf[6] = '\0';
  value = strtoul(buf, &endptr, 16);
  if ((endptr == NULL) || (*endptr != '\0'))
  {
    return 0U;
  }

  out[0] = (uint8_t)((value >> 16) & 0xFFU);
  out[1] = (uint8_t)((value >> 8) & 0xFFU);
  out[2] = (uint8_t)(value & 0xFFU);
  return 1U;
}

static char *App_SkipSpaces(char *s)
{
  while ((s != NULL) && (*s != '\0') && isspace((unsigned char)*s))
  {
    s++;
  }
  return s;
}
static void App_LedTxPulse(void)
{
  TimerStop(&g_led_tx_timer);
  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
  TimerSetValue(&g_led_tx_timer, LED_PULSE_MS);
  TimerStart(&g_led_tx_timer);
}

static void App_LedRxPulse(void)
{
  TimerStop(&g_led_rx_timer);
  HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
  TimerSetValue(&g_led_rx_timer, LED_PULSE_MS);
  TimerStart(&g_led_rx_timer);
}

static void App_LedErrPulse(void)
{
  TimerStop(&g_led_err_timer);
  HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_SET);
  TimerSetValue(&g_led_err_timer, LED_PULSE_MS);
  TimerStart(&g_led_err_timer);
}
static void OnTxDone(void)
{
  g_radio_tx_done = 1U;
}

static void App_LedTxOff(void *context)
{
  (void)context;
  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);
}

static void App_LedRxOff(void *context)
{
  (void)context;
  HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_RESET);
}

static void App_LedErrOff(void *context)
{
  (void)context;
  HAL_GPIO_WritePin(LED3_GPIO_Port, LED3_Pin, GPIO_PIN_RESET);
}


static void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t cfo)
{
  g_last_rx_rssi = rssi;
  g_last_rx_cfo = cfo;

  if (size > RADIO_MAX_PAYLOAD_SIZE)
  {
    size = RADIO_MAX_PAYLOAD_SIZE;
  }

  memcpy(g_rx_payload, payload, size);
  g_rx_payload_len = size;
  g_radio_rx_done = 1U;
}

static void OnTxTimeout(void)
{
  g_radio_tx_timeout = 1U;
}

static void OnRxTimeout(void)
{
  g_radio_rx_timeout = 1U;
}

static void OnRxError(void)
{
  g_radio_rx_error = 1U;
}
