First commit

This commit is contained in:
Paul Faure 2022-01-18 01:01:35 +01:00
commit c270086b31
3 changed files with 1511 additions and 0 deletions

449
GasSensor/GasSensor.ino Normal file
View file

@ -0,0 +1,449 @@
#include "rn2xx3.h"
#include <SoftwareSerial.h>
#include <string.h>
#include <MsTimer2.h>
#include <avr/sleep.h>
#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 *************/
/******************************************/
/******************************************/

789
GasSensor/rn2xx3.cpp Normal file
View file

@ -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 <string.h>
#include <stdlib.h>
}
/*
@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; i<size; i++)
{
sprintf(buffer, "%02X", data[i]);
memcpy(&msgBuffer[i*2], &buffer, sizeof(buffer));
}
String dataToTx(msgBuffer);
return txCommand("mac tx uncnf 1 ", dataToTx, false);
}
TX_RETURN_TYPE rn2xx3::txCnf(String data)
{
return txCommand("mac tx cnf 1 ", data, true);
}
TX_RETURN_TYPE rn2xx3::txUncnf(String data)
{
return txCommand("mac tx uncnf 1 ", data, true);
}
TX_RETURN_TYPE rn2xx3::txCommand(String command, String data, bool shouldEncode)
{
bool send_success = false;
uint8_t busy_count = 0;
uint8_t retry_count = 0;
//clear serial buffer
while(_serial.available())
_serial.read();
while(!send_success)
{
//retransmit a maximum of 10 times
retry_count++;
if(retry_count>10)
{
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<input.length(); i++)
{
working = input.charAt(i);
sprintf(buffer, "%02x", int(working));
_serial.print(buffer);
}
}
String rn2xx3::base16encode(String input)
{
char charsOut[input.length()*2+1];
char charsIn[input.length()+1];
input.trim();
input.toCharArray(charsIn, input.length()+1);
unsigned i = 0;
for(i = 0; i<input.length()+1; i++)
{
if(charsIn[i] == '\0') break;
int value = int(charsIn[i]);
char buffer[3];
sprintf(buffer, "%02x", value);
charsOut[2*i] = buffer[0];
charsOut[2*i+1] = buffer[1];
}
charsOut[2*i] = '\0';
String toReturn = String(charsOut);
return toReturn;
}
String rn2xx3::getRx() {
return _rxMessenge;
}
int rn2xx3::getSNR()
{
String snr = sendRawCommand(F("radio get snr"));
snr.trim();
return snr.toInt();
}
String rn2xx3::base16decode(String input)
{
char charsIn[input.length()+1];
char charsOut[input.length()/2+1];
input.trim();
input.toCharArray(charsIn, input.length()+1);
unsigned i = 0;
for(i = 0; i<input.length()/2+1; i++)
{
if(charsIn[i*2] == '\0') break;
if(charsIn[i*2+1] == '\0') break;
char toDo[2];
toDo[0] = charsIn[i*2];
toDo[1] = charsIn[i*2+1];
int out = strtoul(toDo, 0, 16);
if(out<128)
{
charsOut[i] = char(out);
}
}
charsOut[i] = '\0';
return charsOut;
}
void rn2xx3::setDR(int dr)
{
if(dr>=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 <dataRate> <frequency>
//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 <dutyCycle> value that needs to be configured can be
* obtained from the actual duty cycle X (in percentage)
* using the following formula: <dutyCycle> = (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;
}

273
GasSensor/rn2xx3.h Normal file
View file

@ -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