797 lines
22 KiB
C++
797 lines
22 KiB
C++
#include <lmic.h>
|
|
#include <hal/hal.h>
|
|
#include <SPI.h>
|
|
#include "HX711.h"
|
|
#include "Adafruit_Si7021.h"
|
|
#include "Adafruit_FRAM_SPI.h"
|
|
|
|
// Defines
|
|
#define TIMEOUTMS 60000
|
|
#define SENDDIFFTHRESHOLD 999
|
|
#define DONEPIN A5
|
|
#define VBATPIN A7
|
|
#define MAX_VALUES_TO_SEND 4
|
|
#define MAX_SHORT 32767
|
|
#define JOIN_AFTER_N_STARTUPS 100
|
|
|
|
// Enumerations
|
|
enum which {
|
|
NWKSKEY,
|
|
APPSKEY,
|
|
DEVADDR,
|
|
APPEUI,
|
|
DEVEUI,
|
|
APPKEY
|
|
};
|
|
|
|
// Constants
|
|
const uint8_t FRAM_CS = 10;
|
|
|
|
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
|
|
// Required for Serial on Zero based boards
|
|
#define Serial SERIAL_PORT_USBVIRTUAL
|
|
#endif
|
|
|
|
typedef struct {
|
|
band_t bands[MAX_BANDS];
|
|
u4_t channelFreq[MAX_CHANNELS];
|
|
u2_t channelDrMap[MAX_CHANNELS];
|
|
u2_t channelMap;
|
|
u4_t seqnoUp;
|
|
u4_t seqnoDn;
|
|
u1_t datarate;
|
|
u1_t dn2Dr;
|
|
u4_t dn2Freq;
|
|
s1_t adrTxPow;
|
|
u1_t adrEnabled;
|
|
} SESSION_info;
|
|
|
|
// Data Types
|
|
typedef struct {
|
|
// for ABP
|
|
u1_t nwkskey[16];
|
|
u1_t appskey[16];
|
|
u4_t devaddr;
|
|
|
|
// for OTAA
|
|
u1_t appeui[8]; // Swisscom: F0:3D:29:AC:71:00:00:01
|
|
u1_t deveui[8];
|
|
u1_t appkey[16];
|
|
|
|
long cal_w1_0;
|
|
long cal_w2_0;
|
|
float cal_w1_factor;
|
|
float cal_w2_factor;
|
|
byte startup_counter[MAX_VALUES_TO_SEND];
|
|
short weight[MAX_VALUES_TO_SEND];
|
|
short temperature[MAX_VALUES_TO_SEND];
|
|
byte my_position;
|
|
byte logging; // 0: no, otherwise: yes
|
|
byte current_startup_counter;
|
|
|
|
// sesion info
|
|
SESSION_info session_info;
|
|
|
|
} FRAM_data;
|
|
|
|
typedef struct {
|
|
uint8_t version; // Versionierung des Paketformats
|
|
uint8_t vbat; // Batteriespannung (1 Einheit => 20 mV)
|
|
uint8_t startup_counter[MAX_VALUES_TO_SEND]; // wann die Messung gemacht wurde
|
|
int16_t weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
|
int16_t temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
|
} LORA_data;
|
|
|
|
// Global Variables
|
|
bool lora_data_sent = false;
|
|
bool usb_cable_attached = false;
|
|
FRAM_data fram_data;
|
|
LORA_data lora_data;
|
|
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI
|
|
//static uint8_t mydata[] = "Hello, world!";
|
|
static osjob_t sendjob;
|
|
char mystring[100];
|
|
|
|
// Temp./Humidity Sensor
|
|
Adafruit_Si7021 temp_sensor;
|
|
|
|
// Scales
|
|
HX711 scale1;
|
|
HX711 scale2;
|
|
|
|
// These callbacks are only used in over-the-air activation, so they are
|
|
// left empty here (we cannot leave them out completely unless
|
|
// DISABLE_JOIN is set in config.h, otherwise the linker will complain).
|
|
void os_getArtEui (u1_t* buf) { memcpy_P(buf, fram_data.appeui, 8); }
|
|
void os_getDevEui (u1_t* buf) { memcpy_P(buf, fram_data.deveui, 8); }
|
|
void os_getDevKey (u1_t* buf) { memcpy_P(buf, fram_data.appkey, 16); }
|
|
|
|
// Pin mapping
|
|
const lmic_pinmap lmic_pins = {
|
|
.nss = 8,
|
|
.rxtx = LMIC_UNUSED_PIN,
|
|
.rst = 4,
|
|
.dio = {3, 6, LMIC_UNUSED_PIN},
|
|
};
|
|
|
|
void logit(char* logstring) {
|
|
if (fram_data.logging) {
|
|
Serial.print(millis());
|
|
Serial.print(": ");
|
|
Serial.println(logstring);
|
|
}
|
|
}
|
|
|
|
void print_byte_array(u1_t arr[], int n) {
|
|
int i;
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
if (arr[i] < 16) Serial.write('0');
|
|
Serial.print(arr[i], HEX);
|
|
}
|
|
}
|
|
|
|
void ShowLORAData() {
|
|
Serial.println('{');
|
|
Serial.print(" \"version\": \"");
|
|
Serial.print(lora_data.version);
|
|
Serial.println("\",");
|
|
Serial.print(" \"vbat\": \"");
|
|
Serial.print(lora_data.vbat);
|
|
Serial.println("\",");
|
|
Serial.print(" \"startup_counter\": [");
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
Serial.print(lora_data.startup_counter[i]);
|
|
if (i < (MAX_VALUES_TO_SEND - 1)) {
|
|
Serial.print(",");
|
|
}
|
|
}
|
|
Serial.println("],");
|
|
Serial.print(" \"weight\": [");
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
Serial.print(lora_data.weight[i]);
|
|
if (i < (MAX_VALUES_TO_SEND - 1)) {
|
|
Serial.print(",");
|
|
}
|
|
}
|
|
Serial.println("],");
|
|
Serial.print(" \"temperature\": [");
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
Serial.print(lora_data.temperature[i]);
|
|
if (i < (MAX_VALUES_TO_SEND - 1)) {
|
|
Serial.print(",");
|
|
}
|
|
}
|
|
Serial.println("],");
|
|
Serial.println("}");
|
|
}
|
|
|
|
|
|
void ShowFRAMData() {
|
|
Serial.println('{');
|
|
Serial.print(" \"nwkskey\": \"");
|
|
print_byte_array(fram_data.nwkskey, 16);
|
|
Serial.println("\", ");
|
|
Serial.print(" \"appskey\": \"");
|
|
print_byte_array(fram_data.appskey, 16);
|
|
Serial.println("\", ");
|
|
Serial.print(" \"devaddr\": \"");
|
|
Serial.print(fram_data.devaddr, HEX);
|
|
Serial.println("\",");
|
|
|
|
Serial.print(" \"appeui\": \"");
|
|
print_byte_array(fram_data.appeui, 8);
|
|
Serial.println("\", ");
|
|
Serial.print(" \"deveui\": \"");
|
|
print_byte_array(fram_data.deveui, 8);
|
|
Serial.println("\", ");
|
|
Serial.print(" \"appkey\": \"");
|
|
print_byte_array(fram_data.appkey, 16);
|
|
Serial.println("\", ");
|
|
|
|
|
|
Serial.print(" \"framecounterup\": ");
|
|
Serial.print(fram_data.session_info.seqnoUp);
|
|
Serial.println(",");
|
|
Serial.print(" \"framecounterdown\": ");
|
|
Serial.print(fram_data.session_info.seqnoDn);
|
|
Serial.println(",");
|
|
Serial.print(" \"cal_w1_0\": ");
|
|
Serial.print(fram_data.cal_w1_0);
|
|
Serial.println(",");
|
|
Serial.print(" \"cal_w2_0\": ");
|
|
Serial.print(fram_data.cal_w2_0);
|
|
Serial.println(",");
|
|
Serial.print(" \"cal_w1_factor\": ");
|
|
Serial.print(fram_data.cal_w1_factor);
|
|
Serial.println(",");
|
|
Serial.print(" \"cal_w2_factor\": ");
|
|
Serial.print(fram_data.cal_w2_factor);
|
|
Serial.println(",");
|
|
Serial.print(" \"startup_counter\": [");
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
Serial.print(fram_data.startup_counter[i]);
|
|
if (i < (MAX_VALUES_TO_SEND - 1)) {
|
|
Serial.print(",");
|
|
}
|
|
}
|
|
Serial.println("],");
|
|
Serial.print(" \"weight\": [");
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
Serial.print(fram_data.weight[i]);
|
|
if (i < (MAX_VALUES_TO_SEND - 1)) {
|
|
Serial.print(",");
|
|
}
|
|
}
|
|
Serial.println("],");
|
|
Serial.print(" \"temperature\": [");
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
Serial.print(fram_data.temperature[i]);
|
|
if (i < (MAX_VALUES_TO_SEND - 1)) {
|
|
Serial.print(",");
|
|
}
|
|
}
|
|
Serial.println("],");
|
|
Serial.print(" \"my_position\": ");
|
|
Serial.print(fram_data.my_position);
|
|
Serial.println(",");
|
|
Serial.print(" \"current_startup_counter\": ");
|
|
Serial.println(fram_data.current_startup_counter);
|
|
Serial.println("}");
|
|
}
|
|
|
|
void onEvent (ev_t ev) {
|
|
logit("Received LoraWAN Event");
|
|
switch (ev) {
|
|
case EV_SCAN_TIMEOUT:
|
|
logit("EV_SCAN_TIMEOUT");
|
|
break;
|
|
case EV_BEACON_FOUND:
|
|
logit("EV_BEACON_FOUND");
|
|
break;
|
|
case EV_BEACON_MISSED:
|
|
logit("EV_BEACON_MISSED");
|
|
break;
|
|
case EV_BEACON_TRACKED:
|
|
logit("EV_BEACON_TRACKED");
|
|
break;
|
|
case EV_JOINING:
|
|
logit("EV_JOINING");
|
|
break;
|
|
case EV_JOINED:
|
|
logit("EV_JOINED");
|
|
memcpy(fram_data.nwkskey,LMIC.nwkKey,16);
|
|
memcpy(fram_data.appskey,LMIC.artKey,16);
|
|
fram_data.devaddr = LMIC.devaddr;
|
|
break;
|
|
case EV_RFU1:
|
|
logit("EV_RFU1");
|
|
break;
|
|
case EV_JOIN_FAILED:
|
|
logit("EV_JOIN_FAILED");
|
|
break;
|
|
case EV_REJOIN_FAILED:
|
|
logit("EV_REJOIN_FAILED");
|
|
break;
|
|
case EV_TXCOMPLETE:
|
|
logit("EV_TXCOMPLETE (includes waiting for RX windows)");
|
|
if (LMIC.txrxFlags & TXRX_ACK)
|
|
logit("Received ack");
|
|
lora_data_sent = true;
|
|
if (LMIC.dataLen) {
|
|
sprintf(mystring,"Received %d bytes of payload", LMIC.dataLen);
|
|
}
|
|
break;
|
|
case EV_LOST_TSYNC:
|
|
logit("EV_LOST_TSYNC");
|
|
break;
|
|
case EV_RESET:
|
|
logit("EV_RESET");
|
|
break;
|
|
case EV_RXCOMPLETE:
|
|
// data received in ping slot
|
|
logit("EV_RXCOMPLETE");
|
|
break;
|
|
case EV_LINK_DEAD:
|
|
logit("EV_LINK_DEAD");
|
|
break;
|
|
case EV_LINK_ALIVE:
|
|
logit("EV_LINK_ALIVE");
|
|
break;
|
|
default:
|
|
logit("Unknown event");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void InitFeather() {
|
|
// DONEPIN must be low...
|
|
pinMode(DONEPIN, OUTPUT);
|
|
digitalWrite(DONEPIN, LOW);
|
|
}
|
|
|
|
float GetTemp() {
|
|
return temp_sensor.readTemperature();
|
|
}
|
|
|
|
int GetBat() {
|
|
float vb = analogRead(VBATPIN);
|
|
vb *= 2; // we divided by 2, so multiply back
|
|
vb *= 3.3; // Multiply by 3.3V, our reference voltage
|
|
return vb;
|
|
}
|
|
|
|
bool isUSBCableAttached() {
|
|
// Reading battery; Value is in Millivolts
|
|
int vbat = GetBat();
|
|
// Wir nehmen an, dass das USB-Kabel gesteckt ist, wenn die Batteriespannung
|
|
// groesser als 4.4V ist (Schalter muss auf 0 sein)
|
|
usb_cable_attached = (vbat >= 4400);
|
|
|
|
// fuer Debugzwecke
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
if (usb_cable_attached) {
|
|
digitalWrite(LED_BUILTIN, HIGH);
|
|
} else {
|
|
digitalWrite(LED_BUILTIN, LOW);
|
|
}
|
|
|
|
return usb_cable_attached;
|
|
}
|
|
|
|
void InitSerial() {
|
|
Serial.begin(115200);
|
|
Serial.setTimeout(6000);
|
|
//while (!Serial);
|
|
delay(1000);
|
|
logit("Serial Port initialized");
|
|
}
|
|
|
|
bool ShouldDataBeSent() {
|
|
// Data should be sent if difference is too big or if count is reached
|
|
bool res = false;
|
|
|
|
if (fram_data.my_position >= MAX_VALUES_TO_SEND) {
|
|
res = true;
|
|
}
|
|
if (fram_data.my_position > 1) {
|
|
if ((fram_data.weight[(fram_data.my_position - 1)] - fram_data.weight[(fram_data.my_position - 2)]) >= SENDDIFFTHRESHOLD) {
|
|
res = true;
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void RestoreLoraSessionInfo() {
|
|
memcpy(&LMIC.bands,&fram_data.session_info.bands,sizeof(LMIC.bands));
|
|
memcpy(&LMIC.channelFreq,&fram_data.session_info.channelFreq,sizeof(LMIC.channelFreq));
|
|
memcpy(&LMIC.channelDrMap,&fram_data.session_info.channelDrMap,sizeof(LMIC.channelDrMap));
|
|
LMIC.channelMap = fram_data.session_info.channelMap;
|
|
LMIC.seqnoUp = fram_data.session_info.seqnoUp;
|
|
LMIC.seqnoDn = fram_data.session_info.seqnoDn;
|
|
LMIC.datarate = fram_data.session_info.datarate;
|
|
LMIC.dn2Dr = fram_data.session_info.dn2Dr;
|
|
LMIC.dn2Freq = fram_data.session_info.dn2Freq;
|
|
LMIC.adrTxPow = fram_data.session_info.adrTxPow;
|
|
LMIC.adrEnabled = fram_data.session_info.adrEnabled;
|
|
}
|
|
|
|
void SaveLoraSessionInfo() {
|
|
memcpy(&fram_data.session_info.bands,&LMIC.bands,sizeof(LMIC.bands));
|
|
memcpy(&fram_data.session_info.channelFreq,&LMIC.channelFreq,sizeof(LMIC.channelFreq));
|
|
memcpy(&fram_data.session_info.channelDrMap,&LMIC.channelDrMap,sizeof(LMIC.channelDrMap));
|
|
fram_data.session_info.channelMap = LMIC.channelMap;
|
|
fram_data.session_info.seqnoUp = LMIC.seqnoUp;
|
|
fram_data.session_info.seqnoDn = LMIC.seqnoDn;
|
|
fram_data.session_info.datarate = LMIC.datarate;
|
|
fram_data.session_info.dn2Dr = LMIC.dn2Dr;
|
|
fram_data.session_info.dn2Freq = LMIC.dn2Freq;
|
|
fram_data.session_info.adrTxPow = LMIC.adrTxPow;
|
|
fram_data.session_info.adrEnabled = LMIC.adrEnabled;
|
|
}
|
|
|
|
void InitLORA() {
|
|
// LMIC init
|
|
os_init();
|
|
// Reset the MAC state. Session and pending data transfers will be discarded.
|
|
LMIC_reset();
|
|
// see https://startiot.telenor.com/learning/getting-started-with-adafruit-feather-m0-lora/
|
|
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
|
|
|
|
// We normally use ABP, but make a join for the first startup and after JOIN_AFTER_N_STARTUPS startups
|
|
if (fram_data.current_startup_counter == 0) {
|
|
// do nothing
|
|
logit("we do nothing => OTAA");
|
|
// We use maximum SF for OTAA Joins...
|
|
LMIC_setDrTxpow(DR_SF12, 14);
|
|
}
|
|
else if (fram_data.current_startup_counter >= JOIN_AFTER_N_STARTUPS) {
|
|
logit("we do a OTAA Join again, as the startup counter is high enough...");
|
|
fram_data.current_startup_counter = 0;
|
|
} else {
|
|
LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
|
RestoreLoraSessionInfo();
|
|
}
|
|
// Disable link check validation
|
|
//LMIC_setLinkCheckMode(0);
|
|
// TTN uses SF9 for its RX2 window.
|
|
//LMIC.dn2Dr = DR_SF9;
|
|
// Swisscom uses SF12 for its RX2 window.
|
|
//LMIC.dn2Dr = DR_SF12;
|
|
|
|
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
|
|
//LMIC_setDrTxpow(DR_SF7, 14);
|
|
//LMIC_setDrTxpow(DR_SF12, 14);
|
|
}
|
|
|
|
void do_send(osjob_t* j) {
|
|
// Check if there is not a current TX/RX job running
|
|
if (LMIC.opmode & OP_TXRXPEND) {
|
|
logit("OP_TXRXPEND, not sending");
|
|
} else {
|
|
// Prepare upstream data transmission at the next possible time.
|
|
//LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
|
|
LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(LORA_data), 0);
|
|
logit("Packet queued");
|
|
}
|
|
}
|
|
|
|
void InitAndReaFRAM() {
|
|
// we set #8 to High (CS of Lora Module), to be able to use FRAM
|
|
pinMode(8, INPUT_PULLUP);
|
|
digitalWrite(8, HIGH);
|
|
if (fram.begin()) {
|
|
logit("Found SPI FRAM");
|
|
} else {
|
|
logit("No SPI FRAM found ... check your connections\r\n");
|
|
}
|
|
// wir lesen die FRAM-Werte
|
|
logit("Reading FRAM...");
|
|
fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data));
|
|
fram_data.logging = 1;
|
|
logit("FRAM was read...");
|
|
}
|
|
|
|
float GetWeight() {
|
|
long raw_weight1 = scale1.read_average(3);
|
|
long raw_weight2 = scale2.read_average(3);
|
|
|
|
return (((raw_weight1 - fram_data.cal_w1_0) / fram_data.cal_w1_factor) + ((raw_weight2 - fram_data.cal_w2_0) / fram_data.cal_w2_factor));
|
|
}
|
|
|
|
void ReadSensors() {
|
|
temp_sensor = Adafruit_Si7021();
|
|
|
|
// we initialize the scales
|
|
logit("Initializing the scale");
|
|
scale1.begin(A3, A2);
|
|
scale2.begin(A1, A0);
|
|
|
|
// Now we initialize the Si7021
|
|
if (!temp_sensor.begin()) {
|
|
logit("Did not find Si7021 sensor!");
|
|
}
|
|
|
|
|
|
lora_data.vbat = (byte)(GetBat() / 20);
|
|
|
|
// wir verwerten die Messung nur, falls der Unterschied zur Letzten registrierten Messung gross genug ist
|
|
short weight = (int)GetWeight() / 10;
|
|
short temperature = (int)GetTemp() * 10;
|
|
short last_weight = 0;
|
|
short last_temperature = 0;
|
|
if (fram_data.my_position > 0) {
|
|
last_weight = fram_data.weight[fram_data.my_position - 1];
|
|
last_temperature = fram_data.temperature[fram_data.my_position -1];
|
|
}
|
|
|
|
Serial.println("AAA");
|
|
Serial.println(fram_data.my_position);
|
|
Serial.println(temperature);
|
|
Serial.println(last_temperature);
|
|
Serial.println(weight);
|
|
Serial.println(last_weight);
|
|
Serial.println("ZZZ");
|
|
|
|
if ((fram_data.my_position == 0) || (abs(temperature - last_temperature) > 10) || (abs(weight - last_weight) > 200)) {
|
|
fram_data.startup_counter[fram_data.my_position] = fram_data.current_startup_counter;
|
|
fram_data.weight[fram_data.my_position] = weight;
|
|
fram_data.temperature[fram_data.my_position] = temperature;
|
|
|
|
fram_data.my_position++;
|
|
}
|
|
}
|
|
|
|
void SendLoraData() {
|
|
logit("Now sending packet...");
|
|
lora_data.version = 1;
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
lora_data.startup_counter[i] = fram_data.startup_counter[i];
|
|
lora_data.weight[i] = fram_data.weight[i];
|
|
lora_data.temperature[i] = fram_data.temperature[i];
|
|
}
|
|
|
|
// Start job
|
|
|
|
//lora_data.version=11;
|
|
//lora_data.vbat=22;
|
|
//for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
// lora_data.weight[i] = 200+i;
|
|
// lora_data.temperature[i] = 300+i;
|
|
//}
|
|
ShowLORAData();
|
|
|
|
do_send(&sendjob);
|
|
unsigned long starttime;
|
|
starttime = millis();
|
|
bool logged = false;
|
|
while (!lora_data_sent && ((millis() - starttime) < TIMEOUTMS)) {
|
|
os_runloop_once();
|
|
if ((millis() % 1000) == 0) {
|
|
if (!logged) {
|
|
logit("Loop until sent...");
|
|
logged = true;
|
|
}
|
|
} else {
|
|
logged = false;
|
|
}
|
|
}
|
|
if (lora_data_sent) {
|
|
logit("Lora Data was sent!");
|
|
} else {
|
|
logit("Timeout elapsed...");
|
|
}
|
|
|
|
// We clear the data...
|
|
for (int i = 0; i < MAX_VALUES_TO_SEND; i++) {
|
|
fram_data.startup_counter[i] = 0;
|
|
fram_data.weight[i] = MAX_SHORT;
|
|
fram_data.temperature[i] = MAX_SHORT;
|
|
}
|
|
fram_data.my_position = 0;
|
|
|
|
SaveLoraSessionInfo();
|
|
}
|
|
|
|
void SaveFRAM() {
|
|
logit("we save the fram_data");
|
|
fram.writeEnable(true);
|
|
fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) );
|
|
fram.writeEnable(false);
|
|
logit("fram_data saved...");
|
|
}
|
|
|
|
boolean isValidHexKey(String hk, int length) {
|
|
if (hk.length() != length) {
|
|
return false;
|
|
}
|
|
char mychar;
|
|
for (int i = 0; i < hk.length(); i++) {
|
|
mychar = hk.charAt(i);
|
|
if (not(isHexadecimalDigit(mychar))) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
byte dehex(char c) { // Get nibble value 0...15 from character c
|
|
// Treat digit if c<'A', else letter
|
|
return c < 'A' ? c & 0xF : 9 + (c & 0xF);
|
|
// Above assumes that c is a 'hex digit' in 0...9, A or a ... F or f.
|
|
// It would make more sense to just use 16 consecutive characters,
|
|
// like eg 0123456789:;<=>? or @ABCDEFGHIJKLMNO so the above
|
|
// could just say `return c & 0xF;`
|
|
}
|
|
|
|
void WriteKey (String input, which w) {
|
|
const char *hin = input.c_str(); // Get character array
|
|
int clen = input.length() / 2;
|
|
// Next line invalid in C++, ok in C99. Probably need to
|
|
// instead declare a fixed-length array, cmd[MAXCMDLEN], etc
|
|
char cmd[clen + 1]; // Leave a byte for null terminator
|
|
for (int i = 0; i < 2 * clen; i += 2) {
|
|
cmd[i / 2] = dehex(hin[i]) << 4 | dehex(hin[i + 1]);
|
|
}
|
|
cmd[clen] = 0; // Null-byte terminator
|
|
if (w == APPSKEY) {
|
|
memcpy(&fram_data.appskey, &cmd, 16);
|
|
} else if (w == NWKSKEY) {
|
|
memcpy(&fram_data.nwkskey, &cmd, 16);
|
|
} else if (w == DEVADDR) {
|
|
logit("write device address");
|
|
fram_data.devaddr = strtol(hin, 0, 16);
|
|
} else if (w == APPEUI) {
|
|
memcpy(&fram_data.appeui, &cmd, 8);
|
|
} else if (w == DEVEUI) {
|
|
memcpy(&fram_data.deveui, &cmd, 8);
|
|
} else if (w == APPKEY) {
|
|
memcpy(&fram_data.appkey, &cmd, 16);
|
|
} else {
|
|
logit("Invalid which");
|
|
}
|
|
SaveFRAM();
|
|
}
|
|
|
|
void Setup() {
|
|
String s = "";
|
|
while (s != "exit") {
|
|
s = Serial.readStringUntil('\n');
|
|
s.trim();
|
|
|
|
if (s == "") {
|
|
//Serial.println("Leerzeile wird ignoriert...");
|
|
}
|
|
else if (s == "setup") {
|
|
Serial.println("{ \"msg\": \"Entering setup mode\" }");
|
|
}
|
|
else if (s.startsWith("setnwkskey")) {
|
|
String key;
|
|
key = s.substring(11);
|
|
if (isValidHexKey(key, 32)) {
|
|
WriteKey(key, NWKSKEY);
|
|
Serial.println("{ \"msg\": \"setnwkskey was successful\" }");
|
|
}
|
|
else {
|
|
Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }");
|
|
}
|
|
}
|
|
else if (s.startsWith("setappskey")) {
|
|
String key;
|
|
key = s.substring(11);
|
|
if (isValidHexKey(key, 32)) {
|
|
WriteKey(key, APPSKEY);
|
|
Serial.println("{ \"msg\": \"setappskey was successful\" }");
|
|
}
|
|
else {
|
|
Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }");
|
|
}
|
|
}
|
|
else if (s.startsWith("setdevaddr")) {
|
|
String key;
|
|
key = s.substring(11);
|
|
if (isValidHexKey(key, 8)) {
|
|
WriteKey(key, DEVADDR);
|
|
Serial.println("{ \"msg\": \"setdevaddr was successful\" }");
|
|
}
|
|
else {
|
|
Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }");
|
|
}
|
|
}
|
|
|
|
else if (s.startsWith("setappeui")) {
|
|
String key;
|
|
key = s.substring(10);
|
|
if (isValidHexKey(key, 16)) {
|
|
WriteKey(key, APPEUI);
|
|
Serial.println("{ \"msg\": \"setappeui was successful\" }");
|
|
}
|
|
else {
|
|
Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 16 Zeichen\" }");
|
|
}
|
|
}
|
|
else if (s.startsWith("setdeveui")) {
|
|
String key;
|
|
key = s.substring(10);
|
|
if (isValidHexKey(key, 16)) {
|
|
WriteKey(key, DEVEUI);
|
|
Serial.println("{ \"msg\": \"setdeveui was successful\" }");
|
|
}
|
|
else {
|
|
Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 16 Zeichen\" }");
|
|
}
|
|
}
|
|
else if (s.startsWith("setappkey")) {
|
|
String key;
|
|
key = s.substring(10);
|
|
if (isValidHexKey(key, 32)) {
|
|
WriteKey(key, APPKEY);
|
|
Serial.println("{ \"msg\": \"setappkey was successful\" }");
|
|
}
|
|
else {
|
|
Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }");
|
|
}
|
|
}
|
|
|
|
else if (s == "getvalues") {
|
|
Serial.println('{');
|
|
Serial.print(" \"weight\": ");
|
|
Serial.print(GetWeight());
|
|
Serial.println(", ");
|
|
Serial.print(" \"temperature\": ");
|
|
Serial.print(GetTemp());
|
|
Serial.println(", ");
|
|
Serial.print(" \"batt\": ");
|
|
Serial.println(GetBat());
|
|
Serial.println("}");
|
|
}
|
|
else if (s == "getrawvalues") {
|
|
long raw_weight1 = scale1.read_average(3);
|
|
long raw_weight2 = scale2.read_average(3);
|
|
Serial.println('{');
|
|
Serial.print(" \"w1_raw\": ");
|
|
Serial.print(raw_weight1);
|
|
Serial.println(", ");
|
|
Serial.print(" \"w2_raw\": ");
|
|
Serial.print(raw_weight2);
|
|
Serial.println("");
|
|
Serial.println("}");
|
|
}
|
|
else if (s == "getframdata") {
|
|
ShowFRAMData();
|
|
}
|
|
else if (s == "calibrate_zero_1") {
|
|
long raw_weight1 = scale1.read_average(3);
|
|
fram_data.cal_w1_0 = (int)raw_weight1;
|
|
Serial.println("{ \"msg\": \"calibrate_zero_1 was successful\" }");
|
|
}
|
|
else if (s == "calibrate_zero_2") {
|
|
long raw_weight2 = scale2.read_average(3);
|
|
fram_data.cal_w2_0 = (int)raw_weight2;
|
|
Serial.println("{ \"msg\": \"calibrate_zero_2 was successful\" }");
|
|
}
|
|
else if (s.startsWith("calibrate_1")) {
|
|
String w1_gramm;
|
|
w1_gramm = s.substring(12);
|
|
long raw_weight1 = scale1.read_average(3);
|
|
//fram_data.cal_w1_factor = (w1_gramm.toFloat() / (float)raw_weight1);
|
|
fram_data.cal_w1_factor = (((float)raw_weight1 - fram_data.cal_w1_0)/ w1_gramm.toFloat());
|
|
Serial.println("{ \"msg\": \"calibrate_1 was successful\" }");
|
|
}
|
|
else if (s.startsWith("calibrate_2")) {
|
|
String w2_gramm;
|
|
w2_gramm = s.substring(12);
|
|
long raw_weight2 = scale2.read_average(3);
|
|
fram_data.cal_w2_factor = (((float)raw_weight2 - fram_data.cal_w2_0)/ w2_gramm.toFloat());
|
|
Serial.println("{ \"msg\": \"calibrate_2 was successful\" }");
|
|
}
|
|
else if (s == "exit") {
|
|
// we exit the loop
|
|
}
|
|
else {
|
|
Serial.println("{ \"msg\": \"You sent me an unknown command (valid commands: exit,calibrate_zero_1,calibrate_zero_2,calibrate_1,calibrate_2,getvalues,getrawvalues,setnwkskey,setappskey,setdevaddr,setappeui,setdeveui,setappkey,getframdata)\" }");
|
|
}
|
|
}
|
|
Serial.println("We exited the setup loop...");
|
|
}
|
|
|
|
void TurnOff() {
|
|
logit("We turn off...");
|
|
logit("Jetzt senden wir das DONE Signal...");
|
|
delay(500);
|
|
while (1) {
|
|
digitalWrite(DONEPIN, HIGH);
|
|
delay(5);
|
|
digitalWrite(DONEPIN, LOW);
|
|
delay(5);
|
|
}
|
|
}
|
|
|
|
void setup() {
|
|
fram_data.logging = 1;
|
|
//fram_data.current_startup_counter = 0;
|
|
InitFeather();
|
|
if (isUSBCableAttached()) {
|
|
InitSerial();
|
|
}
|
|
InitAndReaFRAM();
|
|
//fram_data.current_startup_counter = 0;
|
|
ReadSensors();
|
|
if (isUSBCableAttached() && Serial.available()) {
|
|
Setup();
|
|
}
|
|
if (ShouldDataBeSent()) {
|
|
InitLORA();
|
|
SendLoraData();
|
|
}
|
|
fram_data.current_startup_counter++;
|
|
|
|
ShowFRAMData();
|
|
|
|
SaveFRAM();
|
|
TurnOff();
|
|
}
|
|
|
|
void loop() {
|
|
}
|