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