commit c270086b31fd78cbacf0f02deb39b2cb1e9cf216 Author: Paul Faure Date: Tue Jan 18 01:01:35 2022 +0100 First commit diff --git a/GasSensor/GasSensor.ino b/GasSensor/GasSensor.ino new file mode 100644 index 0000000..9019975 --- /dev/null +++ b/GasSensor/GasSensor.ino @@ -0,0 +1,449 @@ +#include "rn2xx3.h" +#include +#include +#include +#include + +#define LED_OFF HIGH +#define LED_ON LOW + +#define MESURE_PERIOD 30 + + +/* DECLARATION DES PINs */ +const int pinGasSensor = A0; + + +const int pinInterruptUp = 2; +const int pinInterruptDown = 3; + +const int pinRedLED = 4; +const int pinBlueLED = 5; +const int pinGreenLED = 6; +const int pinYellowLED = 7; + +const int pinBuzzer = 9; + +const int pinRX = 10; +const int pinTX = 11; +const int pinRST = 12; +/* FIN DECLARATION DES PINs */ + +/* SELECTION DES MODULES UTILISES */ +const bool useLoRa = true; +const bool useLEDs = true; +const bool useBuzzer = true; +const bool useInterrupts = true; +const bool usePeriodic = false; +const bool usePowerSaveMode = false; +/* FIN SELECTION DES MODULES UTILISES */ + +/* VARIABLES GLOBALE D'ETAT */ +volatile bool gasDetected = false; +volatile bool endGasDetected = false; +volatile bool lecture = false; +/* FIN VARIABLES GLOBALE D'ETAT */ + +/* DECLARATION DES VARIABLES GLOBALES */ +SoftwareSerial mySerial(pinRX, pinTX); // RX, TX +rn2xx3 myLora(mySerial); +/* FIN DECLARATION DES VARIABLES GLOBALES */ + + + + +/******************************************/ +/******************************************/ +/************* DEBUT DU SETUP *************/ +/******************************************/ +/******************************************/ + +/* Fonction de setup du module LoRa */ +void setupLoRa(boolean used) { + if (used) { + mySerial.begin(9600); //serial port to radio + + //reset rn2483 + pinMode(pinRST, OUTPUT); + digitalWrite(pinRST, LOW); + delay(500); + digitalWrite(pinRST, HIGH); + delay(100); //wait for the RN2xx3's startup message + mySerial.flush(); + + //Autobaud the rn2483 module to 9600. The default would otherwise be 57600. + myLora.autobaud(); + + //check communication with radio + String hweui = myLora.hweui(); + while(hweui.length() != 16) + { + Serial.println("Communication with RN2xx3 unsuccessful. Power cycle the board."); + Serial.println(hweui); + delay(10000); + hweui = myLora.hweui(); + } + + //print out the HWEUI so that we can register it via ttnctl + Serial.println("When using OTAA, register this DevEUI: "); + Serial.println(myLora.hweui()); + Serial.println("RN2xx3 firmware version:"); + Serial.println(myLora.sysver()); + + //configure your keys and join the network + Serial.println("Trying to join TTN"); + bool join_result = false; + + const char *appEui = "0000000000000000"; + const char *appKey = "27AF4F436A03803BE0E96437B8AD270E"; + + join_result = myLora.initOTAA(appEui, appKey); + + while(!join_result) + { + Serial.println("Unable to join. Are your keys correct, and do you have TTN coverage?"); + delay(60000); //delay a minute before retry + join_result = myLora.init(); + } + Serial.println("Successfully joined TTN"); + } +} + +/* Fonction de setup des LEDs */ +void setupLEDs(boolean used) { + if (used) { + pinMode(pinRedLED, OUTPUT); + digitalWrite(pinRedLED, LED_OFF); + pinMode(pinBlueLED, OUTPUT); + digitalWrite(pinBlueLED, LED_OFF); + pinMode(pinGreenLED, OUTPUT); + digitalWrite(pinGreenLED, LED_OFF); + pinMode(pinYellowLED, OUTPUT); + digitalWrite(pinYellowLED, LED_OFF); + } +} + +/* Fonction de setup du buzzer */ +void setupBuzzer(boolean used) { + if (used) { + pinMode(pinBuzzer, OUTPUT); + } +} + +/* Fonction de setup des interruptions */ +void setupInterrupt(boolean used) { + if (used) { + attachInterrupt(digitalPinToInterrupt(pinInterruptUp), setAlertState, RISING); + attachInterrupt(digitalPinToInterrupt(pinInterruptDown), resetAlertState, FALLING); + } +} + +/* Fonction de setup du Timer */ +void setupPeriodic(boolean used) { + if (used) { + MsTimer2::set(MESURE_PERIOD*1000, setPeriodicState); + MsTimer2::start(); + } +} + +/* Fonction de setup du power save mode */ +void setupPowerSaveMode(boolean used) { + if (used) { + set_sleep_mode(SLEEP_MODE_PWR_SAVE); + } +} + +/* Fonction d'initialisation globale */ +void setup() +{ + Serial.begin(9600); + + setupLoRa(useLoRa); + setupLEDs(useLEDs); + setupBuzzer(useBuzzer); + setupInterrupt(useInterrupts); + setupPeriodic(usePeriodic); + setupPowerSaveMode(usePowerSaveMode); + + + if (useLEDs) { + digitalWrite(pinYellowLED, LED_ON); + } +} + +/******************************************/ +/******************************************/ +/************** FIN DU SETUP **************/ +/******************************************/ +/******************************************/ + + + + + + + + + + +/******************************************/ +/******************************************/ +/************* DEBUT HANDLER **************/ +/******************************************/ +/******************************************/ + +void setAlertState() { + gasDetected = true; + endGasDetected = false; +} + +void resetAlertState() { + gasDetected = false; + endGasDetected = true; +} + +void setPeriodicState() { + lecture = true; +} + +/******************************************/ +/******************************************/ +/************** FIN HANDLER ***************/ +/******************************************/ +/******************************************/ + + + + + + + + + +/******************************************/ +/******************************************/ +/********** DEBUT FONCTIONS TEST **********/ +/******************************************/ +/******************************************/ + +void testGasSensor() { + Serial.print("Valeur lue sur le capteur : "); + Serial.print(readValue()); + Serial.println(" V"); + delay(1000); +} + +void testLoRa() { + if (useLoRa) { + static int i = 0; + Serial.print("Envoi du TEST n°"); + Serial.println(i); + String toSend = "TEST" + String(i); + myLora.tx(toSend); + Serial.println("Envoi terminé"); + i++; + } else { + Serial.println("Veuillez activer le module LoRa"); + } + delay(20000); +} + +void testLEDs() { + int pause = 50; + if (useLEDs) { + digitalWrite(pinYellowLED, LED_OFF); // RED + BLUE + delay(pause); + digitalWrite(pinBlueLED, LED_ON); + delay(pause); + digitalWrite(pinRedLED, LED_OFF); // BLUE + GREEN + delay(pause); + digitalWrite(pinGreenLED, LED_ON); + delay(pause); + digitalWrite(pinBlueLED, LED_OFF); // GREEN + YELLOW + delay(pause); + digitalWrite(pinYellowLED, LED_ON); + delay(pause); + digitalWrite(pinGreenLED, LED_OFF); // YELLOW + RED + delay(pause); + digitalWrite(pinRedLED, LED_ON); + delay(pause); + } else { + Serial.println("Veuillez activer les LEDs"); + delay(20000); + } +} + +void testBuzzer() { + if (useBuzzer) { + playAlertPompier(); + } else { + Serial.println("Veuillez activer le Buzzer"); + } + delay(10000); +} + +/******************************************/ +/******************************************/ +/*********** FIN FONCTIONS TEST ***********/ +/******************************************/ +/******************************************/ + + + + + + + + + + + +/******************************************/ +/******************************************/ +/************ DEBUT DU PROCESS ************/ +/******************************************/ +/******************************************/ + + +/* Primitive connection TTN */ +void connectTTN() { + if (useLEDs) { + digitalWrite(pinBlueLED, LED_ON); + } +} + +/* Primitive disconnection TTN */ +void disconnectTTN() { + if (useLEDs) { + digitalWrite(pinBlueLED, LED_OFF); + } +} + +/* Primitive du buzzer */ +void playTone(int tone, int duration) { + for (long i = 0; i < duration * 1000L; i += tone * 2) { + digitalWrite(pinBuzzer, HIGH); + delayMicroseconds(tone); + digitalWrite(pinBuzzer, LOW); + delayMicroseconds(tone); + } +} + +/* Primitive du buzzer */ +void playNote(char note, int duration) { + char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' }; + int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 }; + + // play the tone corresponding to the note name + for (int i = 0; i < sizeof(names); i++) { + //for (int i = 0; i < 8; i++) { + if (names[i] == note) { + playTone(tones[i], duration); + } + } +} + +/* Primitive du buzzer */ +void playAlertPompier() { + int length = 15; // the number of notes + char notes[] = "bababababababa "; // a space represents a rest + int beats[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + int tempo = 900; + + for (int i = 0; i < length; i++) { + if (notes[i] == ' ') { + delay(beats[i] * tempo); // rest + } else { + playNote(notes[i], beats[i] * tempo); + } + } +} + +/* Primitive de lecture de la valeur du capteur */ +float readValue() { + int value = 0; + for (int i = 0; i<10; i++) { + value += analogRead(pinGasSensor); + } + return ((float)value/10.0)*5.0/1024.0; +} + +/* Gestion de l'alerte */ +void gasDetectedHandler() { + Serial.println("ALERTE présence de gaz !!!"); + if (useLEDs) { + digitalWrite(pinRedLED, LED_ON); + } + if (useBuzzer) { + playAlertPompier(); + } + if (useLoRa) { + connectTTN(); + String toSend = "ALERTE présence de gaz !!!"; + myLora.tx(toSend); + disconnectTTN(); + } +} + +/* Gestion de la fin de l'alerte */ +void gasDetectionEndHandler() { + Serial.println("Fin ALERTE présence de gaz !!!"); + if (useLoRa) { + connectTTN(); + String toSend = "Fin ALERTE présence de gaz !!!"; + myLora.tx(toSend); + disconnectTTN(); + } + if (useLEDs) { + digitalWrite(pinRedLED, LED_OFF); + } +} + +/* Fonction de detection du gaz */ +void analyseGasAtmosphere() { + + if (lecture) { + if (useLEDs) { + digitalWrite(pinGreenLED, LED_ON); + } + float sensorValue = readValue(); + Serial.println(sensorValue); + if (useLoRa) { + connectTTN(); + String sensorValueStr = String(sensorValue, 1); + myLora.tx(sensorValueStr); + disconnectTTN(); + } + lecture = false; + if (useLEDs) { + digitalWrite(pinGreenLED, LED_OFF); + } + } + + if (gasDetected) { + gasDetectedHandler(); + while (!endGasDetected) { + gasDetectedHandler(); + } + gasDetectionEndHandler(); + } + + if (usePowerSaveMode) { + sleep_mode(); + } +} + +/* Processus */ +void loop(){ + //testGasSensor(); + //testLoRa(); + //testLEDs(); + //testBuzzer(); + analyseGasAtmosphere(); +} + +/******************************************/ +/******************************************/ +/************* FIN DU PROCESS *************/ +/******************************************/ +/******************************************/ diff --git a/GasSensor/rn2xx3.cpp b/GasSensor/rn2xx3.cpp new file mode 100644 index 0000000..0e7aaad --- /dev/null +++ b/GasSensor/rn2xx3.cpp @@ -0,0 +1,789 @@ +/* + * 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; +} diff --git a/GasSensor/rn2xx3.h b/GasSensor/rn2xx3.h new file mode 100644 index 0000000..9cd4940 --- /dev/null +++ b/GasSensor/rn2xx3.h @@ -0,0 +1,273 @@ +/* + * 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