A mbed library for the RN2483. Heavily based on the Sodaq_RN2483 library for Arduino (https://github.com/SodaqMoja/Sodaq_RN2483). This is currently under-going initial testing, but seems to work! Tested on a NRF51 and FRDM K64F.

Dependents:   rn2483-TestProgram

RN2483.cpp

Committer:
azazeal88
Date:
2016-11-08
Revision:
0:a8609e6f88f3
Child:
1:cf9b0c21907a

File content as of revision 0:a8609e6f88f3:

#include "RN2483.h"
#include "StringLiterals.h"
#include "Utils.h"

// Structure for mapping error response strings and error codes.
typedef struct StringEnumPair
{
    const char* stringValue;
    uint8_t enumValue;
} StringEnumPair_t;


// Creates a new RN2483 instance.
RN2483::RN2483(PinName tx, PinName rx) : 
    _RN2483(tx, rx, getDefaultBaudRate()),
    inputBufferSize(DEFAULT_INPUT_BUFFER_SIZE),
    receivedPayloadBufferSize(DEFAULT_RECEIVED_PAYLOAD_BUFFER_SIZE),
    packetReceived(false),
    isRN2903(false) 
{
#ifdef USE_DYNAMIC_BUFFER
    this->isBufferInitialized = false;
#endif
    
}
 
// Takes care of the init tasks common to both initOTA() and initABP.
void RN2483::init()
{
#ifdef USE_DYNAMIC_BUFFER
    // make sure the buffers are only initialized once
    if (!isBufferInitialized) {
        this->inputBuffer = static_cast<char*>(malloc(this->inputBufferSize));
        this->receivedPayloadBuffer = static_cast<char*>(malloc(this->receivedPayloadBufferSize));
        isBufferInitialized = true;
    }
#endif
    // make sure the module's state is synced and woken up
    sleep();
    wakeUp();
}

// Initializes the device and connects to the network using Over-The-Air Activation.
// Returns true on successful connection.
bool RN2483::initOTA(const uint8_t devEUI[8], const uint8_t appEUI[8], const uint8_t appKey[16], bool adr)
{
    init();
    return resetDevice() &&
        setMacParam(STR_DEV_EUI, devEUI, 8) &&
        setMacParam(STR_APP_EUI, appEUI, 8) &&
        setMacParam(STR_APP_KEY, appKey, 16) &&
        setMacParam(STR_ADR, BOOL_TO_ONOFF(adr)) &&
        joinNetwork(STR_OTAA);
}

// Initializes the device and connects to the network using Activation By Personalization.
// Returns true on successful connection.
bool RN2483::initABP(const uint8_t devAddr[4], const uint8_t appSKey[16], const uint8_t nwkSKey[16], bool adr)
{
    init();
    return resetDevice() &&
        setMacParam(STR_DEV_ADDR, devAddr, 4) &&
        setMacParam(STR_APP_SESSION_KEY, appSKey, 16) &&
        setMacParam(STR_NETWORK_SESSION_KEY, nwkSKey, 16) &&
        setMacParam(STR_ADR, BOOL_TO_ONOFF(adr)) &&
        joinNetwork(STR_ABP);
}
    
// Sends the given payload without acknowledgement.
// Returns 0 (NoError) when transmission is successful or one of the MacTransmitErrorCodes otherwise.
uint8_t RN2483::send(uint8_t port, const uint8_t* payload, uint8_t size)
{
    return macTransmit(STR_UNCONFIRMED, port, payload, size);
}

// Sends the given payload with acknowledgement.
// Returns 0 (NoError) when transmission is successful or one of the MacTransmitErrorCodes otherwise.
uint8_t RN2483::sendReqAck(uint8_t port, const uint8_t* payload,
    uint8_t size, uint8_t maxRetries)
{
    return macTransmit(STR_CONFIRMED, port, payload, size);
}

// Copies the latest received packet (optionally starting from the "payloadStartPosition"
// position of the payload) into the given "buffer", up to "size" number of bytes.
// Returns the number of bytes written or 0 if no packet is received since last transmission.
uint16_t RN2483::receive(uint8_t* buffer, uint16_t size,
    uint16_t payloadStartPosition)
{

    if (!this->packetReceived) {
        return 0;
    }

    uint16_t inputIndex = payloadStartPosition * 2; // payloadStartPosition is in bytes, not hex char pairs
    uint16_t outputIndex = 0;

    // check that the asked starting position is within bounds
    if (inputIndex >= this->receivedPayloadBufferSize) {
        return 0;
    }

    // stop at the first string termination char, or if output buffer is over, or if payload buffer is over
    while (outputIndex < size
        && inputIndex + 1 < this->receivedPayloadBufferSize
        && this->receivedPayloadBuffer[inputIndex] != 0
        && this->receivedPayloadBuffer[inputIndex + 1] != 0) {
        buffer[outputIndex] = HEX_PAIR_TO_BYTE(
            this->receivedPayloadBuffer[inputIndex],
            this->receivedPayloadBuffer[inputIndex + 1]);

        inputIndex += 2;
        outputIndex++;
    }

    // Note: if the payload has an odd length, the last char is discarded

    buffer[outputIndex] = 0; // terminate the string

    return outputIndex;
}

// Gets the preprogrammed EUI node address from the module.
// Returns the number of bytes written or 0 in case of error.
uint8_t RN2483::getHWEUI(uint8_t* buffer, uint8_t size)
{
    _RN2483.printf(STR_CMD_GET_HWEUI);
    _RN2483.printf(CRLF);

    // TODO move to general "read hex" method
    uint8_t inputIndex = 0;
    uint8_t outputIndex = 0;
    Timer t;
    t.start();

    unsigned long start = t.read_ms ();
    while (t.read_ms () < start + DEFAULT_TIMEOUT) {
        if (readLn() > 0) {
            while (outputIndex < size
                && inputIndex + 1 < this->inputBufferSize
                && this->inputBuffer[inputIndex] != 0
                && this->inputBuffer[inputIndex + 1] != 0) {
                buffer[outputIndex] = HEX_PAIR_TO_BYTE(
                    this->inputBuffer[inputIndex],
                    this->inputBuffer[inputIndex + 1]);
                inputIndex += 2;
                outputIndex++;
            }
            t.stop();
            return outputIndex;
        }
    }
    t.stop();     
    return 0;
}

#ifdef ENABLE_SLEEP

void RN2483::wakeUp()
{
    // "emulate" break condition
    _RN2483.send_break();
    // set baudrate
    _RN2483.baud(getDefaultBaudRate());
    _RN2483.putc((uint8_t)0x55);
}

void RN2483::sleep()
{
    _RN2483.printf(STR_CMD_SLEEP);
    _RN2483.printf(CRLF);
}


#endif

// Reads a line from the device stream into the "buffer" starting at the "start" position of the buffer.
// Returns the number of bytes read.
uint16_t RN2483::readLn(char* buffer, uint16_t size, uint16_t start)
{
    int len = readBytesUntil('\n', buffer + start, size);
    if (len > 0) {
        this->inputBuffer[start + len - 1] = 0; // bytes until \n always end with \r, so get rid of it (-1)
    }

    return len;
}

// Waits for the given string. Returns true if the string is received before a timeout.
// Returns false if a timeout occurs or if another string is received.
bool RN2483::expectString(const char* str, uint16_t timeout)
{
    Timer t;
    t.start();
    unsigned long start = t.read_ms();
    while (t.read_ms() < start + timeout) {
        if (readLn() > 0) {
            if (strstr(this->inputBuffer, str) != NULL) {
                t.stop();
                return true;
            }
             t.stop();
            return false;
        }
    }
    t.stop();
    return false;
}

bool RN2483::expectOK()
{
    return expectString(STR_RESULT_OK);
}

// Sends a reset command to the module and waits for the success response (or timeout).
// Also sets-up some initial parameters like power index, SF and FSB channels.
// Returns true on success.
bool RN2483::resetDevice()
{
    _RN2483.printf(STR_CMD_RESET);
    _RN2483.printf(CRLF);
    if (expectString(STR_DEVICE_TYPE_RN)) {
        if (strstr(this->inputBuffer, STR_DEVICE_TYPE_RN2483) != NULL) {
            isRN2903 = false;
            return setPowerIndex(DEFAULT_PWR_IDX_868) &&
                setSpreadingFactor(DEFAULT_SF_868);
        }
        else if (strstr(this->inputBuffer, STR_DEVICE_TYPE_RN2903) != NULL) {
            // TODO move into init once it is decided how to handle RN2903-specific operations
            isRN2903 = true;

            return setFsbChannels(DEFAULT_FSB) &&
                setPowerIndex(DEFAULT_PWR_IDX_915) &&
                setSpreadingFactor(DEFAULT_SF_915);
        }
        else {
            return false;
        }
    }
    return false;
}

// Enables all the channels that belong to the given Frequency Sub-Band (FSB)
// and disables the rest.
// fsb is [1, 8] or 0 to enable all channels.
// Returns true if all channels were set successfully.
bool RN2483::setFsbChannels(uint8_t fsb)
{
    uint8_t first125kHzChannel = fsb > 0 ? (fsb - 1) * 8 : 0;
    uint8_t last125kHzChannel = fsb > 0 ? first125kHzChannel + 7 : 71;
    uint8_t fsb500kHzChannel = fsb + 63;

    bool allOk = true;
    for (uint8_t i = 0; i < 72; i++) {
        _RN2483.printf(STR_CMD_SET_CHANNEL_STATUS);
        _RN2483.printf("%u",i);
        _RN2483.printf(" ");
        _RN2483.printf(BOOL_TO_ONOFF(((i == fsb500kHzChannel) || (i >= first125kHzChannel && i <= last125kHzChannel))));
        _RN2483.printf(CRLF);

        allOk &= expectOK();
    }

    return allOk;
}

// Sets the spreading factor.
// In reality it sets the datarate of the module according to the
// LoraWAN specs mapping for 868MHz and 915MHz, 
// using the given spreading factor parameter.
bool RN2483::setSpreadingFactor(uint8_t spreadingFactor)
{
    int8_t datarate;
    if (!isRN2903) {
        // RN2483 SF(DR) = 7(5), 8(4), 9(3), 10(2), 11(1), 12(0)
        datarate = 12 - spreadingFactor;
    }
    else {
        // RN2903 SF(DR) = 7(3), 8(2), 9(1), 10(0)
        datarate = 10 - spreadingFactor;
    }

    if (datarate > -1) {
        return setMacParam(STR_DATARATE, datarate);
    }

    return false;
}

// Sets the power index (868MHz: 1 to 5 / 915MHz: 5, 7, 8, 9 or 10)
// Returns true if succesful.
bool RN2483::setPowerIndex(uint8_t powerIndex)
{
    return setMacParam(STR_PWR_IDX, powerIndex);
}

// Sends the command together with the given paramValue (optional)
// to the device and awaits for the response.
// Returns true on success.
// NOTE: command should include a trailing space if paramValue is set
bool RN2483::sendCommand(const char* command, const uint8_t* paramValue, uint16_t size)
{
    _RN2483.printf(command);

    for (uint16_t i = 0; i < size; ++i) {
        _RN2483.putc(static_cast<char>(NIBBLE_TO_HEX_CHAR(HIGH_NIBBLE(paramValue[i]))));
        _RN2483.putc(static_cast<char>(NIBBLE_TO_HEX_CHAR(LOW_NIBBLE(paramValue[i]))));
    }

    _RN2483.printf(CRLF);

    return expectOK();
}

// Sends the command together with the given paramValue (optional)
// to the device and awaits for the response.
// Returns true on success.
// NOTE: command should include a trailing space if paramValue is set
bool RN2483::sendCommand(const char* command, uint8_t paramValue)
{
    _RN2483.printf(command);
    _RN2483.printf("%u",paramValue);
    _RN2483.printf(CRLF);

    return expectOK();
}

// Sends the command together with the given paramValue (optional)
// to the device and awaits for the response.
// Returns true on success.
// NOTE: command should include a trailing space if paramValue is set
bool RN2483::sendCommand(const char* command, const char* paramValue)
{
    _RN2483.printf(command);
    if (paramValue != NULL) {
        _RN2483.printf(paramValue);
    }
    _RN2483.printf(CRLF);
    return expectOK();
}

// Sends a join network command to the device and waits for the response (or timeout).
// Returns true on success.
bool RN2483::joinNetwork(const char* type)
{
    _RN2483.printf(STR_CMD_JOIN);
    _RN2483.printf(type);
    _RN2483.printf(CRLF);

    return expectOK() && expectString(STR_ACCEPTED, 30000);
}

// Sends the given mac command together with the given paramValue
// to the device and awaits for the response.
// Returns true on success.
// NOTE: paramName should include a trailing space
bool RN2483::setMacParam(const char* paramName, const uint8_t* paramValue, uint16_t size)
{
    _RN2483.printf(STR_CMD_SET);
    _RN2483.printf(paramName);

    for (uint16_t i = 0; i < size; ++i) {
        _RN2483.putc(static_cast<char>(NIBBLE_TO_HEX_CHAR(HIGH_NIBBLE(paramValue[i]))));
        _RN2483.putc(static_cast<char>(NIBBLE_TO_HEX_CHAR(LOW_NIBBLE(paramValue[i]))));
    }

    _RN2483.printf(CRLF);

    return expectOK();
}

// Sends the given mac command together with the given paramValue
// to the device and awaits for the response.
// Returns true on success.
// NOTE: paramName should include a trailing space
bool RN2483::setMacParam(const char* paramName, uint8_t paramValue)
{
    _RN2483.printf(STR_CMD_SET);
    _RN2483.printf(paramName);
    _RN2483.printf("%u",paramValue);
    _RN2483.printf(CRLF);

    return expectOK();
}

// Sends the given mac command together with the given paramValue
// to the device and awaits for the response.
// Returns true on success.
// NOTE: paramName should include a trailing space
bool RN2483::setMacParam(const char* paramName, const char* paramValue)
{
    _RN2483.printf(STR_CMD_SET);
    _RN2483.printf(paramName);
    _RN2483.printf(paramValue);
    _RN2483.printf(CRLF);

    return expectOK();
}

// Returns the enum that is mapped to the given "error" message.
uint8_t RN2483::lookupMacTransmitError(const char* error)
{
    if (error[0] == 0) {
        return NoResponse;
    }

    StringEnumPair_t errorTable[] =
    {
        { STR_RESULT_INVALID_PARAM, InternalError },
        { STR_RESULT_NOT_JOINED, NotConnected },
        { STR_RESULT_NO_FREE_CHANNEL, Busy },
        { STR_RESULT_SILENT, Busy },
        { STR_RESULT_FRAME_COUNTER_ERROR, NetworkFatalError },
        { STR_RESULT_BUSY, Busy },
        { STR_RESULT_MAC_PAUSED, InternalError },
        { STR_RESULT_INVALID_DATA_LEN, PayloadSizeError },
        { STR_RESULT_MAC_ERROR, NoAcknowledgment },
    };

    for (StringEnumPair_t * p = errorTable; p->stringValue != NULL; ++p) {
        if (strcmp(p->stringValue, error) == 0) {
            return p->enumValue;
        }
    }

    return NoResponse;
}

uint8_t RN2483::macTransmit(const char* type, uint8_t port, const uint8_t* payload, uint8_t size)
{
    _RN2483.printf(STR_CMD_MAC_TX);
    _RN2483.printf(type);
    _RN2483.printf("%u",port);
    _RN2483.printf(" ");

    for (int i = 0; i < size; ++i) {
        _RN2483.putc(static_cast<char>(NIBBLE_TO_HEX_CHAR(HIGH_NIBBLE(payload[i]))));
        _RN2483.putc(static_cast<char>(NIBBLE_TO_HEX_CHAR(LOW_NIBBLE(payload[i]))));
    }

    _RN2483.printf(CRLF);

    if (!expectOK()) {
        return lookupMacTransmitError(this->inputBuffer); // inputBuffer still has the last line read
    }

    this->packetReceived = false; // prepare for receiving a new packet
    Timer t;
    t.start();
    unsigned long timeout = t.read_ms() + RECEIVE_TIMEOUT; // hard timeouts
    while (t.read_ms() < timeout) {
        if (readLn() > 0) {
            if (strstr(this->inputBuffer, " ") != NULL) // to avoid double delimiter search
            {
                // there is a splittable line -only case known is mac_rx
                t.stop();
                return onMacRX();
            }
            else if (strstr(this->inputBuffer, STR_RESULT_MAC_TX_OK)) {
                // done
                t.stop();
                return NoError;
            }
            else {
                // lookup the error message
                t.stop();
                return lookupMacTransmitError(this->inputBuffer);
            }
        }
    }
   t.stop();
    return Timedout;
}

// Parses the input buffer and copies the received payload into the "received payload" buffer
// when a "mac rx" message has been received. It is called internally by macTransmit().
// Returns 0 (NoError) or otherwise one of the MacTransmitErrorCodes.
uint8_t RN2483::onMacRX()
{
    // parse inputbuffer, put payload into packet buffer
    char* token = strtok(this->inputBuffer, " ");

    // sanity check
    if (strcmp(token, STR_RESULT_MAC_RX) != 0) {
        return InternalError;
    }

    // port
    token = strtok(NULL, " ");

    // payload
    token = strtok(NULL, " "); // until end of string

    uint16_t len = strlen(token) + 1; // include termination char
    memcpy(this->receivedPayloadBuffer, token, len <= this->receivedPayloadBufferSize ? len : this->receivedPayloadBufferSize);

    this->packetReceived = true; // enable receive() again
    return NoError;
}

 // Private method to read serial port with timeout
int RN2483::timedRead(unsigned long _timeout){
    int c;
    Timer t;
    t.start();
    unsigned long _startMillis = t.read_ms(); // get milliseconds
    do { 
        if(_RN2483.readable()){
            c = _RN2483.getc();
            if (c >= 0){
                t.stop();
                return c;
            }
        }                
    } while(t.read_ms() - _startMillis <_timeout);
    t.stop();
    return -1; // -1 indicates timeout
}    

// Read characters into buffer terminates if length characters have been read, timeout, 
// or if the terminator character  detected returns the number of characters placed in the buffer 
// (0 means no valid data found)
size_t RN2483::readBytesUntil(char terminator, char *buffer, size_t length)
{
    if (length < 1) return 0;
    size_t index = 0;
    while (index < length) {
        int c = timedRead(1000);
        if (c < 0 || c == terminator) break;
        *buffer++ = (char)c;
        index++;
    }
    return index; // return number of characters, not including null terminator        
}    

#ifdef DEBUG
int freeRam()
{
    extern int __heap_start;
    extern int *__brkval;
    int v;
    return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
}
#endif