/*
MIT License

Copyright (c) 2019 Horizon Hobby, LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#include <string.h>
#include <stdint.h>

#include "spm_srxl.h"

/// LOCAL TYPES AND CONSTANTS ///

#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_SPEED)
const uint16_t srxlCRCTable[] =
{
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
    0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
    0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
    0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
    0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,

    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
    0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
    0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
    0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
    0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,

    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
    0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
    0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
    0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
    0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,

    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
    0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
    0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
    0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};
#endif

#define SRXL_TELEM_SUPPRESS_MAX (100)

/// PUBLIC VARIABLES ///

SrxlChannelData srxlChData = {0, 0, 0, {0}};
SrxlTelemetryData srxlTelemData = {0};
SrxlVtxData srxlVtxData = {0, 0, 1, 0, 0, 1};

/// LOCAL VARIABLES ///

static SrxlDevice srxlThisDev = {0};
static SrxlBus srxlBus[SRXL_NUM_OF_BUSES];
static bool srxlChDataIsFailsafe = false;
static bool srxlTelemetryPhase = false;
#ifdef SRXL_INCLUDE_MASTER_CODE
static uint32_t srxlFailsafeChMask = 0;  // Tracks all active channels for use during failsafe transmission
#endif
static SrxlBindData srxlBindInfo = {0, 0, 0, 0};
static SrxlReceiverStats srxlRx = {0};
static uint16_t srxlTelemSuppressCount = 0;

#ifdef SRXL_INCLUDE_FWD_PGM_CODE
static SrxlFullID srxlFwdPgmDevice = {0, 0};  // Device that should accept Forward Programming connection by default
static uint8_t srxlFwdPgmBuffer[FWD_PGM_MAX_DATA_SIZE] = {0};
static uint8_t srxlFwdPgmBufferLength = 0;
#endif

// Include additional header and externs if using STM32 hardware acceleration
#if(SRXL_CRC_OPTIMIZE_MODE > SRXL_CRC_OPTIMIZE_SIZE)
#ifdef __cplusplus
extern "C"
{
#endif
#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HAL)
#if(SRXL_STM_TARGET_FAMILY == SRXL_STM_TARGET_F7)
#include "stm32f7xx_hal.h"
#else
#include "stm32f3xx_hal.h"  // Default to F3 if not given
#endif
    extern CRC_HandleTypeDef hcrc;
#else
#if(SRXL_STM_TARGET_FAMILY == SRXL_STM_TARGET_F7)
#error "STM32F7 targets not yet supported for register-based STM HW optimization"
#else
#include "stm32f30x.h"  // Default to F3 if not given
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif

/// LOCAL HELPER FUNCTIONS ///

// Compute SRXL CRC over packet buffer (assumes length is correctly set)
static uint16_t srxlCrc16(uint8_t* packet)
{
    uint16_t crc = 0;                // Seed with 0
    uint8_t length = packet[2] - 2;  // Exclude 2 CRC bytes at end of packet from the length

    if(length <= SRXL_MAX_BUFFER_SIZE - 2)
    {
#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_SIZE)
        // Use bitwise method
        for(uint8_t i = 0; i < length; ++i)
        {
            crc = crc ^ ((uint16_t)packet[i] << 8);
            for(int b = 0; b < 8; b++)
            {
                if(crc & 0x8000)
                    crc = (crc << 1) ^ 0x1021;
                else
                    crc = crc << 1;
            }
        }
#elif(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HW)
        // Use direct STM32 HW CRC register access
        uint8_t* pEnd = &packet[length];
        srxlEnterCriticalSection();
#ifdef SRXL_SAVE_HW_CRC_CONTEXT
        uint32 savedPOL = CRC->POL;
        uint32 savedINIT = CRC->INIT;
        uint32 savedCR = CRC->CR;
        uint32 savedDR = CRC->DR;
#endif
        CRC->POL = 0x1021;
        CRC->INIT = 0;
        CRC->CR = 0x09;  // 16-bit polynomial, no input or output reversal, reset to init of 0
        while(packet < pEnd)
            *(uint8_t*)(CRC_BASE) = *(packet++);
        crc = (uint16_t)CRC->DR;
#ifdef SRXL_SAVE_HW_CRC_CONTEXT
        // We have to use this convoluted method to restore things because writing INIT sets DR too
        CRC->CR = 0;
        CRC->POL = 0x04C11DB7;
        CRC->INIT = savedINIT;
        CRC->DR = savedINIT;
        CRC->POL = savedDR;
        CRC->DR = 1;
        CRC->CR = savedCR;
        CRC->POL = savedPOL;
#endif
        srxlExitCriticalSection();
#elif(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HAL)
        // STM32f3/f7 hardware optimization using the STM32Cube libraries in HAL mode requires the following
        // configuration, set in the STM32CubeMX "Pinout & Configuration" tab under "Computing > CRC":
        //  Basic Parameters
        //    Default Polynomial State        = Disable
        //    CRC Length                      = 16-bit
        //    CRC Generating Polynomial       = X12+X5+X0
        //    Default Init Value State        = Disable
        //    Init Value For CRC Computation  = 0
        //  Advanced Parameters
        //    Input Data Inversion Mode       = None
        //    Output Data Inversion Mode      = Disable
        //    Input Data Format               = Bytes
        crc = (uint16_t)HAL_CRC_Calculate(&hcrc, (uint32_t*)packet, length);
#elif(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_EXTERNAL)
        crc = SRXL_CRC_CALCULATE(packet, length, crc);
#else
        // Default to table-lookup method
        uint8_t i;
        for(i = 0; i < length; ++i)
        {
            // Get indexed position in lookup table using XOR of current CRC hi byte
            uint8_t pos = (uint8_t)((crc >> 8) ^ packet[i]);
            // Shift LSB up and XOR with the resulting lookup table entry
            crc = (uint16_t)((crc << 8) ^ (uint16_t)(srxlCRCTable[pos]));
        }
#endif
    }
    return crc;
}

// Get the receiver entry for the requested bus and device ID
static inline SrxlRcvrEntry* srxlGetReceiverEntry(uint8_t busIndex, uint8_t deviceID)
{
    SrxlRcvrEntry* pRcvr = 0;
    uint8_t i;
    for(i = 0; i < srxlRx.rcvrCount; ++i)
    {
        if((srxlRx.rcvr[i].busBits & (1u << busIndex)) && (srxlRx.rcvr[i].deviceID == deviceID))
        {
            pRcvr = &srxlRx.rcvr[i];
            break;
        }
    }
    return pRcvr;
}

// Add a new receiver entry for the given device
static inline SrxlRcvrEntry* srxlAddReceiverEntry(SrxlBus* pBus, SrxlDevEntry devEntry)
{
    // Only allow receivers (or flight controllers in certain circumstances) to be added
    if(!pBus || devEntry.deviceID < 0x10 || devEntry.deviceID >= 0x40)
        return 0;

    // If we didn't previously add this receiver, add it now if we have room
    SrxlRcvrEntry* pRcvr = srxlGetReceiverEntry(pBus->fullID.busIndex, devEntry.deviceID);
    if(!pRcvr)
    {
        if(srxlRx.rcvrCount >= SRXL_MAX_RCVRS)
            return 0;

        uint8_t i = srxlRx.rcvrCount++;
        pRcvr = &srxlRx.rcvr[i];
        pRcvr->deviceID = devEntry.deviceID;
        pRcvr->busBits = (1u << pBus->fullID.busIndex);
        pRcvr->info = devEntry.info;

        // If this receiver is full-range, insert into our sorted list after the other full-range telemetry receivers
        if(pRcvr->info & SRXL_DEVINFO_TELEM_FULL_RANGE)
        {
            uint8_t n;
            for(n = i; n > srxlRx.rcvrSortInsert; --n)
                srxlRx.rcvrSorted[n] = srxlRx.rcvrSorted[n - 1];
            srxlRx.rcvrSorted[(srxlRx.rcvrSortInsert)++] = pRcvr;
        }
        // Else just tack onto the end
        else
        {
            srxlRx.rcvrSorted[i] = pRcvr;
        }
    }

    // If this new receiver is a base receiver that supports telemetry or we haven't set a default active telemetry receiver, set it
    if(!srxlRx.pTelemRcvr || (pRcvr->deviceID >= 0x20 && pRcvr->deviceID < 0x30 && (pRcvr->info & SRXL_DEVINFO_TELEM_TX_ENABLED)))
    {
        srxlRx.pTelemRcvr = pRcvr;
    }

    return pRcvr;
}

// Pick the best receiver to send telemetry on
static inline SrxlRcvrEntry* srxlChooseTelemRcvr(void)
{
    // If we only know about one receiver, set it to that
    if(srxlRx.rcvrCount == 1)
        return srxlRx.rcvrSorted[0];

    // If we were previously sending telemetry
    if(srxlRx.pTelemRcvr && srxlRx.pTelemRcvr->channelMask)
    {
        // If the current choice is not full-range
        if((srxlRx.pTelemRcvr->info & SRXL_DEVINFO_TELEM_FULL_RANGE) == 0)
        {
            // Then see if there is a full-range choice that received channel data to switch to
            uint8_t i;
            for(i = 0; i < srxlRx.rcvrSortInsert; ++i)
            {
                if(srxlRx.rcvrSorted[i]->channelMask)
                    return srxlRx.rcvrSorted[i];
            }
        }

        // Else keep using the current receiver
        return srxlRx.pTelemRcvr;
    }
    // Else just pick the first one that got channel data this past frame
    else
    {
        uint8_t i;
        for(i = 0; i < srxlRx.rcvrCount; ++i)
        {
            if(srxlRx.rcvrSorted[i]->channelMask)
                return srxlRx.rcvrSorted[i];
        }
    }

    return 0;
}

// Return pointer to device entry matching the given ID, or NULL if not found
static SrxlDevEntry* srxlGetDeviceEntry(SrxlBus* pBus, uint8_t deviceID)
{
    if(pBus)
    {
        uint8_t i;
        for(i = 0; i < pBus->rxDevCount; ++i)
        {
            if(pBus->rxDev[i].deviceID == deviceID)
                return &(pBus->rxDev[i]);
        }
    }
    return 0;
}

// Add an entry to our list of devices found on the SRXL bus (or update an entry if it already exists)
static SrxlDevEntry* srxlAddDeviceEntry(SrxlBus* pBus, SrxlDevEntry devEntry)
{
    // Don't allow broadcast or unknown device types to be added
    if(!pBus || devEntry.deviceID < 0x10 || devEntry.deviceID > 0xEF)
        return 0;

    // Limit device priority
    if(devEntry.priority > 100)
        devEntry.priority = 100;

    // Update device entry if it already exists
    SrxlDevEntry* retVal = srxlGetDeviceEntry(pBus, devEntry.deviceID);
    if(retVal)
    {
        pBus->rxDevPrioritySum -= retVal->priority;
        *retVal = devEntry;
        pBus->rxDevPrioritySum += retVal->priority;
    }
    // Else add to the list if we have room
    else if(pBus->rxDevCount < SRXL_MAX_DEVICES)
    {
        retVal = &(pBus->rxDev[pBus->rxDevCount++]);
        *retVal = devEntry;
        pBus->rxDevPrioritySum += retVal->priority;

#ifdef SRXL_INCLUDE_FWD_PGM_CODE
        // If the new device supports Forward Programming and is a base receiver or flight controller, choose as the default
        if(devEntry.info & SRXL_DEVINFO_FWD_PROG_SUPPORT)
        {
            uint8_t devType = devEntry.deviceID >> 4;
            if(devType > (srxlFwdPgmDevice.deviceID >> 4) && devType < SrxlDevType_ESC)
            {
                srxlFwdPgmDevice.deviceID = devEntry.deviceID;
                srxlFwdPgmDevice.busIndex = pBus->fullID.busIndex;
            }
        }
#endif

        // If the new device is a receiver, add to our receiver list
        if(retVal->deviceID < 0x30)
        {
            srxlAddReceiverEntry(pBus, *retVal);
        }
    }

    return retVal;
}

/// PUBLIC FUNCTIONS ///

/**
    @brief  Initialize common SRXL info for this device

    @param  deviceID:   SRXL Device ID (see section 7.1.1 of SRXL2 Spec)
    @param  priority:   Requested telemetry priority (1-100; typical is 10 per unique message type)
    @param  info:       Device info bits (see SRXL_DEVINFO_XXX bits in spm_srxl.h)
    @param  uid:        Unique 32-bit id to avoid device ID collision during handshake
    @return bool:       True if device info was successfully initialized
*/
bool srxlInitDevice(uint8_t deviceID, uint8_t priority, uint8_t info, uint32_t uid)
{
    if(deviceID < 0x10 || deviceID > 0xEF)
        return false;

    srxlThisDev.devEntry.deviceID = deviceID;
    srxlThisDev.devEntry.info = info;
    srxlThisDev.devEntry.priority = priority;
    srxlThisDev.devEntry.rfu = 0;
    srxlThisDev.uid = uid;
    srxlThisDev.vtxProxy = false;

#ifdef SRXL_INCLUDE_MASTER_CODE
    // If this device is a receiver, add to our receiver info
    if(deviceID < 0x30)
    {
        srxlInitReceiver(deviceID, info);
    }
#endif

#ifdef SRXL_INCLUDE_FWD_PGM_CODE
    // If this device is a receiver or flight controller that supports Forward Programming, set as default
    if((info & SRXL_DEVINFO_FWD_PROG_SUPPORT) && deviceID < 0x40)
    {
        srxlFwdPgmDevice.deviceID = deviceID;
        srxlFwdPgmDevice.busIndex = 0;
    }
#endif

#if(SRXL_CRC_OPTIMIZE_MODE == SRXL_CRC_OPTIMIZE_STM_HW)
    // Enable the peripheral clock for the HW CRC engine
    RCC->AHBENR |= RCC_AHBENR_CRCEN;
#endif

    return true;
}

/**
    @brief  Initialize bus settings for the given SRXL bus

    @param  busIndex:       Index into srxlBus array of bus entries
    @param  uart:           Number to identify UART to which this SRXL bus should be connected
    @param  baudSupported:  0 = 115200 baud, 1 = 400000 baud
    @return bool:           True if SRXL bus was successfully initialized
*/
bool srxlInitBus(uint8_t busIndex, uint8_t uart, uint8_t baudSupported)
{
    if(busIndex >= SRXL_NUM_OF_BUSES || !srxlThisDev.devEntry.deviceID)
        return false;

    SrxlBus* pBus = &srxlBus[busIndex];
    pBus->state = SrxlState_ListenOnStartup;
    pBus->fullID.deviceID = srxlThisDev.devEntry.deviceID;
    pBus->fullID.busIndex = busIndex;
    pBus->rxDevCount = 0;
    pBus->rxDevPrioritySum = 0;
    pBus->requestID = (srxlThisDev.devEntry.deviceID == 0x10) ? 0x11 : 0;
    pBus->baudSupported = baudSupported;
    pBus->baudRate = SRXL_BAUD_115200;
    pBus->frameErrCount = 0;
    pBus->uart = uart;
    // Default remote receiver is automatically master -- everyone else figures it out during handshake
    pBus->master = (srxlThisDev.devEntry.deviceID == 0x10);
    pBus->pMasterRcvr = (srxlThisDev.devEntry.deviceID == 0x10) ? &srxlRx.rcvr[0] : 0;
    pBus->initialized = true;

    return true;
}

/**
    @brief  See if this device is the bus master on the given bus

    @param  busIndex:   Index into srxlBus array for the desired SRXL bus
    @return bool:       True if this device is the bus master on the given SRXL bus
*/
bool srxlIsBusMaster(uint8_t busIndex)
{
    return (busIndex < SRXL_NUM_OF_BUSES && srxlBus[busIndex].master);
}

/**
    @brief  Get the current SRXL state machine timeout count for the given bus

    @param  busIndex:   Index into srxlBus array for the desired SRXL bus
    @return uint16_t:    Timeout count in ms for the given SRXL bus
*/
uint16_t srxlGetTimeoutCount_ms(uint8_t busIndex)
{
    return (busIndex < SRXL_NUM_OF_BUSES) ? srxlBus[busIndex].timeoutCount_ms : 0;
}

/**
    @brief  Get the Device ID of this device on the given bus

    @param  busIndex:   Index into srxlBus array for the desired SRXL bus
    @return uint8_t:    Device ID of this device on the given SRXL bus
*/
uint8_t srxlGetDeviceID(uint8_t busIndex)
{
    return (busIndex < SRXL_NUM_OF_BUSES) ? srxlBus[busIndex].fullID.deviceID : 0;
}

/**
    @brief  Internal send function called by srxlRun() -- do not call in user code

    @param  pBus:       Pointer to SRXL bus entry for the desired SRXL bus
    @param  srxlCmd:    Specific type of packet to send
    @param  replyID:    Device ID of the device this Send command is targeting
*/
static void srxlSend(SrxlBus* pBus, SRXL_CMD srxlCmd, uint8_t replyID)
{
    if(!pBus || !pBus->initialized)
        return;

    memset(pBus->srxlOut.raw, 0, SRXL_MAX_BUFFER_SIZE);
    pBus->srxlOut.header.srxlID = SPEKTRUM_SRXL_ID;

    // VTX Data
    if(srxlCmd == SRXL_CMD_VTX)
    {
        pBus->srxlOut.header.packetType = SRXL_CTRL_ID;
        pBus->srxlOut.header.length = SRXL_CTRL_BASE_LENGTH + sizeof(SrxlVtxData);
        pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_VTX;
        pBus->srxlOut.control.payload.replyID = replyID;
        pBus->srxlOut.control.payload.vtxData = srxlVtxData;
    }
#ifdef SRXL_INCLUDE_MASTER_CODE
    // Channel Data
    else if(srxlCmd == SRXL_CMD_CHANNEL || srxlCmd == SRXL_CMD_CHANNEL_FS)
    {
        pBus->srxlOut.header.packetType = SRXL_CTRL_ID;
        uint32_t channelMask;
        if(srxlCmd == SRXL_CMD_CHANNEL)
        {
            pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_CHANNEL;
            pBus->srxlOut.control.payload.replyID = replyID;

            channelMask = srxlChData.mask;
        }
        else // == SRXL_CMD_CHANNEL_FS
        {
            // In failsafe mode, we dont want a telemetry reply
            pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_CHANNEL_FS;
            pBus->srxlOut.control.payload.replyID = 0;

            channelMask = srxlFailsafeChMask;
        }

        // Set signal quality info (only a bus master sends this, so assume srxlChData contains the latest values)
        pBus->srxlOut.control.payload.channelData.rssi = srxlChData.rssi;
#ifdef SRXL_IS_HUB
        pBus->srxlOut.control.payload.channelData.frameLosses = srxlRx.frameLosses;
#else
        pBus->srxlOut.control.payload.channelData.frameLosses = srxlRx.rcvr[0].fades;
#endif

        uint8_t channelIndex = 0;
        uint32_t channelMaskBit = 1;
        for(uint8_t i = 0; i < 32; ++i, channelMaskBit <<= 1)
        {
            if(channelMask & channelMaskBit)
            {
                pBus->srxlOut.control.payload.channelData.values[channelIndex++] = srxlChData.values[i];
            }
        }

        // Set bits in packet for channels we populated, and clear those mask bits if it was part of a normal channel data command
        pBus->srxlOut.control.payload.channelData.mask = channelMask;
        if(srxlCmd == SRXL_CMD_CHANNEL)
            srxlChData.mask &= ~channelMask;

        pBus->srxlOut.header.length = SRXL_CTRL_BASE_LENGTH + 7 + (2 * channelIndex);
    }
#ifdef SRXL_INCLUDE_FWD_PGM_CODE
    // Forward Programming Pass-thru
    else if(srxlCmd == SRXL_CMD_FWDPGM)
    {
        pBus->srxlOut.header.packetType = SRXL_CTRL_ID;
        pBus->srxlOut.header.length = SRXL_CTRL_BASE_LENGTH + 3 + srxlFwdPgmBufferLength;
        pBus->srxlOut.control.payload.cmd = SRXL_CTRL_CMD_FWDPGM;
        pBus->srxlOut.control.payload.replyID = replyID;
        memcpy(pBus->srxlOut.control.payload.fpData.data, srxlFwdPgmBuffer, srxlFwdPgmBufferLength);
    }
#endif  // SRXL_INCLUDE_FWD_PGM_CODE
#endif  // SRXL_INCLUDE_MASTER_CODE
    // RSSI Data
    else if(srxlCmd == SRXL_CMD_RSSI)
    {
        pBus->srxlOut.header.packetType = SRXL_RSSI_ID;
        pBus->srxlOut.header.length = sizeof(SrxlRssiPacket);
        pBus->srxlOut.rssi.request = SRXL_RSSI_REQ_REQUEST;  // TODO: Needs to handle both directions!
        pBus->srxlOut.rssi.antennaA = 0;                     // TODO: Fill in actual data later
        pBus->srxlOut.rssi.antennaB = 0;
        pBus->srxlOut.rssi.antennaC = 0;
        pBus->srxlOut.rssi.antennaD = 0;
    }
    else if(srxlCmd == SRXL_CMD_HANDSHAKE)
    {
        pBus->srxlOut.header.packetType = SRXL_HANDSHAKE_ID;
        pBus->srxlOut.header.length = sizeof(SrxlHandshakePacket);
        pBus->srxlOut.handshake.payload.srcDevID = srxlThisDev.devEntry.deviceID;
        pBus->srxlOut.handshake.payload.destDevID = replyID;
        pBus->srxlOut.handshake.payload.priority = srxlThisDev.devEntry.priority;
        pBus->srxlOut.handshake.payload.baudSupported = pBus->baudSupported;
        pBus->srxlOut.handshake.payload.info = srxlThisDev.devEntry.info;
        pBus->srxlOut.handshake.payload.uid = srxlThisDev.uid;
    }
    else if(srxlCmd == SRXL_CMD_TELEMETRY)
    {
        srxlFillTelemetry(&pBus->srxlOut.telemetry.payload);
        pBus->srxlOut.header.packetType = SRXL_TELEM_ID;
        pBus->srxlOut.header.length = sizeof(SrxlTelemetryPacket);
        // If we successfully received a handshake from the bus master
        if(pBus->pMasterRcvr)
        {
            // If we know that a device on this bus should send it, then target that device
            if(srxlRx.pTelemRcvr && (srxlRx.pTelemRcvr->busBits & (1u << pBus->fullID.busIndex)))
                pBus->srxlOut.telemetry.destDevID = srxlRx.pTelemRcvr->deviceID;
            else
                pBus->srxlOut.telemetry.destDevID = 0;
        }
        else
        {
            // Send 0xFF to tell bus master to re-send the handshake so we know where to direct telemetry in the future
            pBus->srxlOut.telemetry.destDevID = 0xFF;
        }

#ifdef SRXL_INCLUDE_MASTER_CODE
        if(srxlRx.pTelemRcvr && (pBus->srxlOut.telemetry.destDevID == srxlRx.pTelemRcvr->deviceID))
        {
            // Don't mark telemetry as having been sent if we are sending it ourself over RF
            if(pBus->srxlOut.telemetry.destDevID != srxlThisDev.pRcvr->deviceID)
            {
                srxlTelemetrySent();
                // Clear telemetry buffer after sending so we don't repeatedly display old data
//                srxlTelemData.sensorID = srxlTelemData.secondaryID = 0;
            }
        }
#endif
    }
    else if(srxlCmd == SRXL_CMD_ENTER_BIND)
    {
        pBus->srxlOut.header.packetType = SRXL_BIND_ID;
        pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
        pBus->srxlOut.bind.request = SRXL_BIND_REQ_ENTER;
        pBus->srxlOut.bind.deviceID = replyID;
        pBus->srxlOut.bind.data.type = DSMX_11MS;
        pBus->srxlOut.bind.data.options = (replyID != 0xFF) ? SRXL_BIND_OPT_TELEM_TX_ENABLE | SRXL_BIND_OPT_BIND_TX_ENABLE : 0;
        pBus->srxlOut.bind.data.guid = 0;
        pBus->srxlOut.bind.data.uid = 0;
    }
    else if(srxlCmd == SRXL_CMD_REQ_BIND)
    {
        pBus->srxlOut.header.packetType = SRXL_BIND_ID;
        pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
        pBus->srxlOut.bind.request = SRXL_BIND_REQ_STATUS;
        pBus->srxlOut.bind.deviceID = replyID;
        memset(&(pBus->srxlOut.bind.data), 0, sizeof(SrxlBindData));
    }
    else if(srxlCmd == SRXL_CMD_SET_BIND)
    {
        pBus->srxlOut.header.packetType = SRXL_BIND_ID;
        pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
        pBus->srxlOut.bind.request = SRXL_BIND_REQ_SET_BIND;
        pBus->srxlOut.bind.deviceID = replyID;
        pBus->srxlOut.bind.data = srxlBindInfo;
    }
    else if(srxlCmd == SRXL_CMD_BIND_INFO)
    {
        pBus->srxlOut.header.packetType = SRXL_BIND_ID;
        pBus->srxlOut.header.length = sizeof(SrxlBindPacket);
        pBus->srxlOut.bind.request = SRXL_BIND_REQ_BOUND_DATA;
        pBus->srxlOut.bind.deviceID = replyID;
        pBus->srxlOut.bind.data = srxlBindInfo;
    }

    // Compute CRC over entire SRXL packet (excluding the 2 CRC bytes at the end)
    uint16_t crc = srxlCrc16(pBus->srxlOut.raw);

    // Add CRC to packet in big-endian byte order
    pBus->srxlOut.raw[pBus->srxlOut.header.length - 2] = (crc >> 8) & 0xFF;
    pBus->srxlOut.raw[pBus->srxlOut.header.length - 1] = crc & 0xFF;

    // Send the packet out over the assigned UART
    srxlSendOnUart(pBus->uart, pBus->srxlOut.raw, pBus->srxlOut.header.length);
}

/**
    @brief  Parse an SRXL packet received on the given SRXL bus UART

    @param  busIndex:   Index of SRXL bus state information entry in the srxlBus array
    @param  packet:     Pointer to received packet data
    @param  length:     Length in bytes of received packet data
    @return bool:       True if a valid packet was received, else false
*/
bool srxlParsePacket(uint8_t busIndex, uint8_t* packet, uint8_t length)
{
    // Validate parameters
    if(busIndex >= SRXL_NUM_OF_BUSES || !packet || length < 5 || length > SRXL_MAX_BUFFER_SIZE)
        return false;

    // Validate SRXL ID and length
    if(packet[0] != SPEKTRUM_SRXL_ID || packet[2] != length)
        return false;

    // Validate checksum
    uint16_t crc = srxlCrc16(packet);
    if((((uint16_t)packet[length - 2] << 8) | packet[length - 1]) != crc)
        return false;

    // Copy packet into our unioned buffer to avoid "strict aliasing" violations
    SrxlBus* pBus = &srxlBus[busIndex];
    SrxlPacket* pRx = &(pBus->srxlIn);
    memcpy(pRx, packet, length);

    // Handle restart with ongoing communications -- bump to run state
    pBus->timeoutCount_ms = 0;  // TODO: Should we clear this even if packet isn't valid?
    if(pBus->state < SrxlState_Running && pRx->header.packetType != SRXL_HANDSHAKE_ID)
        pBus->state = SrxlState_Running;

    // Parse the specific data
    switch(pRx->header.packetType)
    {
    case SRXL_CTRL_ID:  // 0xCD
    {
        SrxlControlData* pCtrlData = &(pRx->control.payload);

        // Validate command
        if(pCtrlData->cmd > SRXL_CTRL_CMD_FWDPGM)
            break;

        // VTX
        if(pCtrlData->cmd == SRXL_CTRL_CMD_VTX)
        {
            if(srxlSetVtxData(&pCtrlData->vtxData))
                srxlOnVtx(&srxlVtxData);
            if(pCtrlData->replyID == pBus->fullID.deviceID || pCtrlData->replyID == 0xFF || srxlThisDev.vtxProxy)
            {
                // TODO: Should we ack this somehow
            }
        }
#ifdef SRXL_INCLUDE_FWD_PGM_CODE
        // Forward Programming
        else if(pCtrlData->cmd == SRXL_CTRL_CMD_FWDPGM)
        {
            if(pCtrlData->replyID == pBus->fullID.deviceID)
            {
                memcpy(srxlFwdPgmBuffer, pCtrlData->fpData.data, pRx->header.length - 3 - SRXL_CTRL_BASE_LENGTH);
                srxlFwdPgmBufferLength = length;
                if(pCtrlData->replyID == srxlFwdPgmDevice.deviceID)
                {
                    // Handle Forward Programming command locally
                    srxlOnFwdPgm(srxlFwdPgmBuffer, srxlFwdPgmBufferLength);
                }
                else if(srxlFwdPgmDevice.deviceID && srxlBus[srxlFwdPgmDevice.busIndex].master)
                {
                    // Pass it on through to the next target
                    srxlBus[srxlFwdPgmDevice.busIndex].txFlags.sendFwdPgmData = 1;
                }
            }
        }
#endif
        // Channel Data or Failsafe Data
        else
        {
            bool isFailsafe = (pCtrlData->cmd == SRXL_CTRL_CMD_CHANNEL_FS);
            srxlChData.rssi = pCtrlData->channelData.rssi;
            srxlChData.frameLosses = pCtrlData->channelData.frameLosses;
            if(pBus->pMasterRcvr)
            {
                if(pCtrlData->channelData.rssi < 0)
                {
                    pBus->pMasterRcvr->rssi_dBm = pCtrlData->channelData.rssi;
                    pBus->pMasterRcvr->rssiRcvd |= RSSI_RCVD_DBM;
                }
                else
                {
                    pBus->pMasterRcvr->rssi_Pct = pCtrlData->channelData.rssi;
                    pBus->pMasterRcvr->rssiRcvd |= RSSI_RCVD_PCT;
                }
                // If the receiver is sending alternating dBm/%, then use that as phase
                if(pBus->pMasterRcvr->rssiRcvd == RSSI_RCVD_BOTH)
                {
                    srxlTelemetryPhase = pCtrlData->channelData.rssi >= 0;
                }
                pBus->pMasterRcvr->fades = pCtrlData->channelData.frameLosses;
                pBus->pMasterRcvr->channelMask = isFailsafe ? 0 : pCtrlData->channelData.mask;
                srxlRx.rxBusBits |= pBus->pMasterRcvr->busBits;
            }

            // Only save received channel values to srxlChData if it's normal channel data or we're in a hold condition
            if(!isFailsafe || !srxlRx.lossCountdown)
            {
                uint8_t channelIndex = 0;
                uint32_t channelMaskBit = 1;
                uint8_t i;
                for(i = 0; i < 32 && channelMaskBit <= pCtrlData->channelData.mask; ++i, channelMaskBit <<= 1)
                {
                    if(pCtrlData->channelData.mask & channelMaskBit)
                    {
                        srxlChData.values[i] = pCtrlData->channelData.values[channelIndex++];
                        srxlChData.mask |= channelMaskBit;
                    }
                }
            }

            srxlChDataIsFailsafe = isFailsafe;  // TODO: Can we still assume this???
            srxlReceivedChannelData(&(pCtrlData->channelData), isFailsafe);

            // Figure out what type of reply packet to send, if any
            if(pCtrlData->replyID == 0)
            {
                if(pBus->txFlags.enterBind)
                {
                    pBus->state = SrxlState_SendEnterBind;
                    pBus->txFlags.enterBind = 0;
                }
                else if(pBus->txFlags.setBindInfo)
                {
                    if(srxlRx.pBindRcvr)  // TODO: Double-check this logic
                    {
                        pBus->requestID = srxlRx.pBindRcvr->deviceID;
                        pBus->state = SrxlState_SendSetBindInfo;
                    }
                    pBus->txFlags.setBindInfo = 0;
                }
                else if(pBus->txFlags.broadcastBindInfo)
                {
                    pBus->requestID = 0xFF;
                    pBus->state = SrxlState_SendSetBindInfo;
                    pBus->txFlags.broadcastBindInfo = 0;
                }
            }
            else if(pCtrlData->replyID == pBus->fullID.deviceID)
            {
                pBus->state = SrxlState_SendTelemetry;
            }
        }
        break;
    }
    case SRXL_HANDSHAKE_ID:  // 0x21
    {
        if(length < sizeof(SrxlHandshakePacket))
            return false;

        // If this is an unprompted handshake (dest == 0) from a higher device ID, then we're the master
        SrxlHandshakeData* pHandshake = &(pRx->handshake.payload);
        if((pHandshake->destDevID == 0) && (pHandshake->srcDevID > pBus->fullID.deviceID))
        {
            // Send a reply immediately to get the slave to shut up
            pBus->state = SrxlState_SendHandshake;
            pBus->requestID = pHandshake->srcDevID;
            pBus->baudSupported = SRXL_SUPPORTED_BAUD_RATES;
            srxlRun(busIndex, 0);
            pBus->requestID = pBus->fullID.deviceID;
            pBus->state = SrxlState_SendHandshake;
            pBus->master = true;
            pBus->pMasterRcvr = &srxlRx.rcvr[0];
        }

        // Add this device to our list of discovered devices
        SrxlDevEntry newDev = {.deviceID = pHandshake->srcDevID, .priority = pHandshake->priority, .info = pHandshake->info};
        srxlAddDeviceEntry(pBus, newDev);

        // Bus master needs to track responses and poll next device
        if(pBus->master)
        {
            // Keep track of baud rates supported
            pBus->baudSupported &= pHandshake->baudSupported;

            // Make sure the state machine is set to poll -- this will eventually reach broadcast address (0xFF)
            pBus->state = SrxlState_SendHandshake;
        }
        // Broadcast handshake sets the agreed upon baud rate for this bus
        else if(pHandshake->destDevID == 0xFF)
        {
            // Get bus master receiver entry (if it's a flight controller, add it now as a receiver on this bus)
            if(newDev.deviceID >= 0x30 && newDev.deviceID < 0x40)
            {
                pBus->pMasterRcvr = srxlAddReceiverEntry(pBus, newDev);
            }
            else
            {
                pBus->pMasterRcvr = srxlGetReceiverEntry(busIndex, newDev.deviceID);
            }

            // Set baud rate and advance to run state
            pBus->baudRate = pHandshake->baudSupported;
            if(pBus->baudRate & SRXL_BAUD_400000)
            {
                srxlChangeBaudRate(pBus->uart, 400000);  // Only alternate rate supported for now...
            }
            pBus->state = SrxlState_Running;
        }
        // Normal Handshake destined for this device should be replied to -- else ignore
        else
        {
            if(pHandshake->destDevID == pBus->fullID.deviceID && pBus->state != SrxlState_SendHandshake)
            {
                pBus->requestID = pHandshake->srcDevID;
                pBus->state = SrxlState_SendHandshake;
            }
            else
            {
                pBus->state = SrxlState_ListenForHandshake;
            }
        }

        break;
    }
    case SRXL_PARAM_ID:  // 0x50
    {
        // TODO: Add later
        break;
    }
    case SRXL_RSSI_ID:  // 0x55
    {
        // TODO: Add later
        break;
    }
    case SRXL_BIND_ID:  // 0x41
    {
        if(length < sizeof(SrxlBindPacket))
            return false;

        SrxlBindPacket* pBindInfo = &(pRx->bind);

        // If this is a bound data report
        if(pBindInfo->request == SRXL_BIND_REQ_BOUND_DATA)
        {
            // Call the user-defined callback -- if returns true, bind all other receivers
            SrxlFullID boundID;
            boundID.deviceID = pBindInfo->deviceID;
            boundID.busIndex = busIndex;
            if(srxlOnBind(boundID, pBindInfo->data))
            {
                // Update the bind info
                srxlBindInfo.type = pBindInfo->data.type;
                if(pBindInfo->data.options & SRXL_BIND_OPT_TELEM_TX_ENABLE)
                {
                    SrxlRcvrEntry* pNewTelem = srxlGetReceiverEntry(busIndex, pBindInfo->deviceID);
                    if(pNewTelem || !srxlRx.pTelemRcvr)
                        srxlRx.pTelemRcvr = pNewTelem;
                }
                srxlBindInfo.options = 0;  // Disable telemetry and Discovery reply when setting other receivers
                srxlBindInfo.guid = pBindInfo->data.guid;
                srxlBindInfo.uid = pBindInfo->data.uid;

                // Try to set bind info for all other receivers on other buses to match it
                uint8_t b;
                for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
                {
                    if(b == busIndex)
                        continue;
                    srxlBus[b].txFlags.broadcastBindInfo = 1;
                }
            }
        }
        // If this bind packet is directed at us
        else if(pBindInfo->deviceID == pBus->fullID.deviceID || pBindInfo->deviceID == 0xFF)
        {
            // Check for Enter Bind Mode (only valid if sent to a specific receiver)
            if(pBindInfo->request == SRXL_BIND_REQ_ENTER)
            {
#ifdef SRXL_INCLUDE_MASTER_CODE
                srxlBindInfo.type = pBindInfo->data.type;
                srxlBindInfo.options = pBindInfo->data.options;
                srxlBindInfo.guid = 0;
                srxlBindInfo.uid = 0;
                srxlTryToBind(srxlBindInfo);
#endif
            }
            else if(pBindInfo->request == SRXL_BIND_REQ_STATUS && srxlThisDev.pRcvr)
            {
                // TODO: Fill in data if we didn't just bind?
                pBus->txFlags.reportBindInfo = 1;
            }
            // Handle set bind info request
            else if(pBindInfo->request == SRXL_BIND_REQ_SET_BIND)
            {
                srxlBindInfo = pBindInfo->data;
#ifdef SRXL_INCLUDE_MASTER_CODE
                if(pBus->fullID.deviceID < 0x30)
                    srxlTryToBind(srxlBindInfo);
#endif
            }
        }

        break;
    }
    case SRXL_TELEM_ID:  // 0x80
    {
        if(length < sizeof(SrxlTelemetryPacket))
            return false;

        // NOTE: This data should be sent by exactly one telemetry device in response to a bus master request,
        //       so it is safe to update the global pTelemRcvr here even though this is a bus-specific function.

        SrxlTelemetryPacket* pTelem = &(pRx->telemetry);
        memcpy(&srxlTelemData, &pTelem->payload, sizeof(srxlTelemData));
        // If the telemetry destination is set to broadcast, that indicates a request to re-handshake
        if(pBus->master && pTelem->destDevID == 0xFF)
        {
            // If the master only found one device, don't poll again -- just tell the requesting device who we are
            pBus->requestID = pBus->rxDevCount > 1 ? pBus->fullID.deviceID : 0xFF;
            pBus->state = SrxlState_SendHandshake;
        }
        // If the incoming telemetry is destined for us, then we need to figure out who should send it over RF
        else if(pTelem->destDevID == pBus->fullID.deviceID)
        {
            // This needs different logic for hubs versus endpoints
#ifdef SRXL_IS_HUB
            if(srxlRx.pTelemRcvr == 0)
            {
                srxlRx.pTelemRcvr = srxlChooseTelemRcvr();
            }
#else
            srxlRx.pTelemRcvr = srxlThisDev.pRcvr;
#endif
            // Enable this device's telemetry tx based on whether we are the chosen telemetry receiver
#ifdef SRXL_INCLUDE_MASTER_CODE
            srxlSetTelemetryTxEnable(srxlRx.pTelemRcvr && (srxlRx.pTelemRcvr == srxlThisDev.pRcvr));
#endif
        }
        // Else turn off our telemetry and that of any receivers we might reply to via our own telemetry
        else
        {
            srxlRx.pTelemRcvr = 0;
#ifdef SRXL_INCLUDE_MASTER_CODE
            srxlSetTelemetryTxEnable(false);
#endif
        }

        srxlTelemSuppressCount = 0;
#ifdef SRXL_INCLUDE_MASTER_CODE
        srxlSuppressInternalTelemetry(&pTelem->payload);
#endif
        break;
    }
    default:
    {
        break;
    }
    }

    // Run state machine for slave devices after each received packet
    if(!pBus->master)
    {
        srxlRun(busIndex, 0);
    }

    return true;
}

/**
    @brief  Run the SRXL state machine after each receive or rx timeout

    @param  busIndex:           Index of SRXL bus state information entry in the srxlBus array
    @param  timeoutDelta_ms:    Number of milliseconds to increment receive timeout if a timeout
                                occured, or <= 0 to clear timeout count upon packet receive.
*/
void srxlRun(uint8_t busIndex, int16_t timeoutDelta_ms)
{
    SrxlBus* pBus = &srxlBus[busIndex];
    if(busIndex >= SRXL_NUM_OF_BUSES || !pBus->initialized || pBus->state == SrxlState_Disabled)
        return;

    // Check receive timeout and advance state if needed
    if(timeoutDelta_ms > 0)
    {
        pBus->timeoutCount_ms += timeoutDelta_ms;
        if(pBus->timeoutCount_ms >= 30000)
            pBus->timeoutCount_ms = 30000;

        if(pBus->timeoutCount_ms >= 50)
        {
            // After startup delay of 50ms, switch to handshake send or listen based on device unit ID
            if(pBus->state == SrxlState_ListenOnStartup)
            {
                pBus->state = (pBus->fullID.deviceID & 0x0F) ? SrxlState_ListenForHandshake : SrxlState_SendHandshake;
            }
            // Reset non-master device back to startup conditions if 50ms elapses with no communications
            else if(!pBus->master && pBus->state >= SrxlState_Running)
            {
                srxlChangeBaudRate(pBus->uart, 115200);
                pBus->baudRate = SRXL_BAUD_115200;
                pBus->timeoutCount_ms = 0;
                pBus->requestID = 0;  // Change back to 0 to indicate unprompted handshake from slave device
                if(pBus->pMasterRcvr)
                {
                    pBus->pMasterRcvr->channelMask = 0;
                    pBus->pMasterRcvr->fades = 0xFFFF;
                    pBus->pMasterRcvr->rssi_Pct = 0;
                    pBus->pMasterRcvr->rssi_dBm = -1;
                }
                pBus->state = SrxlState_ListenOnStartup;
            }
        }
    }
    else
    {
        pBus->timeoutCount_ms = 0;
    }

    if(!pBus->master)
    {
        // Non-master actions for the given state
        switch(pBus->state)
        {
        case SrxlState_SendHandshake:
        {
            srxlSend(pBus, SRXL_CMD_HANDSHAKE, pBus->requestID);
            break;
        }
        case SrxlState_SendTelemetry:
        {
            srxlSend(pBus, SRXL_CMD_TELEMETRY, pBus->requestID);
            pBus->state = SrxlState_Running;
            break;
        }
        case SrxlState_SendVTX:
        {
            srxlSend(pBus, SRXL_CMD_VTX, pBus->requestID);
            pBus->state = SrxlState_Running;
            break;
        }
        case SrxlState_SendEnterBind:
        {
            if(srxlRx.pBindRcvr && (srxlRx.pBindRcvr != srxlThisDev.pRcvr))
            {
                srxlSend(pBus, SRXL_CMD_ENTER_BIND, srxlRx.pBindRcvr->deviceID);
            }
            else
            {
                srxlBindInfo.options = 0;
                srxlSend(pBus, SRXL_CMD_ENTER_BIND, 0xFF);
            }
            pBus->state = SrxlState_Running;
            break;
        }
        case SrxlState_SendSetBindInfo:
        {
            srxlSend(pBus, SRXL_CMD_SET_BIND, pBus->requestID);
            pBus->state = SrxlState_Running;
            break;
        }
        case SrxlState_SendBoundDataReport:
        {
            srxlSend(pBus, SRXL_CMD_BIND_INFO, pBus->fullID.deviceID);
            pBus->state = SrxlState_Running;
            break;
        }
        case SrxlState_Running:
        default:
        {
            return;
        }
        }
    }
#ifdef SRXL_INCLUDE_MASTER_CODE
    else
    {
        srxlRunMaster(pBus);
    }
#endif  // SRXL_INCLUDE_MASTER_CODE
}

/**
    @brief  Tell the "best" receiver to enter bind mode, either locally or via SRXL command

    @param  bindType: One of the possible bind status types to use when binding -- NOTE: The transmitter may ignore this
    @param  broadcast: True if this is a local request that should tell all connected receivers to enter bind
    @return bool: True if a receiver was told to enter bind mode; else false
*/
bool srxlEnterBind(uint8_t bindType, bool broadcast)
{
    srxlRx.pBindRcvr = 0;
    if(broadcast && srxlThisDev.pRcvr)
    {
        srxlRx.pBindRcvr = srxlThisDev.pRcvr;
    }
    else if(srxlRx.pTelemRcvr)
    {
        srxlRx.pBindRcvr = srxlRx.pTelemRcvr;
    }
    else if(srxlRx.rcvrCount > 0 && srxlRx.rcvrSorted[0]->deviceID < 0x30)
    {
        srxlRx.pBindRcvr = srxlRx.rcvrSorted[0];
    }

    if(srxlRx.pBindRcvr)
    {
#ifdef SRXL_INCLUDE_MASTER_CODE
        // Local bind
        if(srxlRx.pBindRcvr == srxlThisDev.pRcvr)
        {
            srxlBindInfo.type = bindType;
            srxlBindInfo.options = SRXL_BIND_OPT_BIND_TX_ENABLE;
            srxlBindInfo.guid = 0;
            srxlBindInfo.uid = 0;
            if(srxlRx.pBindRcvr == srxlRx.pTelemRcvr)
                srxlBindInfo.options |= SRXL_BIND_OPT_TELEM_TX_ENABLE;
            srxlTryToBind(srxlBindInfo);
            if(broadcast)
            {
                uint8_t b;
                for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
                {
                    srxlBus[b].txFlags.enterBind = 1;
                }
            }
            return true;
        }
#endif // SRXL_INCLUDE_MASTER_CODE

        // Remote bind
        uint8_t i;
        for(i = 0; i < SRXL_NUM_OF_BUSES; ++i)
        {
            if((1u << i) & srxlRx.pBindRcvr->busBits)
            {
                srxlBus[i].txFlags.enterBind = 1;
                return true;
            }
        }
    }

    return false;
}

/**
    @brief  Public function to set bind info for the system, either locally or via SRXL commands

    @param  bindType: Type of bind requested for this receiver or all receivers
    @param  guid: Transmitter GUID to bind the receiver to
    @param  uid: Unique ID provided by transmitter upon initial bind (can be 0 if unknown)
    @return bool: True if bind info was successfully set for the destination device; else false
*/
bool srxlSetBindInfo(uint8_t bindType, uint64_t guid, uint32_t uid)
{
    if(guid == 0)
        return false;

    // Set bind info, with options defaulted to 0
    srxlBindInfo.type = bindType;
    srxlBindInfo.options = SRXL_BIND_OPT_US_POWER; // Request US power, with no guarantee it's supported
    srxlBindInfo.guid = guid;
    srxlBindInfo.uid = uid ? uid : srxlThisDev.uid;

#ifdef SRXL_INCLUDE_MASTER_CODE
    // If we are a receiver
    if(srxlThisDev.pRcvr)
    {
        // Bind locally, which will result in no further packets since options == 0
        srxlTryToBind(srxlBindInfo);
    }
#endif

    // Broadcast this bind info on all SRXL buses
    uint8_t b;
    for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
    {
        srxlBus[b].txFlags.broadcastBindInfo = 1;
    }

    return true;
}

/**
    @brief  Public function to call from the user UART code when a UART frame error occurs

    @param  busIndex: Index of SRXL bus state information entry in the srxlBus array
*/
void srxlOnFrameError(uint8_t busIndex)
{
    SrxlBus* pBus = &srxlBus[busIndex];
    if(busIndex >= SRXL_NUM_OF_BUSES || !pBus->initialized)
        return;

    ++(pBus->frameErrCount);
    if(pBus->master)
    {
        // TODO: If master (i.e. remote receiver 0x10), cause break condition to force reset?
        return;
    }

    if(pBus->state == SrxlState_ListenOnStartup || pBus->state == SrxlState_ListenForHandshake)
    {
        // Wait for multiple frame breaks before trying to change baud rate
        if(pBus->frameErrCount < 3)
            return;

        // Try the next higher baud rate
        switch(pBus->baudRate)
        {
        case SRXL_BAUD_115200:
        {
            if(pBus->baudSupported & SRXL_BAUD_400000)
            {
                srxlChangeBaudRate(pBus->uart, 400000);
                pBus->baudRate = SRXL_BAUD_400000;
                break;
            }
            FALLTHROUGH;
            // else fall thru...
        }
        case SRXL_BAUD_400000:
        default:
        {
            // TODO: Cause break condition to force reset of everyone? Or just keep cycling?

            srxlChangeBaudRate(pBus->uart, 115200);
            pBus->baudRate = SRXL_BAUD_115200;
            break;
        }
        }
        pBus->frameErrCount = 0;
    }
    else
    {
        // TODO: Handle frame error during normal comm -- probably caused by collision on bus?
    }
}

SrxlFullID srxlGetTelemetryEndpoint(void)
{
    SrxlFullID retVal = {0};
    if(srxlRx.pTelemRcvr)
    {
        retVal.deviceID = srxlRx.pTelemRcvr->deviceID;
        retVal.busIndex = srxlRx.pTelemRcvr->busBits;
    }
    return retVal;
}

bool srxlSetVtxData(SrxlVtxData* pVtxData)
{
    if(!pVtxData)
        return false;

    // Update VTX data, ignoring values marked as unchanged
    if(pVtxData->band != 0xFF)
        srxlVtxData.band = pVtxData->band;
    if(pVtxData->channel != 0xFF)
        srxlVtxData.channel = pVtxData->channel;
    if(pVtxData->pit != 0xFF)
        srxlVtxData.pit = pVtxData->pit;
    if(pVtxData->power != 0xFF || pVtxData->powerDec != 0xFFFF)
        srxlVtxData.power = pVtxData->power;
    if(pVtxData->powerDec != 0xFFFF)
        srxlVtxData.powerDec = pVtxData->powerDec;
    if(pVtxData->region != 0xFF)
        srxlVtxData.region = pVtxData->region;

    uint8_t b;
    for(b = 0; b < SRXL_NUM_OF_BUSES; ++b)
    {
        srxlBus[b].txFlags.sendVtxData = 1;
    }

    return true;
}

void srxlSetHoldThreshold(uint8_t countdownReset)
{
    srxlRx.lossHoldCount = (countdownReset > 1) ? countdownReset : 45;
}

void srxlClearCommStats(void)
{
    srxlRx.holds = 0;
    srxlRx.frameLosses = 0;
    srxlRx.lossCountdown = srxlRx.lossHoldCount + 1;
}

// Return true on failsafe hold
bool srxlUpdateCommStats(bool isFade)
{
    srxlRx.rxBusBits = 0;
    if(srxlTelemetryPhase)
    {
        srxlRx.bestRssi_dBm = -128;
        srxlRx.bestRssi_Pct = 0;
    }

    uint8_t i;
    for(i = 0; i < srxlRx.rcvrCount; ++i)
    {
        if(srxlRx.rcvr[i].channelMask)
        {
            srxlRx.lossCountdown = srxlRx.lossHoldCount + 1;

            if((srxlRx.rcvr[i].rssiRcvd & RSSI_RCVD_DBM) && srxlRx.bestRssi_dBm < srxlRx.rcvr[i].rssi_dBm)
                srxlRx.bestRssi_dBm = srxlRx.rcvr[i].rssi_dBm;
            if((srxlRx.rcvr[i].rssiRcvd & RSSI_RCVD_PCT) && srxlRx.bestRssi_dBm < srxlRx.rcvr[i].rssi_Pct)
                srxlRx.bestRssi_Pct = srxlRx.rcvr[i].rssi_Pct;
        }
    }

    // Set RSSI based on telemetry phase and type of telemetry received
    srxlChData.rssi = (srxlTelemetryPhase || srxlChDataIsFailsafe) ? srxlRx.bestRssi_Pct : srxlRx.bestRssi_dBm;

    // Update flight log frame losses and holds
    if(isFade && srxlRx.lossCountdown)
    {
        if(--srxlRx.lossCountdown == 0)
        {
            ++srxlRx.holds;
            srxlRx.frameLosses -= srxlRx.lossHoldCount;
        }
        else
        {
            ++srxlRx.frameLosses;
        }
    }

    static uint8_t telemFadeCount = 0;
    // If we are allowed to send telemetry by the device downstream (i.e. any slave device)
    if(srxlRx.pTelemRcvr)
    {
        if(srxlRx.pTelemRcvr->channelMask == 0 || (srxlRx.pTelemRcvr->info & SRXL_DEVINFO_TELEM_FULL_RANGE) == 0)
        {
            // If our telemetry receiver missed channel data 3 frames in a row, switch
            if(++telemFadeCount > 3)
            {
                srxlRx.pTelemRcvr = srxlChooseTelemRcvr();
#ifdef SRXL_INCLUDE_MASTER_CODE
                srxlSetTelemetryTxEnable(srxlRx.pTelemRcvr && (srxlRx.pTelemRcvr == srxlThisDev.pRcvr));
#endif
                telemFadeCount = 0;
            }
        }
        else
        {
            telemFadeCount = 0;
        }
    }
#ifdef SRXL_INCLUDE_MASTER_CODE
    // Else check to make sure we're still supposed to suppress telemetry (reset countdown when slave tells us not to send again)
    else if(++srxlTelemSuppressCount > SRXL_TELEM_SUPPRESS_MAX)
    {
        // Enable this device's telemetry tx since we stopped being told not to
        srxlRx.pTelemRcvr = srxlThisDev.pRcvr;
        srxlSetTelemetryTxEnable(srxlRx.pTelemRcvr);
    }
#endif

    // Return true while we're in hold condition (failsafe)
    return srxlRx.lossCountdown == 0;
}