Basic MAC data interface for LoRa transceiver

Dependencies:   L2Frame crc

Dependents:   LoRaBaseStation LoRaTerminal

AlohaTransceiver.cpp

Committer:
rba90
Date:
2016-09-14
Revision:
41:37c1d616a848
Parent:
40:271fa9e98589

File content as of revision 41:37c1d616a848:

#include "AlohaTransceiver.h"
#include "mbed.h"
#include "radio.h"
#include "debug.h"
#include "AlohaFrame.h"
#include "RingBuffer.h"

#define ALLOW_CALLBACK_DEBUG  1
#define CSMA_BACKOFF_BASE 900 // in ms
#define CSMA_BACKOFF_RANGE 500 // in ms
#define CSMA_CA_CHANNEL_THRESHOLD -80 // in dbm

#define SET_FLAG(t, x) (t) |= 1 << (x)
#define CLEAR_FLAG(t, x) (t) &= ~(1 << (x))
#define TOGGLE_FLAG(t, x) (t) ^= 1 << (x)
#define CHECK_FLAG(t, x) (t) >> (x) & 1


// declear the type of radio state
typedef enum
{
    LOWPOWER = 0,
    IDLE,

    RX,
    RX_TIMEOUT,
    RX_ERROR,

    TX,
    TX_TIMEOUT,

    CAD,
    CAD_DONE
}AppStates_t;

// radio driver related variables
static int16_t RssiValue;
static int8_t SnrValue;

static volatile AppStates_t State;
static RadioEvents_t RadioEvents;

// rx queue
CircularBuffer<AlohaFrame *> AlohaRxQueue(10);

// callback functions for radio driver
void OnTxDone();
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
void OnTxTimeout();
void OnRxTimeout();
void OnRxError();
void OnFhssChangeChannel( uint8_t channelIndex );
void OnCadDone();

// radio driver
#ifdef DRIVER_SX1276
SX1276MB1xAS Radio( NULL );
#endif

#ifdef DRIVER_INAIR
SX1276inAir Radio( NULL );
#endif



/*
 * Abstract interface for accessing radio driver
 */
AlohaTransceiver::AlohaTransceiver(uint8_t id):
    AlohaTxQueue(32)
{
    // store unique device id
    deviceId = id;
    
    // initialize sequenceid
    memset(seqid, 0x0, sizeof(seqid));
    
    // clear tx done state
    isTxDone = true;
    
    // assume all station is clear to transmit
    isAcked = 0xffff;
    
    // reset CSMA backoff timer
    CSMABackoffTimer.reset();
    
    // reset CSMA backoff state
    isBackoff = false;
    
    // set default CSMA backoff period
    CSMABackoffPeriod = 1000;
    
    
    // configure properties
#if USE_MODEM_LORA == 1
    Settings.Power = TX_OUTPUT_POWER;
    Settings.Bandwidth = LORA_BANDWIDTH;
    Settings.Datarate = LORA_SPREADING_FACTOR;
    Settings.Coderate = LORA_CODINGRATE;
    Settings.PreambleLen = LORA_PREAMBLE_LENGTH;
    Settings.SymbolTimeout = LORA_SYMBOL_TIMEOUT;
    Settings.FixLen = LORA_FIX_LENGTH_PAYLOAD_ON;
    Settings.PayloadLen = 0;
    Settings.CrcOn = LORA_CRC_ENABLED;
    Settings.FreqHopOn = LORA_FHSS_ENABLED;
    Settings.HopPeriod = LORA_NB_SYMB_HOP;
    Settings.IqInverted = LORA_IQ_INVERSION_ON;
    Settings.RxContinuous = true;
    Settings.TxTimeout = TX_TIMEOUT_VALUE;

#elif USE_MODEM_FSK == 1
    // TODO: Complete settings for FSK mode
    #error "FSK not implemented"
#else
    #error "Please define a modem in the compiler options."
#endif
}

AlohaTransceiver::~AlohaTransceiver()
{

}

void AlohaTransceiver::boardInit()
{
    // configure callback functions
    RadioEvents.TxDone = OnTxDone;
    RadioEvents.RxDone = OnRxDone;
    RadioEvents.RxError = OnRxError;
    RadioEvents.TxTimeout = OnTxTimeout;
    RadioEvents.RxTimeout = OnRxTimeout;
    Radio.Init( &RadioEvents );

    // verify the connection with the board
    while( Radio.Read( REG_VERSION ) == 0x00  )
    {
#ifdef DEBUG_ALOHA
        debug( "Radio could not be detected!\n\r" );
#endif
        wait( 1 );
    }
#ifdef DEBUG_ALOHA
    printf("RadioRegVersion: %d\r\n", Radio.Read( REG_VERSION ));
#endif
}

void AlohaTransceiver::updateSettings()
{
    Radio.SetChannel( RF_FREQUENCY );
#if USE_MODEM_LORA == 1

    Radio.SetTxConfig( MODEM_LORA, Settings.Power, 0, Settings.Bandwidth,
                         Settings.Datarate, Settings.Coderate,
                         Settings.PreambleLen, Settings.FixLen,
                         Settings.CrcOn, Settings.FreqHopOn, Settings.HopPeriod,
                         Settings.IqInverted, Settings.TxTimeout );

    Radio.SetRxConfig( MODEM_LORA, Settings.Bandwidth, Settings.Datarate,
                         Settings.Coderate, 0, Settings.PreambleLen,
                         Settings.SymbolTimeout, Settings.FixLen, Settings.PayloadLen,
                         Settings.CrcOn, Settings.FreqHopOn, Settings.HopPeriod,
                         Settings.IqInverted, Settings.RxContinuous );

#elif USE_MODEM_FSK == 1
    #error "FSK not implemented"
#else
    #error "Please define a modem in the compiler options."
#endif
}

void AlohaTransceiver::enable()
{
    // entering passive receiver mode
    Radio.Rx( 0 );
}

void AlohaTransceiver::poll()
{
    switch( State )
    {
        case RX:
        {   
            // process packet if received
            // it is noticed that the rx handler does not queue the received packet
            // due to the hardware limitation
            while (AlohaRxQueue.getCounter() > 0)
            {
                // pop from queue
                AlohaFrame *frame = AlohaRxQueue.dequeue();

                // check destination
                // if the destination is the device id, then processing, otherwise drop the packet and continue
                // listening
                if (frame->getDestinationAddress() == (deviceId & 0x0f))
                {
                    uint8_t type = frame->getType();
                    
                    // schedule the ack frame immediatly after the data frame is received
                    if (type == AlohaFrame::Aloha_Data)
                    {
#ifdef DEBUG_ALOHA
                        printf("\r\nRXDATA::SRC_ADDR:0x%x,SEQID:0x%x\r\n", frame->getSourceAddress(), frame->getSequenceID());
#endif 
                        sendAck(frame);
                    }
                    else if (type == AlohaFrame::Aloha_ACK)
                    {
#ifdef DEBUG_ALOHA
                        printf("\r\nRXACK::SRC_ADDR:0x%x,SEQID:0x%x\r\n", frame->getSourceAddress(), frame->getSequenceID());
#endif 
                        // clear the ack state
                        uint8_t src_addr = frame->getSourceAddress();
                        uint8_t foreign_seqid = frame->getSequenceID();
                        
                        // compare the local sequence id and foreign sequence id for specific host
                        if (seqid[src_addr] <= foreign_seqid)
                        {
                            setAckedFlag(src_addr);
                        }
#ifdef DEBUG_ALOHA
                        printf("ACK::frame_seqid: %d, local_seqid: %d\r\n", frame->getSequenceID(), seqid[frame->getSourceAddress()]);
#endif
                    }
                
                    // check registered callback function
                    // execute callback functions if registered
                    if (AlohaTypeCallbackTable[type] != NULL)
                    {
                        uint8_t payload_length = frame->getPayloadLength();
                        uint8_t payload[payload_length];
                        uint8_t src_addr = frame->getSourceAddress();
                    
                        // extract payload
                        for (uint8_t i = 0; i < payload_length; i++)
                        {
                            payload[i] = frame->getPayload(i);
                        }
                        
                        // execute callback function
                        AlohaTypeCallbackTable[type](payload, payload_length, src_addr);
                    }
                }
                
                // free memory
                delete frame;
            }

            Radio.Rx( 0 );
            State = LOWPOWER;
            break;
        }
        case TX:
        {
            // set tx to done, allow next transmission
            setTxDoneFlag();
            
            Radio.Rx( 0 );
            State = LOWPOWER;
            break;
        }
        case RX_TIMEOUT:
        {
            Radio.Rx( 0 );
            State = LOWPOWER;
            break;
        }
        case RX_ERROR:
        {
            Radio.Rx( 0 );
            State = LOWPOWER;
            break;
        }
        case TX_TIMEOUT:
        {
            // set tx to done, allow next transmission
            setTxDoneFlag();
            
            Radio.Rx( 0 );
            State = LOWPOWER;
            break;
        }
        case LOWPOWER:
        {
            // transmit packet when the radio is free
            if (getTxDoneFlag())
            {
                // find next available packet to transmit
                if ( AlohaTxQueue.getCounter() > 0)
                {
                    // perform CSMA backoff routine
                    if (isBackoff == true) // if the transceiver is already in the backoff state, then wait until timer expires
                    {
                        if (CSMABackoffTimer.read_ms() > CSMABackoffPeriod)
                        {
                            isBackoff = false;
                            CSMABackoffTimer.stop();
                        }
                    }
                    
                    
                    if (isBackoff == false) // if the transceiver is not in backoff state, then attempt to transmit
                    {
                        if (Radio.IsChannelFree(MODEM_LORA, RF_FREQUENCY, CSMA_CA_CHANNEL_THRESHOLD))
                        {
#ifdef DEBUG_ALOHA
                            printf(" CSMA/CA::Channel is free, ready to transmit\r\n");
#endif 
                            AlohaFrame *frame = AlohaTxQueue.dequeue();

                            // determined by the type of the frame, only data frame need to be cleared before transmitting
                            switch (frame->getType())
                            {
                                // Data frame need proper clearance
                                case AlohaFrame::Aloha_Data:
                                {
                                    uint8_t dest_addr = frame->getDestinationAddress();
                                    
                                    // depending on the availability of the destination host,
                                    // the radio will on transmit the packet when acked.
                                    // 
                                    // in the test build, we are not going to enable this feature
                                    // if (getAckedFlag(dest_addr))
                                    if (true)
                                    {
                                        sendFrame(frame);
                                        
                                        // block the next transmission until previous transmission is done
                                        clearTxDoneFlag();
                                        
                                        // block the next transmission of the same host until an acked packet
                                        // is received
                                        clearAckedFlag(dest_addr);
                                        
                                        // free memory
                                        delete frame;
                                    }
                                    
                                    // otherwise put the packet back to the end of queue and wait for its being acked
                                    else
                                    {
                                        AlohaTxQueue.enqueue(frame);
                                    }
                                    break;
                                }
                                
                                default:
                                {
                                    sendFrame(frame);
                                    
                                    // block the next transmission until previous transmission is done
                                    clearTxDoneFlag();
                                        
                                    // free memory
                                    delete frame;
                                    
                                    break;
                                }
                            }
                        }
                        else // if channel if not free, then start the timer, set the backoff state to true
                        {
                            isBackoff = true;
                            
                            // generate random backoff delay
                            CSMABackoffPeriod = Radio.Random() % CSMA_BACKOFF_RANGE + CSMA_BACKOFF_BASE;
                            
#ifdef DEBUG_ALOHA
                            printf("CSMA/CA::Channel is not free, wait for %d ms\r\n", CSMABackoffPeriod);
#endif
                            
                            CSMABackoffTimer.reset();
                            CSMABackoffTimer.start();
                        }
                    }
                }
            }
            break;
        }
        default:
        {
            State = LOWPOWER;
            break;
        }
    }
}

void AlohaTransceiver::sendFrame(AlohaFrame *frame)
{
    // create a buffer for transmit
    uint8_t frame_length = frame->getPayloadLength() + FIXED_BYTE;
    uint8_t buffer[frame_length];         // 4 fix fields
    memset(buffer, 0x0, sizeof(buffer));
    
    // copy content to buffer
    frame->serialize(buffer);
    
    // send to radio 
    Radio.Send(buffer, frame_length);
}

bool AlohaTransceiver::send(BasicPacket *packet)
{   
    // for the reason that we only transmit basic packet format, the 
    // length is always 8
    uint8_t payload_length = 8;
    
    // get destination address
    uint8_t destination_address = findNextHop(packet->getDestinationID());
    
    // serialize the packet from upper layer
    uint8_t buffer[payload_length];
    memset(buffer, 0x0, sizeof(buffer));
    
    // copy bytes into buffer
    packet->serialize(buffer);
    
    // create a new frame
    AlohaFrame *frame = new AlohaFrame();
    
    // set properfies
    // set payload as data
    frame->setType(AlohaFrame::Aloha_Data);
    
    // set payload length (8 bytes for BasicPacket)
    frame->setPayloadLength(payload_length);
    
    // set mac layer device id
    frame->setSourceAddress(deviceId);
    
    // set mac layer destination id
    // for multiple hop system, the destination is the next hop
    // in this case, we use destination id from upper layer
    frame->setDestinationAddress(packet->getDestinationID());
    
    // set full message flag (always true in this case)
    frame->setFullMessageFlag(0x1);
    
    // use dest_addr as key for accessing sequence id
    // the seqid should increase as it successfully transmit the packet
    frame->setSequenceID(seqid[destination_address]++);    
                                                
    // set payload
    for (uint8_t i = 0; i < payload_length; i++)
    {
        frame->setPayload(i, buffer[i]);
    }
    
    // calculate crc
    frame->generateCrc();
    
    // push frame to the back of queue
    AlohaTxQueue.enqueue(frame);
    
    // debug
#ifdef DEBUG_ALOHA
    printf("\r\nTXDATA::DEST_ADDR:0x%x,SEQID:0x%x\r\n", frame->getDestinationAddress(), frame->getSequenceID());
#endif                
    return true;
}

void AlohaTransceiver::sendAck(AlohaFrame *inFrame)
{
    // create a new frame by calling it's constructor
    AlohaFrame *outFrame = new AlohaFrame();
    
    // set frame type
    outFrame->setType(AlohaFrame::Aloha_ACK);
    
    // set payload length (0 byte payload for ack frame)
    outFrame->setPayloadLength(0);
    
    // set mac layer device id
    outFrame->setSourceAddress(deviceId);
    
    // set mac layer destination id
    outFrame->setDestinationAddress(inFrame->getSourceAddress());
    
    // set full message flag
    outFrame->setFullMessageFlag(0x1);
    
    // set seqid
    // the sequence id is always the source id + 1
    outFrame->setSequenceID(inFrame->getSequenceID() + 1);
    
    // no payload
    
    // generate cec
    outFrame->generateCrc();
    
    // push frame to the back of queue
    AlohaTxQueue.enqueue(outFrame);
    
    // debug
#ifdef DEBUG_ALOHA
    printf("\r\nTXACK::DEST_ADDR:0x%x,SEQID:0x%x\r\n", outFrame->getDestinationAddress(), outFrame->getSequenceID());
#endif
}

bool AlohaTransceiver::getTxDoneFlag()
{
    return isTxDone;
}

void AlohaTransceiver::setTxDoneFlag()
{
    isTxDone = true;
}

void AlohaTransceiver::clearTxDoneFlag()
{
    isTxDone = false;
}

bool AlohaTransceiver::getAckedFlag(uint8_t addr)
{
    return CHECK_FLAG(isAcked, addr);
}

void AlohaTransceiver::setAckedFlag(uint8_t addr)
{
    SET_FLAG(isAcked, addr);
}

void AlohaTransceiver::clearAckedFlag(uint8_t addr)
{
    CLEAR_FLAG(isAcked, addr);
}

void AlohaTransceiver::registerType(AlohaFrame::AlohaType_t type, aloha_callback_func f)
{
    AlohaTypeCallbackTable[type] = f;
}

void AlohaTransceiver::deRegisterType(AlohaFrame::AlohaType_t type, aloha_callback_func f)
{
    AlohaTypeCallbackTable[type] = NULL;
}

int16_t AlohaTransceiver::getRssi()
{
    return RssiValue;
}

int8_t AlohaTransceiver::getSnr()
{
    return SnrValue;
}

uint8_t AlohaTransceiver::getDeviceID()
{
    return deviceId;
}

void AlohaTransceiver::setDeviceID(uint8_t id)
{
    deviceId = id;
}

uint8_t AlohaTransceiver::findNextHop(uint8_t addr)
{
    // TODO: maintain a routing lookup table for choosing shortest path
    return addr;
}

#if USE_MODEM_LORA == 1
AlohaTransceiver::LoRaSettings_t *AlohaTransceiver::getSettings()
{
    return &Settings;
}
        
#elif USE_MODEM_FSK == 1
AlohaTransceiver::FskSettings_t *AlohaTransceiver::getSettings()
{
    return &Settings;
}
#else
    #error "Please define a modem in the compiler options."
#endif

void OnTxDone( void )
{
    Radio.Sleep( );
    State = TX;
    
#ifdef DEBUG_ALOHA
    debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnTxDone\n\r" );
#endif
}

void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{
    uint8_t payload_length;
    
    Radio.Sleep( );

    // safeguard: if size exceeded maximum buffer size, it will cause memory overflow
    payload_length = size ? BUFFER_SIZE : size <= BUFFER_SIZE;
    
    // create a new frame instance
    AlohaFrame *frame = new AlohaFrame(payload, payload_length);
    
    // push onto the end of queue
    AlohaRxQueue.enqueue(frame);

    RssiValue = rssi;
    SnrValue = snr;
    State = RX;
    
#ifdef DEBUG_ALOHA
    debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnRxDone, RSSI=%d, SNR=%d\n\r", rssi, snr );
#endif
}

void OnTxTimeout( void )
{
    Radio.Sleep( );
    State = TX_TIMEOUT;
    
#ifdef DEBUG_ALOHA
    debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnTxTimeout\r\n" );
#endif
}

void OnRxTimeout( void )
{
    Radio.Sleep( );
    State = RX_TIMEOUT;
    
#ifdef DEBUG_ALOHA
    debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnRxTimeout\r\n" );
#endif
}

void OnRxError( void )
{
    Radio.Sleep( );
    State = RX_ERROR;
    
#ifdef DEBUG_ALOHA
    debug_if(ALLOW_CALLBACK_DEBUG, "RADIO::OnRxError\r\n" );
#endif
}