From 39f8d405b07f9f699863f144f4504136f3dfc4c0 Mon Sep 17 00:00:00 2001 From: TD-er Date: Thu, 8 Aug 2019 02:45:19 +0200 Subject: [PATCH] Improve RN2xx3 LoRo TTN library --- lib/RN2483-Arduino-Library/src/rn2xx3.cpp | 1707 +++++++++++---------- lib/RN2483-Arduino-Library/src/rn2xx3.h | 603 ++++---- 2 files changed, 1248 insertions(+), 1062 deletions(-) diff --git a/lib/RN2483-Arduino-Library/src/rn2xx3.cpp b/lib/RN2483-Arduino-Library/src/rn2xx3.cpp index 0e7aaad65..a65eb1f2d 100644 --- a/lib/RN2483-Arduino-Library/src/rn2xx3.cpp +++ b/lib/RN2483-Arduino-Library/src/rn2xx3.cpp @@ -1,789 +1,918 @@ -/* - * A library for controlling a Microchip rn2xx3 LoRa radio. - * - * @Author JP Meijers - * @Author Nicolas Schteinschraber - * @Date 18/12/2015 - * - */ - -#include "Arduino.h" -#include "rn2xx3.h" - -extern "C" { -#include -#include -} - -/* - @param serial Needs to be an already opened Stream ({Software/Hardware}Serial) to write to and read from. -*/ -rn2xx3::rn2xx3(Stream& serial): -_serial(serial) -{ - _serial.setTimeout(2000); -} - -//TODO: change to a boolean -void rn2xx3::autobaud() -{ - String response = ""; - - // Try a maximum of 10 times with a 1 second delay - for (uint8_t i=0; i<10 && response==""; i++) - { - delay(1000); - _serial.write((byte)0x00); - _serial.write(0x55); - _serial.println(); - // we could use sendRawCommand(F("sys get ver")); here - _serial.println("sys get ver"); - response = _serial.readStringUntil('\n'); - } -} - - -String rn2xx3::sysver() -{ - String ver = sendRawCommand(F("sys get ver")); - ver.trim(); - return ver; -} - -RN2xx3_t rn2xx3::configureModuleType() -{ - String version = sysver(); - String model = version.substring(2,6); - switch (model.toInt()) { - case 2903: - _moduleType = RN2903; - break; - case 2483: - _moduleType = RN2483; - break; - default: - _moduleType = RN_NA; - break; - } - return _moduleType; -} - -String rn2xx3::hweui() -{ - return (sendRawCommand(F("sys get hweui"))); -} - -String rn2xx3::appeui() -{ - return ( sendRawCommand(F("mac get appeui") )); -} - -String rn2xx3::appkey() -{ - // We can't read back from module, we send the one - // we have memorized if it has been set - return _appskey; -} - -String rn2xx3::deveui() -{ - return (sendRawCommand(F("mac get deveui"))); -} - -bool rn2xx3::init() -{ - if(_appskey=="0") //appskey variable is set by both OTAA and ABP - { - return false; - } - else if(_otaa==true) - { - return initOTAA(_appeui, _appskey); - } - else - { - return initABP(_devAddr, _appskey, _nwkskey); - } -} - - -bool rn2xx3::initOTAA(String AppEUI, String AppKey, String DevEUI) -{ - _otaa = true; - _nwkskey = "0"; - String receivedData; - - //clear serial buffer - while(_serial.available()) - _serial.read(); - - // detect which model radio we are using - configureModuleType(); - - // reset the module - this will clear all keys set previously - switch (_moduleType) - { - case RN2903: - sendRawCommand(F("mac reset")); - break; - case RN2483: - sendRawCommand(F("mac reset 868")); - break; - default: - // we shouldn't go forward with the init - return false; - } - - // If the Device EUI was given as a parameter, use it - // otherwise use the Hardware EUI. - if (DevEUI.length() == 16) - { - _deveui = DevEUI; - } - else - { - String addr = sendRawCommand(F("sys get hweui")); - if( addr.length() == 16 ) - { - _deveui = addr; - } - // else fall back to the hard coded value in the header file - } - - sendRawCommand("mac set deveui "+_deveui); - - // A valid length App EUI was given. Use it. - if ( AppEUI.length() == 16 ) - { - _appeui = AppEUI; - sendRawCommand("mac set appeui "+_appeui); - } - - // A valid length App Key was give. Use it. - if ( AppKey.length() == 32 ) - { - _appskey = AppKey; //reuse the same variable as for ABP - sendRawCommand("mac set appkey "+_appskey); - } - - if (_moduleType == RN2903) - { - sendRawCommand(F("mac set pwridx 5")); - } - else - { - sendRawCommand(F("mac set pwridx 1")); - } - - // TTN does not yet support Adaptive Data Rate. - // Using it is also only necessary in limited situations. - // Therefore disable it by default. - sendRawCommand(F("mac set adr off")); - - // Switch off automatic replies, because this library can not - // handle more than one mac_rx per tx. See RN2483 datasheet, - // 2.4.8.14, page 27 and the scenario on page 19. - sendRawCommand(F("mac set ar off")); - - // Semtech and TTN both use a non default RX2 window freq and SF. - // Maybe we should not specify this for other networks. - // if (_moduleType == RN2483) - // { - // sendRawCommand(F("mac set rx2 3 869525000")); - // } - // Disabled for now because an OTAA join seems to work fine without. - - _serial.setTimeout(30000); - sendRawCommand(F("mac save")); - - bool joined = false; - - // Only try twice to join, then return and let the user handle it. - for(int i=0; i<2 && !joined; i++) - { - sendRawCommand(F("mac join otaa")); - // Parse 2nd response - receivedData = _serial.readStringUntil('\n'); - - if(receivedData.startsWith("accepted")) - { - joined=true; - delay(1000); - } - else - { - delay(1000); - } - } - _serial.setTimeout(2000); - return joined; -} - - -bool rn2xx3::initOTAA(uint8_t * AppEUI, uint8_t * AppKey, uint8_t * DevEUI) -{ - String app_eui; - String dev_eui; - String app_key; - char buff[3]; - - app_eui=""; - for (uint8_t i=0; i<8; i++) - { - sprintf(buff, "%02X", AppEUI[i]); - app_eui += String (buff); - } - - dev_eui = "0"; - if (DevEUI) //==0 - { - dev_eui = ""; - for (uint8_t i=0; i<8; i++) - { - sprintf(buff, "%02X", DevEUI[i]); - dev_eui += String (buff); - } - } - - app_key=""; - for (uint8_t i=0; i<16; i++) - { - sprintf(buff, "%02X", AppKey[i]); - app_key += String (buff); - } - - return initOTAA(app_eui, app_key, dev_eui); -} - -bool rn2xx3::initABP(String devAddr, String AppSKey, String NwkSKey) -{ - _otaa = false; - _devAddr = devAddr; - _appskey = AppSKey; - _nwkskey = NwkSKey; - String receivedData; - - //clear serial buffer - while(_serial.available()) - _serial.read(); - - configureModuleType(); - - switch (_moduleType) { - case RN2903: - sendRawCommand(F("mac reset")); - break; - case RN2483: - sendRawCommand(F("mac reset 868")); - // sendRawCommand(F("mac set rx2 3 869525000")); - // In the past we set the downlink channel here, - // but setFrequencyPlan is a better place to do it. - break; - default: - // we shouldn't go forward with the init - return false; - } - - sendRawCommand("mac set nwkskey "+_nwkskey); - sendRawCommand("mac set appskey "+_appskey); - sendRawCommand("mac set devaddr "+_devAddr); - sendRawCommand(F("mac set adr off")); - - // Switch off automatic replies, because this library can not - // handle more than one mac_rx per tx. See RN2483 datasheet, - // 2.4.8.14, page 27 and the scenario on page 19. - sendRawCommand(F("mac set ar off")); - - if (_moduleType == RN2903) - { - sendRawCommand("mac set pwridx 5"); - } - else - { - sendRawCommand(F("mac set pwridx 1")); - } - sendRawCommand(F("mac set dr 5")); //0= min, 7=max - - _serial.setTimeout(60000); - sendRawCommand(F("mac save")); - sendRawCommand(F("mac join abp")); - receivedData = _serial.readStringUntil('\n'); - - _serial.setTimeout(2000); - delay(1000); - - if(receivedData.startsWith("accepted")) - { - return true; - //with abp we can always join successfully as long as the keys are valid - } - else - { - return false; - } -} - -TX_RETURN_TYPE rn2xx3::tx(String data) -{ - return txUncnf(data); //we are unsure which mode we're in. Better not to wait for acks. -} - -TX_RETURN_TYPE rn2xx3::txBytes(const byte* data, uint8_t size) -{ - char msgBuffer[size*2 + 1]; - - char buffer[3]; - for (unsigned i=0; i10) - { - return TX_FAIL; - } - - _serial.print(command); - if(shouldEncode) - { - sendEncoded(data); - } - else - { - _serial.print(data); - } - _serial.println(); - - String receivedData = _serial.readStringUntil('\n'); - //TODO: Debug print on receivedData - - if(receivedData.startsWith("ok")) - { - _serial.setTimeout(30000); - receivedData = _serial.readStringUntil('\n'); - _serial.setTimeout(2000); - - //TODO: Debug print on receivedData - - if(receivedData.startsWith("mac_tx_ok")) - { - //SUCCESS!! - send_success = true; - return TX_SUCCESS; - } - - else if(receivedData.startsWith("mac_rx")) - { - //example: mac_rx 1 54657374696E6720313233 - _rxMessenge = receivedData.substring(receivedData.indexOf(' ', 7)+1); - send_success = true; - return TX_WITH_RX; - } - - else if(receivedData.startsWith("mac_err")) - { - init(); - } - - else if(receivedData.startsWith("invalid_data_len")) - { - //this should never happen if the prototype worked - send_success = true; - return TX_FAIL; - } - - else if(receivedData.startsWith("radio_tx_ok")) - { - //SUCCESS!! - send_success = true; - return TX_SUCCESS; - } - - else if(receivedData.startsWith("radio_err")) - { - //This should never happen. If it does, something major is wrong. - init(); - } - - else - { - //unknown response - //init(); - } - } - - else if(receivedData.startsWith("invalid_param")) - { - //should not happen if we typed the commands correctly - send_success = true; - return TX_FAIL; - } - - else if(receivedData.startsWith("not_joined")) - { - init(); - } - - else if(receivedData.startsWith("no_free_ch")) - { - //retry - delay(1000); - } - - else if(receivedData.startsWith("silent")) - { - init(); - } - - else if(receivedData.startsWith("frame_counter_err_rejoin_needed")) - { - init(); - } - - else if(receivedData.startsWith("busy")) - { - busy_count++; - - // Not sure if this is wise. At low data rates with large packets - // this can perhaps cause transmissions at more than 1% duty cycle. - // Need to calculate the correct constant value. - // But it is wise to have this check and re-init in case the - // lorawan stack in the RN2xx3 hangs. - if(busy_count>=10) - { - init(); - } - else - { - delay(1000); - } - } - - else if(receivedData.startsWith("mac_paused")) - { - init(); - } - - else if(receivedData.startsWith("invalid_data_len")) - { - //should not happen if the prototype worked - send_success = true; - return TX_FAIL; - } - - else - { - //unknown response after mac tx command - init(); - } - } - - return TX_FAIL; //should never reach this -} - -void rn2xx3::sendEncoded(String input) -{ - char working; - char buffer[3]; - for (unsigned i=0; i=0 && dr<=5) - { - delay(100); - while(_serial.available()) - _serial.read(); - _serial.print("mac set dr "); - _serial.println(dr); - _serial.readStringUntil('\n'); - } -} - -void rn2xx3::sleep(long msec) -{ - _serial.print("sys sleep "); - _serial.println(msec); -} - - -String rn2xx3::sendRawCommand(String command) -{ - delay(100); - while(_serial.available()) - _serial.read(); - _serial.println(command); - String ret = _serial.readStringUntil('\n'); - ret.trim(); - - //TODO: Add debug print - - return ret; -} - -RN2xx3_t rn2xx3::moduleType() -{ - return _moduleType; -} - -bool rn2xx3::setFrequencyPlan(FREQ_PLAN fp) -{ - bool returnValue; - - switch (fp) - { - case SINGLE_CHANNEL_EU: - { - if(_moduleType == RN2483) - { - //mac set rx2 - //sendRawCommand(F("mac set rx2 5 868100000")); //use this for "strict" one channel gateways - sendRawCommand(F("mac set rx2 3 869525000")); //use for "non-strict" one channel gateways - sendRawCommand(F("mac set ch dcycle 0 99")); //1% duty cycle for this channel - sendRawCommand(F("mac set ch dcycle 1 65535")); //almost never use this channel - sendRawCommand(F("mac set ch dcycle 2 65535")); //almost never use this channel - - returnValue = true; - } - else - { - returnValue = false; - } - break; - } - - case TTN_EU: - { - if(_moduleType == RN2483) - { - /* - * The value that needs to be configured can be - * obtained from the actual duty cycle X (in percentage) - * using the following formula: = (100/X) – 1 - * - * 10% -> 9 - * 1% -> 99 - * 0.33% -> 299 - * 8 channels, total of 1% duty cycle: - * 0.125% per channel -> 799 - * - * Most of the TTN_EU frequency plan was copied from: - * https://github.com/TheThingsNetwork/arduino-device-lib - */ - - //RX window 2 - sendRawCommand(F("mac set rx2 3 869525000")); - - //channel 0 - sendRawCommand(F("mac set ch dcycle 0 799")); - - //channel 1 - sendRawCommand(F("mac set ch drrange 1 0 6")); - sendRawCommand(F("mac set ch dcycle 1 799")); - - //channel 2 - sendRawCommand(F("mac set ch dcycle 2 799")); - - //channel 3 - sendRawCommand(F("mac set ch freq 3 867100000")); - sendRawCommand(F("mac set ch drrange 3 0 5")); - sendRawCommand(F("mac set ch dcycle 3 799")); - sendRawCommand(F("mac set ch status 3 on")); - - //channel 4 - sendRawCommand(F("mac set ch freq 4 867300000")); - sendRawCommand(F("mac set ch drrange 4 0 5")); - sendRawCommand(F("mac set ch dcycle 4 799")); - sendRawCommand(F("mac set ch status 4 on")); - - //channel 5 - sendRawCommand(F("mac set ch freq 5 867500000")); - sendRawCommand(F("mac set ch drrange 5 0 5")); - sendRawCommand(F("mac set ch dcycle 5 799")); - sendRawCommand(F("mac set ch status 5 on")); - - //channel 6 - sendRawCommand(F("mac set ch freq 6 867700000")); - sendRawCommand(F("mac set ch drrange 6 0 5")); - sendRawCommand(F("mac set ch dcycle 6 799")); - sendRawCommand(F("mac set ch status 6 on")); - - //channel 7 - sendRawCommand(F("mac set ch freq 7 867900000")); - sendRawCommand(F("mac set ch drrange 7 0 5")); - sendRawCommand(F("mac set ch dcycle 7 799")); - sendRawCommand(F("mac set ch status 7 on")); - - returnValue = true; - } - else - { - returnValue = false; - } - - break; - } - - case TTN_US: - { - /* - * Most of the TTN_US frequency plan was copied from: - * https://github.com/TheThingsNetwork/arduino-device-lib - */ - if(_moduleType == RN2903) - { - for(int channel=0; channel<72; channel++) - { - // Build command string. First init, then add int. - String command = F("mac set ch status "); - command += channel; - - if(channel>=8 && channel<16) - { - sendRawCommand(command+F(" on")); - } - else - { - sendRawCommand(command+F(" off")); - } - } - returnValue = true; - } - else - { - returnValue = false; - } - break; - } - - case DEFAULT_EU: - { - if(_moduleType == RN2483) - { - //fix duty cycle - 1% = 0.33% per channel - sendRawCommand(F("mac set ch dcycle 0 799")); - sendRawCommand(F("mac set ch dcycle 1 799")); - sendRawCommand(F("mac set ch dcycle 2 799")); - - //disable non-default channels - sendRawCommand(F("mac set ch status 3 on")); - sendRawCommand(F("mac set ch status 4 on")); - sendRawCommand(F("mac set ch status 5 on")); - sendRawCommand(F("mac set ch status 6 on")); - sendRawCommand(F("mac set ch status 7 on")); - - returnValue = true; - } - else - { - returnValue = false; - } - - break; - } - default: - { - //set default channels 868.1, 868.3 and 868.5? - returnValue = false; //well we didn't do anything, so yes, false - break; - } - } - - return returnValue; -} +/* + * A library for controlling a Microchip rn2xx3 LoRa radio. + * + * @Author JP Meijers + * @Author Nicolas Schteinschraber + * @Date 18/12/2015 + * + */ + +#include "Arduino.h" +#include "rn2xx3.h" + +extern "C" { +#include +#include +} + +/* + @param serial Needs to be an already opened Stream ({Software/Hardware}Serial) to write to and read from. +*/ +rn2xx3::rn2xx3(Stream& serial): +_serial(serial) +{ + _serial.setTimeout(2000); +} + +//TODO: change to a boolean +void rn2xx3::autobaud() +{ + String response = ""; + + // Try a maximum of 10 times with a 1 second delay + for (uint8_t i=0; i<10 && response==""; i++) + { + delay(1000); + _serial.write((byte)0x00); + _serial.write(0x55); + _serial.println(); + // we could use sendRawCommand(F("sys get ver")); here + _serial.println(F("sys get ver")); + response = _serial.readStringUntil('\n'); + } +} + + +String rn2xx3::sysver() +{ + String ver = sendRawCommand(F("sys get ver")); + ver.trim(); + return ver; +} + +RN2xx3_t rn2xx3::configureModuleType() +{ + String version = sysver(); + String model = version.substring(2,6); + switch (model.toInt()) { + case 2903: + _moduleType = RN2903; + break; + case 2483: + _moduleType = RN2483; + break; + default: + _moduleType = RN_NA; + break; + } + return _moduleType; +} + +String rn2xx3::hweui() +{ + return (sendRawCommand(F("sys get hweui"))); +} + +String rn2xx3::appeui() +{ + return ( sendRawCommand(F("mac get appeui") )); +} + +String rn2xx3::appkey() +{ + // We can't read back from module, we send the one + // we have memorized if it has been set + return _appskey; +} + +String rn2xx3::deveui() +{ + return (sendRawCommand(F("mac get deveui"))); +} + +bool rn2xx3::init() +{ + if(_appskey=="0") //appskey variable is set by both OTAA and ABP + { + return false; + } + else if(_otaa==true) + { + return initOTAA(_appeui, _appskey); + } + else + { + return initABP(_devAddr, _appskey, _nwkskey); + } +} + + +bool rn2xx3::initOTAA(String AppEUI, String AppKey, String DevEUI) +{ + _otaa = true; + _nwkskey = "0"; + String receivedData; + + //clear serial buffer + while(_serial.available()) + _serial.read(); + + // detect which model radio we are using + configureModuleType(); + + // reset the module - this will clear all keys set previously + switch (_moduleType) + { + case RN2903: + sendRawCommand(F("mac reset")); + break; + case RN2483: + sendRawCommand(F("mac reset 868")); + break; + default: + // we shouldn't go forward with the init + return false; + } + + // If the Device EUI was given as a parameter, use it + // otherwise use the Hardware EUI. + if (DevEUI.length() == 16) + { + _deveui = DevEUI; + } + else + { + String addr = sendRawCommand(F("sys get hweui")); + if( addr.length() == 16 ) + { + _deveui = addr; + } + // else fall back to the hard coded value in the header file + } + + sendMacSet(F("deveui"), _deveui); + + // A valid length App EUI was given. Use it. + if ( AppEUI.length() == 16 ) + { + _appeui = AppEUI; + sendMacSet(F("appeui"), _appeui); + } + + // A valid length App Key was give. Use it. + if ( AppKey.length() == 32 ) + { + _appskey = AppKey; //reuse the same variable as for ABP + sendMacSet(F("appkey"), _appskey); + } + + if (_moduleType == RN2903) + { + setTXoutputPower(5); + } + else + { + setTXoutputPower(1); + } + + // TTN does not yet support Adaptive Data Rate. + // Using it is also only necessary in limited situations. + // Therefore disable it by default. + setAdaptiveDataRate(false); + + // Switch off automatic replies, because this library can not + // handle more than one mac_rx per tx. See RN2483 datasheet, + // 2.4.8.14, page 27 and the scenario on page 19. + + setAutomaticReply(false); + + // Semtech and TTN both use a non default RX2 window freq and SF. + // Maybe we should not specify this for other networks. + // if (_moduleType == RN2483) + // { + // set2ndRecvWindow(3, 869525000); + // } + // Disabled for now because an OTAA join seems to work fine without. + + _serial.setTimeout(30000); + sendRawCommand(F("mac save")); + + bool joined = false; + + // Only try twice to join, then return and let the user handle it. + for(int i=0; i<2 && !joined; i++) + { + sendRawCommand(F("mac join otaa")); + // Parse 2nd response + receivedData = _serial.readStringUntil('\n'); + + if(receivedData.startsWith(F("accepted"))) + { + joined=true; + delay(1000); + } + else + { + delay(1000); + } + } + _serial.setTimeout(2000); + return joined; +} + + +bool rn2xx3::initOTAA(uint8_t * AppEUI, uint8_t * AppKey, uint8_t * DevEUI) +{ + String app_eui; + String dev_eui; + String app_key; + char buff[3]; + + app_eui=""; + for (uint8_t i=0; i<8; i++) + { + sprintf(buff, "%02X", AppEUI[i]); + app_eui += String (buff); + } + + dev_eui = "0"; + if (DevEUI) //==0 + { + dev_eui = ""; + for (uint8_t i=0; i<8; i++) + { + sprintf(buff, "%02X", DevEUI[i]); + dev_eui += String (buff); + } + } + + app_key=""; + for (uint8_t i=0; i<16; i++) + { + sprintf(buff, "%02X", AppKey[i]); + app_key += String (buff); + } + + return initOTAA(app_eui, app_key, dev_eui); +} + +bool rn2xx3::initABP(String devAddr, String AppSKey, String NwkSKey) +{ + _otaa = false; + _devAddr = devAddr; + _appskey = AppSKey; + _nwkskey = NwkSKey; + String receivedData; + + //clear serial buffer + while(_serial.available()) + _serial.read(); + + configureModuleType(); + + switch (_moduleType) { + case RN2903: + sendRawCommand(F("mac reset")); + break; + case RN2483: + sendRawCommand(F("mac reset 868")); + // set2ndRecvWindow(3, 869525000); + // In the past we set the downlink channel here, + // but setFrequencyPlan is a better place to do it. + break; + default: + // we shouldn't go forward with the init + return false; + } + + sendMacSet(F("nwkskey"), _nwkskey); + sendMacSet(F("appskey"), _appskey); + sendMacSet(F("devaddr"), _devAddr); + setAdaptiveDataRate(false); + + // Switch off automatic replies, because this library can not + // handle more than one mac_rx per tx. See RN2483 datasheet, + // 2.4.8.14, page 27 and the scenario on page 19. + setAutomaticReply(false); + + if (_moduleType == RN2903) + { + setTXoutputPower(5); + } + else + { + setTXoutputPower(1); + } + sendMacSet(F("dr"), String(5)); //0= min, 7=max + + _serial.setTimeout(60000); + sendRawCommand(F("mac save")); + sendRawCommand(F("mac join abp")); + receivedData = _serial.readStringUntil('\n'); + + _serial.setTimeout(2000); + delay(1000); + + if(receivedData.startsWith(F("accepted"))) + { + return true; + //with abp we can always join successfully as long as the keys are valid + } + else + { + return false; + } +} + +TX_RETURN_TYPE rn2xx3::tx(String data) +{ + return txUncnf(data); //we are unsure which mode we're in. Better not to wait for acks. +} + +TX_RETURN_TYPE rn2xx3::txBytes(const byte* data, uint8_t size) +{ + char msgBuffer[size*2 + 1]; + + char buffer[3]; + for (unsigned i=0; i10) + { + return TX_FAIL; + } + + _serial.print(command); + if(shouldEncode) + { + sendEncoded(data); + } + else + { + _serial.print(data); + } + _serial.println(); + + String receivedData = _serial.readStringUntil('\n'); + //TODO: Debug print on receivedData + + switch (decodeReceived(receivedData)) + { + case rn2xx3::ok: + { + _serial.setTimeout(30000); + receivedData = _serial.readStringUntil('\n'); + _serial.setTimeout(2000); + + //TODO: Debug print on receivedData + + switch (decodeReceived(receivedData)) + { + case rn2xx3::mac_tx_ok: + { + //SUCCESS!! + send_success = true; + return TX_SUCCESS; + } + + case rn2xx3::mac_rx: + { + //example: mac_rx 1 54657374696E6720313233 + _rxMessenge = receivedData.substring(receivedData.indexOf(' ', 7)+1); + send_success = true; + return TX_WITH_RX; + } + + case rn2xx3::mac_err: + { + init(); + break; + } + + case rn2xx3::invalid_data_len: + { + //this should never happen if the prototype worked + send_success = true; + return TX_FAIL; + } + + case rn2xx3::radio_tx_ok: + { + //SUCCESS!! + send_success = true; + return TX_SUCCESS; + } + + case rn2xx3::radio_err: + { + //This should never happen. If it does, something major is wrong. + init(); + break; + } + + default: + { + //unknown response + //init(); + } + } // End while after "ok" + break; + } + + case rn2xx3::invalid_param: + { + //should not happen if we typed the commands correctly + send_success = true; + return TX_FAIL; + } + + case rn2xx3::not_joined: + { + init(); + break; + } + + case rn2xx3::no_free_ch: + { + //retry + delay(1000); + break; + } + + case rn2xx3::silent: + { + init(); + break; + } + + case rn2xx3::frame_counter_err_rejoin_needed: + { + init(); + break; + } + + case rn2xx3::busy: + { + busy_count++; + + // Not sure if this is wise. At low data rates with large packets + // this can perhaps cause transmissions at more than 1% duty cycle. + // Need to calculate the correct constant value. + // But it is wise to have this check and re-init in case the + // lorawan stack in the RN2xx3 hangs. + if(busy_count>=10) + { + init(); + } + else + { + delay(1000); + } + break; + } + + case rn2xx3::mac_paused: + { + init(); + break; + } + + case rn2xx3::invalid_data_len: + { + //should not happen if the prototype worked + send_success = true; + return TX_FAIL; + } + + default: + { + //unknown response after mac tx command + init(); + break; + } + } + } + + return TX_FAIL; //should never reach this +} + +void rn2xx3::sendEncoded(String input) +{ + char working; + char buffer[3]; + for (unsigned i=0; i=0 && dr<=5) + { + sendMacSet(F("dr"), String(dr)); + } +} + +void rn2xx3::sleep(long msec) +{ + _serial.print("sys sleep "); + _serial.println(msec); +} + +String rn2xx3::sendRawCommand(const String& command) +{ + delay(100); + while(_serial.available()) + _serial.read(); + _serial.println(command); + + String ret = _serial.readStringUntil('\n'); + ret.trim(); + + if (ret.equals(F("invalid_param"))) + { + _lastErrorInvalidParam = command; + } + + //TODO: Add debug print + + return ret; +} + +RN2xx3_t rn2xx3::moduleType() +{ + return _moduleType; +} + +bool rn2xx3::setFrequencyPlan(FREQ_PLAN fp) +{ + bool returnValue; + + switch (fp) + { + case SINGLE_CHANNEL_EU: + { + if(_moduleType == RN2483) + { + //mac set rx2 + //set2ndRecvWindow(5, 868100000); //use this for "strict" one channel gateways + set2ndRecvWindow(3, 869525000); //use for "non-strict" one channel gateways + setChannelDutyCycle(0, 99); //1% duty cycle for this channel + setChannelDutyCycle(1, 65535); //almost never use this channel + setChannelDutyCycle(2, 65535); //almost never use this channel + for (uint8_t ch = 3; ch < 8; ch++) + { + setChannelEnabled(ch, false); + } + returnValue = true; + } + else + { + returnValue = false; + } + break; + } + + case TTN_EU: + { + if(_moduleType == RN2483) + { + /* + * The value that needs to be configured can be + * obtained from the actual duty cycle X (in percentage) + * using the following formula: = (100/X) – 1 + * + * 10% -> 9 + * 1% -> 99 + * 0.33% -> 299 + * 8 channels, total of 1% duty cycle: + * 0.125% per channel -> 799 + * + * Most of the TTN_EU frequency plan was copied from: + * https://github.com/TheThingsNetwork/arduino-device-lib + */ + + uint32_t freq = 867100000; + for (uint8_t ch = 0; ch < 8; ch++) + { + setChannelDutyCycle(ch, 799); // All channels + if (ch == 1) + { + setChannelDataRateRange(ch, 0, 6); + } + else if (ch > 2) + { + setChannelDataRateRange(ch, 0, 5); + setChannelFrequency(ch, freq); + freq = freq + 200000; + } + setChannelEnabled(ch, true); // frequency, data rate and duty cycle must be set first. + } + + //RX window 2 + set2ndRecvWindow(3, 869525000); + + returnValue = true; + } + else + { + returnValue = false; + } + + break; + } + + case TTN_US: + { + /* + * Most of the TTN_US frequency plan was copied from: + * https://github.com/TheThingsNetwork/arduino-device-lib + */ + if(_moduleType == RN2903) + { + for(int channel=0; channel<72; channel++) + { + bool enabled = (channel>=8 && channel<16); + setChannelEnabled(channel, enabled); + } + returnValue = true; + } + else + { + returnValue = false; + } + break; + } + + case DEFAULT_EU: + { + if(_moduleType == RN2483) + { + for(int channel=0; channel<8; channel++) + { + if (channel < 3) { + //fix duty cycle - 1% = 0.33% per channel + setChannelDutyCycle(channel, 799); + setChannelEnabled(channel, true); + } else { + //disable non-default channels + setChannelEnabled(channel, false); + } + } + returnValue = true; + } + else + { + returnValue = false; + } + + break; + } + default: + { + //set default channels 868.1, 868.3 and 868.5? + returnValue = false; //well we didn't do anything, so yes, false + break; + } + } + + return returnValue; +} + + +rn2xx3::received_t rn2xx3::decodeReceived(const String& receivedData) { + if (receivedData.length() != 0) { + #define MATCH_STRING(S) \ + if (receivedData.startsWith(F(#S))) return (rn2xx3::S); + + switch (receivedData[0]) { + case 'b': + MATCH_STRING(busy); + break; + case 'f': + MATCH_STRING(frame_counter_err_rejoin_needed); + break; + case 'i': + MATCH_STRING(invalid_data_len); + MATCH_STRING(invalid_param); + break; + case 'm': + MATCH_STRING(mac_err); + MATCH_STRING(mac_paused); + MATCH_STRING(mac_rx); + MATCH_STRING(mac_tx_ok); + break; + case 'n': + MATCH_STRING(no_free_ch); + MATCH_STRING(not_joined); + break; + case 'o': + MATCH_STRING(ok); + break; + case 'r': + MATCH_STRING(radio_err); + MATCH_STRING(radio_tx_ok); + break; + case 's': + MATCH_STRING(silent); + break; + } + #undef MATCH_STRING + } + return rn2xx3::UNKNOWN; +} + + +int rn2xx3::readIntValue(const String& command) +{ + String value = sendRawCommand(command); + value.trim(); + return value.toInt(); +} + +String rn2xx3::getLastErrorInvalidParam() +{ + String res = _lastErrorInvalidParam; + _lastErrorInvalidParam = ""; + return res; +} + +bool rn2xx3::sendMacSet(const String& param, const String& value) +{ + String command; + command.reserve(10 + param.length() + value.length()); + command = F("mac set "); + command += param; + command += ' '; + command += value; + + return sendRawCommand(command).equals(F("ok")); +} + +bool rn2xx3::sendMacSetEnabled(const String& param, bool enabled) +{ + return sendMacSet(param, enabled ? F("on") : F("off")); +} + +bool rn2xx3::sendMacSetCh(const String& param, unsigned int channel, const String& value) +{ + String command; + command.reserve(20); + command = param; + command += ' '; + command += channel; + command += ' '; + command += value; + return sendMacSet(F("ch"), command); +} + +bool rn2xx3::sendMacSetCh(const String& param, unsigned int channel, uint32_t value) +{ + return sendMacSetCh(param, channel, String(value)); +} + +bool rn2xx3::setChannelDutyCycle(unsigned int channel, unsigned int dutyCycle) +{ + return sendMacSetCh(F("dcycle"), channel, dutyCycle); +} + +bool rn2xx3::setChannelFrequency(unsigned int channel, uint32_t frequency) +{ + return sendMacSetCh(F("freq"), channel, frequency); +} + +bool rn2xx3::setChannelDataRateRange(unsigned int channel, unsigned int minRange, unsigned int maxRange) +{ + String value; + value = String(minRange); + value += ' '; + value += String(maxRange); + return sendMacSetCh(F("drrange"), channel, value); +} + +bool rn2xx3::setChannelEnabled(unsigned int channel, bool enabled) +{ + return sendMacSetCh(F("status"), channel, enabled ? F("on") : F("off")); +} + +bool rn2xx3::set2ndRecvWindow(unsigned int dataRate, uint32_t frequency) +{ + String value; + value = String(dataRate); + value += ' '; + value += String(frequency); + return sendMacSet(F("rx2"), value); +} + +bool rn2xx3::setAdaptiveDataRate(bool enabled) +{ + return sendMacSetEnabled(F("adr"), enabled); +} + +bool rn2xx3::setAutomaticReply(bool enabled) +{ + return sendMacSetEnabled(F("ar"), enabled); +} + +bool rn2xx3::setTXoutputPower(int pwridx) +{ + return sendMacSet(F("pwridx"), String(pwridx)); +} \ No newline at end of file diff --git a/lib/RN2483-Arduino-Library/src/rn2xx3.h b/lib/RN2483-Arduino-Library/src/rn2xx3.h index 9cd49404d..c5fe10674 100644 --- a/lib/RN2483-Arduino-Library/src/rn2xx3.h +++ b/lib/RN2483-Arduino-Library/src/rn2xx3.h @@ -1,273 +1,330 @@ -/* - * A library for controlling a Microchip RN2xx3 LoRa radio. - * - * @Author JP Meijers - * @Author Nicolas Schteinschraber - * @Date 18/12/2015 - * - */ - -#ifndef rn2xx3_h -#define rn2xx3_h - -#include "Arduino.h" - -enum RN2xx3_t { - RN_NA = 0, // Not set - RN2903 = 2903, - RN2483 = 2483 -}; - -enum FREQ_PLAN { - SINGLE_CHANNEL_EU, - TTN_EU, - TTN_US, - DEFAULT_EU -}; - -enum TX_RETURN_TYPE { - TX_FAIL = 0, // The transmission failed. - // If you sent a confirmed message and it is not acked, - // this will be the returned value. - - TX_SUCCESS = 1, // The transmission was successful. - // Also the case when a confirmed message was acked. - - TX_WITH_RX = 2 // A downlink message was received after the transmission. - // This also implies that a confirmed message is acked. -}; - -class rn2xx3 -{ - public: - - /* - * A simplified constructor taking only a Stream ({Software/Hardware}Serial) object. - * The serial port should already be initialised when initialising this library. - */ - rn2xx3(Stream& serial); - - /* - * Transmit the correct sequence to the rn2xx3 to trigger its autobauding feature. - * After this operation the rn2xx3 should communicate at the same baud rate than us. - */ - void autobaud(); - - /* - * Get the hardware EUI of the radio, so that we can register it on The Things Network - * and obtain the correct AppKey. - * You have to have a working serial connection to the radio before calling this function. - * In other words you have to at least call autobaud() some time before this function. - */ - String hweui(); - - /* - * Returns the AppSKey or AppKey used when initializing the radio. - * In the case of ABP this function will return the App Session Key. - * In the case of OTAA this function will return the App Key. - */ - String appkey(); - - /* - * In the case of OTAA this function will return the Application EUI used - * to initialize the radio. - */ - String appeui(); - - /* - * In the case of OTAA this function will return the Device EUI used to - * initialize the radio. This is not necessarily the same as the Hardware EUI. - * To obtain the Hardware EUI, use the hweui() function. - */ - String deveui(); - - /* - * Get the RN2xx3's hardware and firmware version number. This is also used - * to detect if the module is either an RN2483 or an RN2903. - */ - String sysver(); - - /* - * Initialise the RN2xx3 and join the LoRa network (if applicable). - * This function can only be called after calling initABP() or initOTAA(). - * The sole purpose of this function is to re-initialise the radio if it - * is in an unknown state. - */ - bool init(); - - /* - * Initialise the RN2xx3 and join a network using personalization. - * - * addr: The device address as a HEX string. - * Example "0203FFEE" - * AppSKey: Application Session Key as a HEX string. - * Example "8D7FFEF938589D95AAD928C2E2E7E48F" - * NwkSKey: Network Session Key as a HEX string. - * Example "AE17E567AECC8787F749A62F5541D522" - */ - bool initABP(String addr, String AppSKey, String NwkSKey); - - //TODO: initABP(uint8_t * addr, uint8_t * AppSKey, uint8_t * NwkSKey) - - /* - * Initialise the RN2xx3 and join a network using over the air activation. - * - * AppEUI: Application EUI as a HEX string. - * Example "70B3D57ED00001A6" - * AppKey: Application key as a HEX string. - * Example "A23C96EE13804963F8C2BD6285448198" - * DevEUI: Device EUI as a HEX string. - * Example "0011223344556677" - * If the DevEUI parameter is omitted, the Hardware EUI from module will be used - * If no keys, or invalid length keys, are provided, no keys - * will be configured. If the module is already configured with some keys - * they will be used. Otherwise the join will fail and this function - * will return false. - */ - bool initOTAA(String AppEUI="", String AppKey="", String DevEUI=""); - - /* - * Initialise the RN2xx3 and join a network using over the air activation, - * using byte arrays. This is useful when storing the keys in eeprom or flash - * and reading them out in runtime. - * - * AppEUI: Application EUI as a uint8_t buffer - * AppKey: Application key as a uint8_t buffer - * DevEui: Device EUI as a uint8_t buffer (optional - set to 0 to use Hardware EUI) - */ - bool initOTAA(uint8_t * AppEUI, uint8_t * AppKey, uint8_t * DevEui); - - /* - * Transmit the provided data. The data is hex-encoded by this library, - * so plain text can be provided. - * This function is an alias for txUncnf(). - * - * Parameter is an ascii text string. - */ - TX_RETURN_TYPE tx(String); - - /* - * Transmit raw byte encoded data via LoRa WAN. - * This method expects a raw byte array as first parameter. - * The second parameter is the count of the bytes to send. - */ - TX_RETURN_TYPE txBytes(const byte*, uint8_t); - - /* - * Do a confirmed transmission via LoRa WAN. - * - * Parameter is an ascii text string. - */ - TX_RETURN_TYPE txCnf(String); - - /* - * Do an unconfirmed transmission via LoRa WAN. - * - * Parameter is an ascii text string. - */ - TX_RETURN_TYPE txUncnf(String); - - /* - * Transmit the provided data using the provided command. - * - * String - the tx command to send - can only be one of "mac tx cnf 1 " or "mac tx uncnf 1 " - * String - an ascii text string if bool is true. A HEX string if bool is false. - * bool - should the data string be hex encoded or not - */ - TX_RETURN_TYPE txCommand(String, String, bool); - - /* - * Change the datarate at which the RN2xx3 transmits. - * A value of between 0 and 5 can be specified, - * as is defined in the LoRaWan specs. - * This can be overwritten by the network when using OTAA. - * So to force a datarate, call this function after initOTAA(). - */ - void setDR(int dr); - - /* - * Put the RN2xx3 to sleep for a specified timeframe. - * The RN2xx3 accepts values from 100 to 4294967296. - * Rumour has it that you need to do a autobaud() after the module wakes up again. - */ - void sleep(long msec); - - /* - * Send a raw command to the RN2xx3 module. - * Returns the raw string as received back from the RN2xx3. - * If the RN2xx3 replies with multiple line, only the first line will be returned. - */ - String sendRawCommand(String command); - - /* - * Returns the module type either RN2903 or RN2483, or NA. - */ - RN2xx3_t moduleType(); - - /* - * Set the active channels to use. - * Returns true if setting the channels is possible. - * Returns false if you are trying to use the wrong channels on the wrong module type. - */ - bool setFrequencyPlan(FREQ_PLAN); - - /* - * Returns the last downlink message HEX string. - */ - String getRx(); - - /* - * Get the RN2xx3's SNR of the last received packet. Helpful to debug link quality. - */ - int getSNR(); - - /* - * Encode an ASCII string to a HEX string as needed when passed - * to the RN2xx3 module. - */ - String base16encode(String); - - /* - * Decode a HEX string to an ASCII string. Useful to decode a - * string received from the RN2xx3. - */ - String base16decode(String); - - private: - Stream& _serial; - - RN2xx3_t _moduleType = RN_NA; - - //Flags to switch code paths. Default is to use OTAA. - bool _otaa = true; - - //The default address to use on TTN if no address is defined. - //This one falls in the "testing" address space. - String _devAddr = "03FFBEEF"; - - // if you want to use another DevEUI than the hardware one - // use this deveui for LoRa WAN - String _deveui = "0011223344556677"; - - //the appeui to use for LoRa WAN - String _appeui = "0"; - - //the nwkskey to use for LoRa WAN - String _nwkskey = "0"; - - //the appskey/appkey to use for LoRa WAN - String _appskey = "0"; - - // The downlink messenge - String _rxMessenge = ""; - - /* - * Auto configure for either RN2903 or RN2483 module - */ - RN2xx3_t configureModuleType(); - - void sendEncoded(String); -}; - -#endif +/* + * A library for controlling a Microchip RN2xx3 LoRa radio. + * + * @Author JP Meijers + * @Author Nicolas Schteinschraber + * @Date 18/12/2015 + * + */ + +#ifndef rn2xx3_h +#define rn2xx3_h + +#include "Arduino.h" + +enum RN2xx3_t { + RN_NA = 0, // Not set + RN2903 = 2903, + RN2483 = 2483 +}; + +enum FREQ_PLAN { + SINGLE_CHANNEL_EU, + TTN_EU, + TTN_US, + DEFAULT_EU +}; + +enum TX_RETURN_TYPE { + TX_FAIL = 0, // The transmission failed. + // If you sent a confirmed message and it is not acked, + // this will be the returned value. + + TX_SUCCESS = 1, // The transmission was successful. + // Also the case when a confirmed message was acked. + + TX_WITH_RX = 2 // A downlink message was received after the transmission. + // This also implies that a confirmed message is acked. +}; + +class rn2xx3 +{ + public: + + /* + * A simplified constructor taking only a Stream ({Software/Hardware}Serial) object. + * The serial port should already be initialised when initialising this library. + */ + rn2xx3(Stream& serial); + + /* + * Transmit the correct sequence to the rn2xx3 to trigger its autobauding feature. + * After this operation the rn2xx3 should communicate at the same baud rate than us. + */ + void autobaud(); + + /* + * Get the hardware EUI of the radio, so that we can register it on The Things Network + * and obtain the correct AppKey. + * You have to have a working serial connection to the radio before calling this function. + * In other words you have to at least call autobaud() some time before this function. + */ + String hweui(); + + /* + * Returns the AppSKey or AppKey used when initializing the radio. + * In the case of ABP this function will return the App Session Key. + * In the case of OTAA this function will return the App Key. + */ + String appkey(); + + /* + * In the case of OTAA this function will return the Application EUI used + * to initialize the radio. + */ + String appeui(); + + /* + * In the case of OTAA this function will return the Device EUI used to + * initialize the radio. This is not necessarily the same as the Hardware EUI. + * To obtain the Hardware EUI, use the hweui() function. + */ + String deveui(); + + /* + * Get the RN2xx3's hardware and firmware version number. This is also used + * to detect if the module is either an RN2483 or an RN2903. + */ + String sysver(); + + /* + * Initialise the RN2xx3 and join the LoRa network (if applicable). + * This function can only be called after calling initABP() or initOTAA(). + * The sole purpose of this function is to re-initialise the radio if it + * is in an unknown state. + */ + bool init(); + + /* + * Initialise the RN2xx3 and join a network using personalization. + * + * addr: The device address as a HEX string. + * Example "0203FFEE" + * AppSKey: Application Session Key as a HEX string. + * Example "8D7FFEF938589D95AAD928C2E2E7E48F" + * NwkSKey: Network Session Key as a HEX string. + * Example "AE17E567AECC8787F749A62F5541D522" + */ + bool initABP(String addr, String AppSKey, String NwkSKey); + + //TODO: initABP(uint8_t * addr, uint8_t * AppSKey, uint8_t * NwkSKey) + + /* + * Initialise the RN2xx3 and join a network using over the air activation. + * + * AppEUI: Application EUI as a HEX string. + * Example "70B3D57ED00001A6" + * AppKey: Application key as a HEX string. + * Example "A23C96EE13804963F8C2BD6285448198" + * DevEUI: Device EUI as a HEX string. + * Example "0011223344556677" + * If the DevEUI parameter is omitted, the Hardware EUI from module will be used + * If no keys, or invalid length keys, are provided, no keys + * will be configured. If the module is already configured with some keys + * they will be used. Otherwise the join will fail and this function + * will return false. + */ + bool initOTAA(String AppEUI="", String AppKey="", String DevEUI=""); + + /* + * Initialise the RN2xx3 and join a network using over the air activation, + * using byte arrays. This is useful when storing the keys in eeprom or flash + * and reading them out in runtime. + * + * AppEUI: Application EUI as a uint8_t buffer + * AppKey: Application key as a uint8_t buffer + * DevEui: Device EUI as a uint8_t buffer (optional - set to 0 to use Hardware EUI) + */ + bool initOTAA(uint8_t * AppEUI, uint8_t * AppKey, uint8_t * DevEui); + + /* + * Transmit the provided data. The data is hex-encoded by this library, + * so plain text can be provided. + * This function is an alias for txUncnf(). + * + * Parameter is an ascii text string. + */ + TX_RETURN_TYPE tx(String); + + /* + * Transmit raw byte encoded data via LoRa WAN. + * This method expects a raw byte array as first parameter. + * The second parameter is the count of the bytes to send. + */ + TX_RETURN_TYPE txBytes(const byte*, uint8_t); + + /* + * Do a confirmed transmission via LoRa WAN. + * + * Parameter is an ascii text string. + */ + TX_RETURN_TYPE txCnf(String); + + /* + * Do an unconfirmed transmission via LoRa WAN. + * + * Parameter is an ascii text string. + */ + TX_RETURN_TYPE txUncnf(String); + + /* + * Transmit the provided data using the provided command. + * + * String - the tx command to send + can only be one of "mac tx cnf 1 " or "mac tx uncnf 1 " + * String - an ascii text string if bool is true. A HEX string if bool is false. + * bool - should the data string be hex encoded or not + */ + TX_RETURN_TYPE txCommand(String, String, bool); + + /* + * Change the datarate at which the RN2xx3 transmits. + * A value of between 0 and 5 can be specified, + * as is defined in the LoRaWan specs. + * This can be overwritten by the network when using OTAA. + * So to force a datarate, call this function after initOTAA(). + */ + void setDR(int dr); + + /* + * Put the RN2xx3 to sleep for a specified timeframe. + * The RN2xx3 accepts values from 100 to 4294967296. + * Rumour has it that you need to do a autobaud() after the module wakes up again. + */ + void sleep(long msec); + + /* + * Send a raw command to the RN2xx3 module. + * Returns the raw string as received back from the RN2xx3. + * If the RN2xx3 replies with multiple line, only the first line will be returned. + */ + String sendRawCommand(const String& command); + + /* + * Returns the module type either RN2903 or RN2483, or NA. + */ + RN2xx3_t moduleType(); + + /* + * Set the active channels to use. + * Returns true if setting the channels is possible. + * Returns false if you are trying to use the wrong channels on the wrong module type. + */ + bool setFrequencyPlan(FREQ_PLAN); + + /* + * Returns the last downlink message HEX string. + */ + String getRx(); + + /* + * Get the RN2xx3's SNR of the last received packet. Helpful to debug link quality. + */ + int getSNR(); + + /* + * Get the RN2xx3's voltage measurement on the Vdd in mVolt + * 0–3600 (decimal value from 0 to 3600) + */ + int getVbat(); + + /* + * Encode an ASCII string to a HEX string as needed when passed + * to the RN2xx3 module. + */ + String base16encode(String); + + /* + * Decode a HEX string to an ASCII string. Useful to decode a + * string received from the RN2xx3. + */ + String base16decode(String); + + /* + * Almost all commands can return "invalid_param" + * The last command resulting in such an error can be retrieved. + * Reading this will clear the error. + */ + String getLastErrorInvalidParam(); + + private: + Stream& _serial; + + RN2xx3_t _moduleType = RN_NA; + + //Flags to switch code paths. Default is to use OTAA. + bool _otaa = true; + + //The default address to use on TTN if no address is defined. + //This one falls in the "testing" address space. + String _devAddr = "03FFBEEF"; + + // if you want to use another DevEUI than the hardware one + // use this deveui for LoRa WAN + String _deveui = "0011223344556677"; + + //the appeui to use for LoRa WAN + String _appeui = "0"; + + //the nwkskey to use for LoRa WAN + String _nwkskey = "0"; + + //the appskey/appkey to use for LoRa WAN + String _appskey = "0"; + + // The downlink messenge + String _rxMessenge = ""; + + String _lastErrorInvalidParam = ""; + + /* + * Auto configure for either RN2903 or RN2483 module + */ + RN2xx3_t configureModuleType(); + + void sendEncoded(String); + + enum received_t { + busy, + frame_counter_err_rejoin_needed, + invalid_data_len, + invalid_param, + mac_err, + mac_paused, + mac_rx, + mac_tx_ok, + no_free_ch, + not_joined, + ok, + radio_err, + radio_tx_ok, + silent, + UNKNOWN + }; + + static received_t decodeReceived(const String& receivedData); + + int readIntValue(const String& command); + + + // All "mac set ..." commands return either "ok" or "invalid_param" + bool sendMacSet(const String& param, const String& value); + bool sendMacSetEnabled(const String& param, bool enabled); + bool sendMacSetCh(const String& param, unsigned int channel, const String& value); + bool sendMacSetCh(const String& param, unsigned int channel, uint32_t value); + bool setChannelDutyCycle(unsigned int channel, unsigned int dutyCycle); + bool setChannelFrequency(unsigned int channel, uint32_t frequency); + bool setChannelDataRateRange(unsigned int channel, unsigned int minRange, unsigned int maxRange); + + // Set channel enabled/disabled. + // Frequency, data range, duty cycle must be issued prior to enabling the status of that channe + bool setChannelEnabled(unsigned int channel, bool enabled); + + bool set2ndRecvWindow(unsigned int dataRate, uint32_t frequency); + bool setAdaptiveDataRate(bool enabled); + bool setAutomaticReply(bool enabled); + bool setTXoutputPower(int pwridx); + +}; + +#endif