/* * Copyright (c) 2015 SODAQ. All rights reserved. * * This file is part of Sodaq_RN2483. * * Sodaq_RN2483 is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or(at your option) any later version. * * Sodaq_RN2483 is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sodaq_RN2483. If not, see * . */ #include "Sodaq_RN2483.h" #include "StringLiterals.h" #include "Utils.h" #ifdef DEBUG #define debugPrintLn(...) { if (this->diagStream) this->diagStream->println(__VA_ARGS__); } #define debugPrint(...) { if (this->diagStream) this->diagStream->print(__VA_ARGS__); } #warning "Debug mode is ON" #else #define debugPrintLn(...) #define debugPrint(...) #endif // Structure for mapping error response strings and error codes. typedef struct StringEnumPair { const char* stringValue; uint8_t enumValue; } StringEnumPair_t; Sodaq_RN2483 LoRaBee; // Creates a new Sodaq_RN2483 instance. Sodaq_RN2483::Sodaq_RN2483() : loraStream(0), diagStream(0), inputBufferSize(DEFAULT_INPUT_BUFFER_SIZE), receivedPayloadBufferSize(DEFAULT_RECEIVED_PAYLOAD_BUFFER_SIZE), packetReceived(false) { #ifdef USE_DYNAMIC_BUFFER this->isBufferInitialized = false; #endif } // Takes care of the init tasks common to both initOTA() and initABP. void Sodaq_RN2483::init(Stream& stream) { debugPrintLn("[init]"); this->loraStream = &stream; #ifdef USE_DYNAMIC_BUFFER // make sure the buffers are only initialized once if (!isBufferInitialized) { this->inputBuffer = static_cast(malloc(this->inputBufferSize)); this->receivedPayloadBuffer = static_cast(malloc(this->receivedPayloadBufferSize)); isBufferInitialized = true; } #endif } // Initializes the device and connects to the network using Over-The-Air Activation. // Returns true on successful connection. bool Sodaq_RN2483::initOTA(Stream& stream, const uint8_t devEUI[8], const uint8_t appEUI[8], const uint8_t appKey[16], bool adr) { debugPrintLn("[initOTA]"); init(stream); 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 Sodaq_RN2483::initABP(Stream& stream, const uint8_t devAddr[4], const uint8_t appSKey[16], const uint8_t nwkSKey[16], bool adr) { debugPrintLn("[initABP]"); init(stream); 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 Sodaq_RN2483::send(uint8_t port, const uint8_t* payload, uint8_t size) { debugPrintLn("[send]"); 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 Sodaq_RN2483::sendReqAck(uint8_t port, const uint8_t* payload, uint8_t size, uint8_t maxRetries) { debugPrintLn("[sendReqAck]"); if (!setMacParam(STR_RETRIES, maxRetries)) { // not a fatal error -just show a debug message debugPrintLn("[sendReqAck] Non-fatal error: setting number of retries failed."); } 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 Sodaq_RN2483::receive(uint8_t* buffer, uint16_t size, uint16_t payloadStartPosition) { debugPrintLn("[receive]"); if (!this->packetReceived) { debugPrintLn("[receive]: There is no packet received!"); 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) { debugPrintLn("[receive]: Out of bounds start position!"); 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 debugPrintLn("[receive]: Done"); return outputIndex; } // 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 Sodaq_RN2483::readLn(char* buffer, uint16_t size, uint16_t start) { int len = this->loraStream->readBytesUntil('\n', buffer + start, size); 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 Sodaq_RN2483::expectString(const char* str, uint16_t timeout) { debugPrint("[expectString] expecting "); debugPrint(str); unsigned long start = millis(); while (millis() < start + timeout) { debugPrint("."); if (readLn() > 0) { debugPrint("("); debugPrint(this->inputBuffer); debugPrint(")"); if (strstr(this->inputBuffer, str) != NULL) { debugPrintLn(" found a match!"); return true; } return false; } } return false; } bool Sodaq_RN2483::expectOK() { return expectString(STR_RESULT_OK); } // Sends a reset command to the module and waits for the success response (or timeout). // Returns true on success. bool Sodaq_RN2483::resetDevice() { debugPrintLn("[resetDevice]"); this->loraStream->print(STR_CMD_RESET); this->loraStream->print(CRLF); return expectString(STR_DEVICE_TYPE); } // Sends a join network command to the device and waits for the response (or timeout). // Returns true on success. bool Sodaq_RN2483::joinNetwork(const char* type) { debugPrintLn("[joinNetwork]"); this->loraStream->print(STR_CMD_JOIN); this->loraStream->print(type); this->loraStream->print(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 Sodaq_RN2483::setMacParam(const char* paramName, const uint8_t* paramValue, uint16_t size) { debugPrint("[setMacParam] "); debugPrint(paramName); debugPrint("= [array]"); this->loraStream->print(STR_CMD_SET); this->loraStream->print(paramName); for (uint16_t i = 0; i < size; ++i) { this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(HIGH_NIBBLE(paramValue[i])))); this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(LOW_NIBBLE(paramValue[i])))); } this->loraStream->print(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 Sodaq_RN2483::setMacParam(const char* paramName, uint8_t paramValue) { debugPrint("[setMacParam] "); debugPrint(paramName); debugPrint("= "); debugPrintLn(paramValue); this->loraStream->print(STR_CMD_SET); this->loraStream->print(paramName); this->loraStream->print(paramValue); this->loraStream->print(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 Sodaq_RN2483::setMacParam(const char* paramName, const char* paramValue) { debugPrint("[setMacParam] "); debugPrint(paramName); debugPrint("= "); debugPrintLn(paramValue); this->loraStream->print(STR_CMD_SET); this->loraStream->print(paramName); this->loraStream->print(paramValue); this->loraStream->print(CRLF); return expectOK(); } // Returns the enum that is mapped to the given "error" message. uint8_t Sodaq_RN2483::lookupMacTransmitError(const char* error) { debugPrint("[lookupMacTransmitError]: "); debugPrintLn(error); if (error[0] == 0) { debugPrintLn("[lookupMacTransmitError]: The string is empty!"); 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) { debugPrint("[lookupMacTransmitError]: found "); debugPrintLn(p->enumValue); return p->enumValue; } } debugPrintLn("[lookupMacTransmitError]: not found!"); return NoResponse; } uint8_t Sodaq_RN2483::macTransmit(const char* type, uint8_t port, const uint8_t* payload, uint8_t size) { debugPrintLn("[macTransmit]"); this->loraStream->print(STR_CMD_MAC_TX); this->loraStream->print(type); this->loraStream->print(port); this->loraStream->print(" "); for (int i = 0; i < size; ++i) { this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(HIGH_NIBBLE(payload[i])))); this->loraStream->print(static_cast(NIBBLE_TO_HEX_CHAR(LOW_NIBBLE(payload[i])))); } this->loraStream->print(CRLF); if (!expectOK()) { return lookupMacTransmitError(this->inputBuffer); // inputBuffer still has the last line read } this->packetReceived = false; // prepare for receiving a new packet debugPrint("Waiting for server response"); unsigned long timeout = millis() + RECEIVE_TIMEOUT; // hard timeout while (millis() < timeout) { debugPrint("."); if (readLn() > 0) { debugPrintLn(".");debugPrint("(");debugPrint(this->inputBuffer);debugPrintLn(")"); if (strstr(this->inputBuffer, " ") != NULL) // to avoid double delimiter search { // there is a splittable line -only case known is mac_rx debugPrintLn("Splittable response found"); return onMacRX(); } else if (strstr(this->inputBuffer, STR_RESULT_MAC_TX_OK)) { // done debugPrintLn("Received mac_tx_ok"); return NoError; } else { // lookup the error message debugPrintLn("Some other string received (error)"); return lookupMacTransmitError(this->inputBuffer); } } } debugPrintLn("Timed-out waiting for a response!"); return Timeout; } // 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 Sodaq_RN2483::onMacRX() { debugPrintLn("[onMacRX]"); // parse inputbuffer, put payload into packet buffer char* token = strtok(this->inputBuffer, " "); // sanity check if (strcmp(token, STR_RESULT_MAC_RX) != 0) { debugPrintLn("[onMacRX]: mac_rx keyword not found!"); 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; } #ifdef DEBUG // Provides a quick test of several methods as a pseudo-unit test. void Sodaq_RN2483::runTestSequence(Stream& stream) { debugPrint("free ram: "); debugPrintLn(freeRam()); init(stream); this->loraStream = &stream; this->diagStream = &stream; // expectString debugPrintLn("write \"testString\" and then CRLF"); if (expectString("testString", 5000)) { debugPrintLn("[expectString] positive case works!"); } debugPrintLn(""); debugPrintLn("write something other than \"testString\" and then CRLF"); if (!expectString("testString", 5000)) { debugPrintLn("[expectString] negative case works!"); } debugPrint("free ram: "); debugPrintLn(freeRam()); // setMacParam(array) debugPrintLn(""); debugPrintLn(""); uint8_t testValue[] = {0x01, 0x02, 0xDE, 0xAD, 0xBE, 0xEF}; setMacParam("testParam ", testValue, ARRAY_SIZE(testValue)); // macTransmit debugPrintLn(""); debugPrintLn(""); uint8_t testValue2[] = {0x01, 0x02, 0xDE, 0xAD, 0xBE, 0xEF}; macTransmit(STR_CONFIRMED, 1, testValue2, ARRAY_SIZE(testValue2)); debugPrint("free ram: "); debugPrintLn(freeRam()); // receive debugPrintLn(""); debugPrintLn("==== receive"); char mockResult[] = "303132333435363738"; memcpy(this->receivedPayloadBuffer, mockResult, strlen(mockResult) + 1); uint8_t payload[64]; debugPrintLn("* without having received packet"); uint8_t length = receive(payload, sizeof(payload)); debugPrintLn(reinterpret_cast(payload)); debugPrint("Length: "); debugPrintLn(length); debugPrintLn("* having received packet"); this->packetReceived = true; length = receive(payload, sizeof(payload)); debugPrintLn(reinterpret_cast(payload)); debugPrint("Length: "); debugPrintLn(length); // onMacRX debugPrintLn(""); debugPrintLn("==== onMacRX"); char mockRx[] = "mac_rx 1 303132333435363738"; memcpy(this->inputBuffer, mockRx, strlen(mockRx) + 1); this->packetReceived = false;// reset debugPrint("Input buffer now is: "); debugPrintLn(this->inputBuffer); debugPrint("onMacRX result code: "); debugPrintLn(onMacRX()); uint8_t payload2[64]; if (receive(payload2, sizeof(payload2)) != 9) { debugPrintLn("len is wrong!"); } debugPrintLn(reinterpret_cast(payload2)); if (receive(payload2, sizeof(payload2), 2) != 7) { debugPrintLn("len is wrong!"); } debugPrintLn(reinterpret_cast(payload2)); if (receive(payload2, sizeof(payload2), 3) != 6) { debugPrintLn("len is wrong!"); } debugPrintLn(reinterpret_cast(payload2)); debugPrint("free ram: "); debugPrintLn(freeRam()); // lookup error debugPrintLn(""); debugPrintLn(""); debugPrint("empty string: "); debugPrintLn((lookupMacTransmitError("") == NoResponse) ? "passed" : "wrong"); debugPrint("\"random\": "); debugPrintLn((lookupMacTransmitError("random") == NoResponse) ? "passed" : "wrong"); debugPrint("\"invalid_param\": "); debugPrintLn((lookupMacTransmitError("invalid_param") == InternalError) ? "passed" : "wrong"); debugPrint("\"not_joined\": "); debugPrintLn((lookupMacTransmitError("not_joined") == NotConnected) ? "passed" : "wrong"); debugPrint("\"busy\": "); debugPrintLn((lookupMacTransmitError("busy") == Busy) ? "passed" : "wrong"); debugPrint("\"invalid_param\": "); debugPrintLn((lookupMacTransmitError("invalid_param") == InternalError) ? "passed" : "wrong"); debugPrint("free ram: "); debugPrintLn(freeRam()); } int Sodaq_RN2483::freeRam() { extern int __heap_start; extern int *__brkval; int v; return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval); } #endif