#include "LoRa.h"
#include "math.h"
#include <SPI.h>
#include "Cape.h"
#include "cellular_hal.h"
SYSTEM_MODE(SEMI_AUTOMATIC);
//STARTUP(cellular_credentials_set("m2minternet.apn", "", "", NULL)); // ROGERS APN
STARTUP(cellular_credentials_set("mnet.bell.ca.ioe", "", "", NULL)); // BELL APN
ApplicationWatchdog wd(360000, watchdogCallback); // arf, 6 mins
// Source content to be crypted
// Result buffer needs an additional byte for the initialization vector
char decrypted[70];
char encrypted[69];
char RX_DATA[20][70];
int RSSI[20];
char temp[40];
int WakeCommand = 0;
// Insert secret key and its length
Cape cape(key, 10);
//STARTUP(System.enableFeature(FEATURE_RETAINED_MEMORY)); //uses 9ua of power, but prob worth it
//retained int lastTime;
long int catchTime;
int n;
int bootTime;
int waitforfirmware = 0; // 1 millisecond
int TXint = 15;
String ACK = "";
char rxID[6];
bool LoRaSwitch = false;
char T1[6];
char T2[6];
char T3[6];
char T4[6];
char ICCID[5] = "";
char targetID[6] = "EMPTY";
char SleepID[6] = "EMPTY";
char WakeID[6] = "EMPTY";
int retries = 0;
char * Type;
char * ID;
char * Temp1;
char * Temp2;
char * Temp3;
char * Temp4;
char * Temp5;
char * Temp6;
char * Temp7;
char * Vbatt;
char * RXICCID;
char * rxIDACK;
char * deviceType;
char * timeRX;
char * checkTemp1;
char * checkTemp2;
char * checkTemp3;
char * checkTemp4;
char * checkTemp5;
char * checkTemp6;
char * checkVbatt;
bool aliveflag = false;
bool isEncrypted = false;
volatile char RX_data_flag[20];
int j;
uint32_t input1 = 0;
uint32_t input2 = 0;
const uint8_t chipSelect = D5;
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000)
unsigned long lastSync = millis();
void setup() {
cape.set_key(key, 10);
Serial.begin(115200);
delay(5000);
Particle.connect();
// Print true or false depending on whether current time is valid
waitFor(Time.isValid, 60000);
Serial.println("ETC Receiver");
getICCID();
if (!LoRa.begin(915E6)) {
Serial.println("Starting LoRa failed!");
while (1);
}
LoRa.enableCrc();
LoRa.onReceive(onReceive);
// put the radio into receive mode
LoRa.receive();
PMIC pmic;
pmic.setChargeVoltage(4208); // set max voltage to 4.2v (instead of default 4.112)
pmic.setChargeCurrent(0, 0, 1, 0, 0, 0); // set charging to 1024 milliamp (instead of default 0.5 amp)
catchTime = millis();
// register the receive callback
Particle.function("TX", TXinterval);
Particle.variable("TXinterval", & TXint, INT);
Particle.function("DataonDemand",DataonDemand);
Particle.function("SleepSensor",Sleepsensor);
Particle.function("WakeSensor",Wakesensor);
Serial.println("Setup Finished");
}
void loop() {
wd.checkin(); // resets the AWDT count
if (Particle.connected()) {
if (!aliveflag) {
aliveflag = true;
Particle.keepAlive(45);
}
}
else {
aliveflag = false;
}
for (j = 0; j < 20; j++) {
if (RX_data_flag[j] == 1 && strstr(RX_DATA[j], "S,")) { //if data is available in the buffer
int i;
char * pt;
char DATA[13][12];
pt = strtok(RX_DATA[j], ",");
if (pt != NULL) {
for (i = 0; i < 13; i++) {
strcpy(DATA[i], pt);
pt = strtok(NULL, ",");
}
}
Type = DATA[2];
ID = DATA[3];
Vbatt = DATA[4];
Temp1 = DATA[5];
timeRX = DATA[6];
Temp2 = DATA[7];
Temp3 = DATA[8];
Temp4 = DATA[9];
Temp5 = DATA[10];
Temp6 = DATA[11];
Temp7 = DATA[12];
memset(RX_DATA[j], 0, sizeof RX_DATA[j]);
RX_data_flag[j] = 0;
retries = 0;
RETRY_START:
//Serial.println("Data Available"); //empty for now.
long int startTime;
FuelGauge fuel;
float battLevel;
battLevel = fuel.getVCell();
//int sb = round(battLevel * 100); //round to 2 decimals step 1
//float b1 = sb / 100.00; // round to 2 decimals step 2
float b1 = roundf(battLevel * 100) / 100;
String Vpart = String(b1, 2); //remove trailing zeros.
String output = "";
int theTime = Time.now();
String niceTime = "";
if (theTime > 0) {
niceTime = String(Time.year(theTime)) + "-" + String(Time.month(theTime)) + "-" + String(Time.day(theTime)) + " " + String(Time.hour(theTime)) + ":" + String(Time.minute(theTime)) + ":" + String(Time.second(theTime));
}
else {
niceTime = "not yet acquired";
}
// need a time out here!
startTime = millis();
while (!Particle.connected()) {
delay(50);
Serial.println("not connected");
Serial.println(millis() - startTime);
if ((millis() - startTime) > 120000) {
Serial.println("did not connect for 120 seconds!");
Serial.println("breaking out of first while loop");
int notConnected = 1;
break;
}
}
if (Particle.connected()) {
//Serial.println("Particle.connected() == true, so let's do this.");
int bootSecs;
if (bootTime > 0) {
bootSecs = Time.now() - bootTime;
}
else {
bootSecs = -1;
}
// get cell signal data
CellularSignal sig = Cellular.RSSI();
int rssi = sig.rssi;
int qual = sig.qual;
//recreate output, because for GSM we don't want to send values where no thermistor is hooked up, i.e. -72.6, etc. For SD card, we wanted everything.
// output = output + "The Office Temp is:" + n + " RSSI of Signal:" + RSSI + " Vbatt:" + b1;
output = output + Type + "," + RSSI[j] + "," + ID + "," + Vbatt + "," + rssi + "," + Vpart + "," + qual + ",0.1," + Temp1 + "," + timeRX + "," + Temp2 + "," + Temp3 + "," + Temp4 + "," + Temp5 + "," + Temp6 + "," + Temp7 + ",";
Serial.println(output);
// wait for it to actually publish! 8/22/17
static uint32_t b4Publish;
b4Publish = millis();
bool success = false;
//success = Particle.publish("PETER", output, PRIVATE, WITH_ACK); //Particle.publish("readyforfirmware", "end", PRIVATE, WITH_ACK); //Particle.publish("publish-with-ack", String(count), PRIVATE, WITH_ACK);
success = Particle.publish("NOWIRES", output, PRIVATE, WITH_ACK);
//success = 1;
output = "";
//success = Particle.publish("googlesheets", output, PRIVATE); //Particle.publish("readyforfirmware", "end", PRIVATE, WITH_ACK); //Particle.publish("publish-with-ack", String(count), PRIVATE, WITH_ACK);
if (!success) {
Serial.printlnf("%7d(s): publish failed: %d", (millis() - b4Publish) / 1000);
if (retries < 3) {
Particle.disconnect();
delay(3000);
Particle.connect();
Serial.print("Retry: ");
Serial.println(retries + 1);
retries++;
goto RETRY_START;
}
}
else {
retries = 0;
Serial.printlnf("%7d(s): publish passed: %d", (millis() - b4Publish) / 1000);
}
delay(500);
}
}
else if (RX_data_flag[j] == 1 && !strstr(RX_DATA[j], "S,")) {
Serial.println(RX_DATA[j]);
Serial.println("Data conditions not met. ");
memset(RX_DATA[j], 0, sizeof RX_DATA[j]);
RX_data_flag[j] = 0;
}
}
long int check = millis() - catchTime;
if (check >= 120000) {
Serial.println("Restart LoRa");
if (!LoRa.begin(915E6)) {
Serial.println("Starting LoRa failed!");
while (1);
}
LoRa.enableCrc();
LoRa.onReceive(onReceive);
LoRa.receive();
catchTime = millis();
}
if (millis() - lastSync > ONE_DAY_MILLIS) {
// Request time synchronization from the Particle Cloud
Particle.syncTime();
lastSync = millis();
}
}
void updateTime() {
long int startTime = millis();
while (!Time.isValid()) {
Particle.process();
delay(50);
Serial.print("been waiting for the time update for this many millisecs: ");
Serial.println(millis() - startTime);
if ((millis() - startTime) > 5000) {
Serial.println("forget time update, it took more than 5 secs.");
break;
}
}
}
void goToSleep() {
LoRa.sleep();
int secsTilQtr = 0;
if (Time.now() > 0) {
// calculate how many seconds until the next :00 / :15 / :30 is so we can reboot at the right time
int minsTilQtr = TXint - (Time.minute() % TXint) - 1; // subtract 1 bc the rest is secs
secsTilQtr = (60 * minsTilQtr) + (60 - Time.second());
}
// if (secsTilQtr < 0 || secsTilQtr > 900 || secsTilQtr == 0){
// secsTilQtr = 900; // extra safety, in case something goes weird, or if we couldn't connect to cloud to get the time...
// }
// It's necessary to turn on the cellular modem in MANUAL or SEMI_AUTOMATIC mode
// before going to sleep. This happens because the modem is put to sleep using AT
// commands, and they don't work when the modem is not on.
secsTilQtr = secsTilQtr - 45;
if (secsTilQtr <= 0) secsTilQtr = 10;
Cellular.on();
System.sleep(SLEEP_MODE_DEEP, secsTilQtr);
}
int TXinterval(String command) {
/* Particle.functions always take a string as an argument and return an integer.
Since we can pass a string, it means that we can give the program commands on how the function should be used.
In this case, telling the function "on" will turn the LED on and telling it "off" will turn the LED off.
Then, the function returns a value to us to let us know what happened.
In this case, it will return 1 for the LEDs turning on, 0 for the LEDs turning off,
and -1 if we received a totally bogus command that didn't do anything to the LEDs.
*/
if (command == "1 min") {
Serial.println("1 min");
TXint = 1;
return 1;
}
else if (command == "15 min") {
Serial.println("15 min");
TXint = 15;
return 1;
}
else if (command == "60 min") {
Serial.println("60 min");
TXint = 60;
return 1;
}
else {
return -1;
}
}
void onReceive(int packetSize) {
noInterrupts();
// try to parse packet
if (packetSize > sizeof RX_DATA[j]) packetSize = sizeof RX_DATA[j];
for (j = 0; j < 20; j++) {
if (RX_data_flag[j] == 0) {
for (int i = 0; i < packetSize; i++) {
char spi = ((char) LoRa.read());
RX_DATA[j][i] = spi;
}
RSSI[j] = LoRa.packetRssi();
if (!sendACK()) {
// Serial.println("Received Data Bad: ");
// Serial.println(RX_DATA[j]);
RX_data_flag[j] = 0;
memset(RX_DATA[j], 0, sizeof RX_DATA[j]);
break;
}
else {
RX_data_flag[j] = 1;
break;
}
}
else if (j == 19) {
for (int i = 0; i < packetSize; i++) {
char spi = ((char) LoRa.read());
}
}
}
LoRa.receive(); // go back into receive mode
interrupts();
}
int sendACK() {
//cape.encrypt(RX_DATA[j], encrypted, sizeof encrypted-1, 176);
//Serial.print("Decrypted: ");
isEncrypted = false;
if (!strstr(RX_DATA[j], "S,")){
isEncrypted = true;
cape.decrypt(RX_DATA[j], decrypted, strlen(RX_DATA[j]));
memcpy(RX_DATA[j], decrypted, 69);
memset(decrypted, 0, sizeof decrypted);
memset(encrypted, 0, sizeof encrypted);
Serial.println(RX_DATA[j]);
}
if (strstr(RX_DATA[j], "S,")) {
int i;
char * pt;
char DATA[12][12];
char buffer[80];
memcpy(buffer, RX_DATA[j], 80);
//Serial.println(buffer);
pt = strtok(buffer, ",");
if (pt != NULL) {
for (i = 0; i < 12; i++) {
strcpy(DATA[i], pt);
pt = strtok(NULL, ",");
}
}
float datacheck = NULL;
char * end;
RXICCID = DATA[1];
deviceType = DATA[2];
datacheck = strtol(deviceType, & end, 10);
if (strlen(end)) {
//Serial.println("Conversion error deviceType");
return 0;
}
//else Serial.println("good data ID");
if (1 > datacheck || datacheck > 9) return 0;
rxIDACK = DATA[3];
datacheck = strtol(rxIDACK, & end, 10);
if (strlen(end)) {
//Serial.println("Conversion error ID");
return 0;
}
//else Serial.println("good data ID");
if (10000 > datacheck || datacheck > 99999) return 0;
checkVbatt = DATA[4];
datacheck = strtof(checkVbatt, & end);
if (strlen(end)) {
//Serial.println("Conversion error Vbatt");
return 0;
}
//else Serial.println("good data Vbatt");
if (0 > datacheck || datacheck > 6) return 0;
checkTemp1 = DATA[5];
datacheck = strtof(checkTemp1, & end);
if (strlen(end)) {
Serial.println("Conversion error T1");
return 0;
}
//else Serial.println("good data T1");
if (-60 > datacheck || datacheck > 150) return 0;
if (!strstr(DATA[7], "*")){
checkTemp2 = DATA[7];
datacheck = strtof(checkTemp2, & end);
if (strlen(end)) {
//Serial.println("Conversion error T2");
return 0;
}
}
//else Serial.println("good data T1");
if (-60 > datacheck || datacheck > 150) return 0;
if (!strstr(DATA[8], "*")){
checkTemp3 = DATA[8];
datacheck = strtof(checkTemp3, & end);
if (strlen(end)) {
//Serial.println("Conversion error T3");
return 0;
}
//else Serial.println("good data T1");
if (-60 > datacheck || datacheck > 150) return 0;
}
if (!strstr(DATA[9], "*")){
checkTemp4 = DATA[9];
datacheck = strtof(checkTemp4, & end);
if (strlen(end)) {
//Serial.println("Conversion error T4");
return 0;
}
//else Serial.println("good data T1");
if (-60 > datacheck || datacheck > 150) return 0;
}
if (!strstr(DATA[10], "*")){
checkTemp5 = DATA[10];
datacheck = strtof(checkTemp5, & end);
if (strlen(end)) {
//Serial.println("Conversion error T5");
return 0;
}
//else Serial.println("good data T1");
if (-60 > datacheck || datacheck > 150) return 0;
}
if (!strstr(DATA[11], "*")){
checkTemp6 = DATA[11];
datacheck = strtof(checkTemp6, & end);
if (strlen(end)) {
//Serial.println("Conversion error T6");
return 0;
}
//else Serial.println("good data T1");
if (-60 > datacheck || datacheck > 150) return 0;
}
char tempACK[3];
sprintf(tempACK, "%02d", TXint); //maintain TX interval size ie. 01 or 15
int datatype = strtol(deviceType, & end, 10);
if (strstr(WakeID, rxIDACK)){
WakeCommand = 0;
strcpy(WakeID, "EMPTY");
WakeCommand = 0;
}
if (isEncrypted == false){
Serial.println("sending to unencrypted device");
char sNew[3];
char mNew[3];
char hNew[3];
sprintf(hNew, "%02d", Time.hour());
sprintf(mNew, "%02d", Time.minute());
sprintf(sNew, "%02d", Time.second());
if (strstr(RXICCID, ICCID) || strstr(RXICCID, "OPEN")) {
ACK = ACK + rxIDACK + "," + tempACK + "," + hNew + "," + mNew + "," + sNew + "," + ICCID;
Serial.println(ACK);
LoRa.beginPacket();
LoRa.print(ACK);
LoRa.endPacket();
ACK = "";
return 1;
}
}
switch (datatype) {
case 3:
if (strstr(SleepID, rxIDACK)) {
//sleep ACK command;
ACK = ACK + rxIDACK + ",SLEEP";
Serial.println(ACK);
ACK.toCharArray(decrypted, strlen(ACK)+1);
cape.encrypt(decrypted, encrypted, strlen(ACK)+1, 176);
LoRa.beginPacket();
LoRa.print(encrypted);
LoRa.endPacket();
// Serial.println(strlen(encrypted));
// Serial.println(strlen(decrypted));
// Serial.println(strlen(ACK));
memset(decrypted, 0, sizeof decrypted);
memset(encrypted, 0, sizeof encrypted);
ACK = "";
strcpy(SleepID, "EMPTY");
}
else if (strstr(rxIDACK,targetID)) {
Serial.println("targetID");
ACK = ACK + rxIDACK + "," + tempACK + "," + ICCID + "," + Time.now() + "," + input1 + "," + input2;
Serial.println(ACK);
ACK.toCharArray(decrypted, strlen(ACK)+1);
cape.encrypt(decrypted, encrypted, strlen(ACK)+1, 176);
LoRa.beginPacket();
LoRa.print(encrypted);
LoRa.endPacket();
memset(decrypted, 0, sizeof decrypted);
memset(encrypted, 0, sizeof encrypted);
strcpy(targetID, "EMPTY");
ACK = "";
input1 = 0;
input2 = 0;
}
else if (strstr(RXICCID, ICCID) || strstr(RXICCID, "OPEN")) {
ACK = ACK + rxIDACK + "," + tempACK + "," + ICCID + "," + Time.now() + ",0,0";
Serial.println(ACK);
ACK.toCharArray(decrypted, strlen(ACK)+1);
cape.encrypt(decrypted, encrypted, strlen(ACK)+1, 176);
LoRa.beginPacket();
LoRa.print(encrypted);
LoRa.endPacket();
memset(decrypted, 0, sizeof decrypted);
memset(encrypted, 0, sizeof encrypted);
ACK = "";
}
break;
default:
char sNew[3];
char mNew[3];
char hNew[3];
sprintf(hNew, "%02d", Time.hour());
sprintf(mNew, "%02d", Time.minute());
sprintf(sNew, "%02d", Time.second());
if (strstr(RXICCID, ICCID) || strstr(RXICCID, "OPEN")) {
ACK = ACK + rxIDACK + "," + tempACK + "," + hNew + "," + mNew + "," + sNew + "," + ICCID;
ACK.toCharArray(decrypted, strlen(ACK)+1);
cape.encrypt(decrypted, encrypted, strlen(ACK)+1, 176);
LoRa.beginPacket();
LoRa.print(encrypted);
LoRa.endPacket();
memset(decrypted, 0, sizeof decrypted);
memset(encrypted, 0, sizeof encrypted);
ACK = "";
}
}
if (WakeCommand) {
//Wake ACK command;
ACK = ACK + WakeID + ",WAKE";
Serial.println(ACK);
ACK.toCharArray(decrypted, strlen(ACK)+1);
cape.encrypt(decrypted, encrypted, strlen(ACK)+1, 176);
LoRa.beginPacket();
LoRa.print(encrypted);
LoRa.endPacket();
memset(decrypted, 0, sizeof decrypted);
memset(encrypted, 0, sizeof encrypted);
ACK = "";
WakeCommand--;
if (WakeCommand <= 0){
strcpy(WakeID, "EMPTY");
WakeCommand = 0;
}
}
return 1;
}
else return 0;
}
char * extract_between(const char * str,
const char * p1,
const char * p2) {
char * ret;
const char * i1 = strstr(str, p1);
if (i1 != NULL) {
const size_t pl1 = strlen(p1);
const char * i2 = strstr(i1 + pl1, p2);
if (p2 != NULL) {
/* Found both markers, extract text. */
const size_t mlen = i2 - (i1 + pl1);
ret = (char * ) malloc(mlen + 1);
if (ret != NULL) {
memcpy(ret, i1 + pl1, mlen);
ret[mlen] = '\0';
return ret;
} else {
ret = "FAIL";
return ret;
}
} else {
ret = "FAIL";
return ret;
}
} else {
ret = "FAIL";
return ret;
}
}
void getICCID() {
CellularDevice device;
memset( & device, 0, sizeof(device));
device.size = sizeof(device);
cellular_device_info( & device, NULL);
Serial.println(device.iccid);
int N = 4;
int sublen = strlen(device.iccid) - N;
memcpy(ICCID, device.iccid + sublen, N);
ICCID[N] = '\0';
Serial.print("My ICCID is: ");
Serial.println(ICCID);
}
int DataonDemand(String command){
char * pt;
char *ptr;
char test[50];
command.toCharArray(test, strlen(command)+1);
Serial.println(test);
pt = strtok(test, ",");
input1 = strtoul(pt, &ptr, 10);
pt = strtok(NULL, ",");
input2 = strtoul(pt, &ptr, 10);
pt = strtok(NULL, ",");
strcpy(targetID, pt);
Serial.println(targetID);
if(input2-input1 > 259200) {
input1 = input2 = 0;
return -1; //period is longer than 72hrs
}
else return 1;
}
void watchdogCallback() {
System.reset();
}
int Sleepsensor(String command){
char * pt;
char *ptr;
char test[50];
command.toCharArray(test, strlen(command)+1);
Serial.println(test);
pt = strtok(test, "Sleep:");
strcpy(SleepID, pt);
Serial.println(SleepID);
return 1;
}
int Wakesensor(String command){
char * pt;
char *ptr;
char test[50];
command.toCharArray(test, strlen(command)+1);
Serial.println(test);
pt = strtok(test, "Wake:");
strcpy(WakeID, pt);
Serial.println(WakeID);
WakeCommand = 5;
ACK = ACK + WakeID + ",WAKE";
Serial.println(ACK);
ACK.toCharArray(decrypted, strlen(ACK)+1);
cape.encrypt(decrypted, encrypted, strlen(ACK)+1, 176);
LoRa.beginPacket();
LoRa.print(encrypted);
LoRa.endPacket();
memset(decrypted, 0, sizeof decrypted);
memset(encrypted, 0, sizeof encrypted);
ACK = "";
LoRa.receive(); // go back into receive mode
return 1;
}
void changeLoRaNet(){
if((Time.minute() - 1)%15 == 0 && LoRaSwitch == false){
Serial.println("Long distance operation..");
LoRaSwitch = true;
if (!LoRa.begin(915E6)) {
Serial.println("Starting LoRa failed!");
while (1);
}
LoRa.setSpreadingFactor(8);
LoRa.setSignalBandwidth(62500);
LoRa.enableCrc();
LoRa.onReceive(onReceive);
LoRa.receive();
catchTime = millis();
}
else if ((Time.minute() - 2)%15 == 0 && LoRaSwitch == true){
Serial.println("Regular operation..");
LoRaSwitch = false;
if (!LoRa.begin(915E6)) {
Serial.println("Starting LoRa failed!");
while (1);
}
LoRa.setSpreadingFactor(8);
LoRa.setSignalBandwidth(62500);
LoRa.enableCrc();
LoRa.onReceive(onReceive);
LoRa.receive();
catchTime = millis();
}
}
This is the code I’m running, essentially takes a pile of wireless readings and uploads them to the network…