first commit
|
|
@ -0,0 +1,712 @@
|
|||
/*********************************************************************
|
||||
BeieliScale by nbit Informatik GmbH
|
||||
|
||||
*********************************************************************/
|
||||
#include <lmic.h>
|
||||
#include <hal/hal.h>
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <avr/dtostrf.h>
|
||||
#include "HX711.h"
|
||||
#include "Adafruit_Si7021.h"
|
||||
#include "Adafruit_FRAM_SPI.h"
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
|
||||
// Required for Serial on Zero based boards
|
||||
#define Serial SERIAL_PORT_USBVIRTUAL
|
||||
#endif
|
||||
|
||||
#define MAX_VALUES_TO_SEND 5
|
||||
|
||||
bool send_lora_data = false;
|
||||
bool lora_package_sent = false;
|
||||
bool timeout_elapsed = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} LORA_data;
|
||||
|
||||
LORA_data lora_data;
|
||||
|
||||
typedef struct {
|
||||
u1_t nwkskey[16];
|
||||
u1_t appskey[16];
|
||||
u4_t devaddr;
|
||||
u1_t framecounter;
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
} FRAM_data;
|
||||
|
||||
FRAM_data fram_data;
|
||||
bool usb_power_only;
|
||||
|
||||
// Im DEBUG-Modus werden Meldungen auf der seriellen Schnittstelle ausgegeben
|
||||
#define DEBUG 1
|
||||
|
||||
#define DONEPIN A5
|
||||
#define VBATPIN A7
|
||||
#define LONG_OFFSET 2147483648L
|
||||
#define MAX_CHARS 80
|
||||
#define MAX_SHORT 32767
|
||||
|
||||
#define NWKSKEY 1
|
||||
#define APPSKEY 2
|
||||
#define DEVADDR 3
|
||||
|
||||
uint8_t FRAM_CS = 10;
|
||||
|
||||
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI
|
||||
|
||||
// Temperatursensor
|
||||
Adafruit_Si7021 sensor = Adafruit_Si7021();
|
||||
|
||||
// die beiden Waagen
|
||||
HX711 scale1;
|
||||
HX711 scale2;
|
||||
|
||||
char charVal[MAX_CHARS];
|
||||
|
||||
// LORA
|
||||
// LoRaWAN NwkSKey, network session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
static const PROGMEM u1_t NWKSKEYJOERG[16] = { 0x5F, 0x33, 0x20, 0x8C, 0x13, 0x3A, 0x39, 0x3F, 0xA5, 0x6F, 0xE1, 0xC7, 0x9B, 0x78, 0x2B, 0xDF };
|
||||
|
||||
// LoRaWAN AppSKey, application session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
static const u1_t PROGMEM APPSKEYJOERG[16] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
|
||||
;
|
||||
|
||||
// LoRaWAN end-device address (DevAddr)
|
||||
static const u4_t DEVADDRJOERG = 0x260112C8 ; // <-- Change this address for every node!
|
||||
|
||||
// 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) { }
|
||||
void os_getDevEui (u1_t* buf) { }
|
||||
void os_getDevKey (u1_t* buf) { }
|
||||
|
||||
static uint8_t mydata[] = "Hello, world!";
|
||||
static osjob_t sendjob;
|
||||
|
||||
// Pin mapping
|
||||
const lmic_pinmap lmic_pins = {
|
||||
.nss = 8,
|
||||
.rxtx = LMIC_UNUSED_PIN,
|
||||
.rst = 4,
|
||||
.dio = {3, 6, LMIC_UNUSED_PIN},
|
||||
};
|
||||
|
||||
void onEvent (ev_t ev) {
|
||||
Serial.print(os_getTime());
|
||||
Serial.println("Received Lora Event: ");
|
||||
switch (ev) {
|
||||
case EV_SCAN_TIMEOUT:
|
||||
Serial.println(F("EV_SCAN_TIMEOUT"));
|
||||
break;
|
||||
case EV_BEACON_FOUND:
|
||||
Serial.println(F("EV_BEACON_FOUND"));
|
||||
break;
|
||||
case EV_BEACON_MISSED:
|
||||
Serial.println(F("EV_BEACON_MISSED"));
|
||||
break;
|
||||
case EV_BEACON_TRACKED:
|
||||
Serial.println(F("EV_BEACON_TRACKED"));
|
||||
break;
|
||||
case EV_JOINING:
|
||||
Serial.println(F("EV_JOINING"));
|
||||
break;
|
||||
case EV_JOINED:
|
||||
Serial.println(F("EV_JOINED"));
|
||||
break;
|
||||
case EV_RFU1:
|
||||
Serial.println(F("EV_RFU1"));
|
||||
break;
|
||||
case EV_JOIN_FAILED:
|
||||
Serial.println(F("EV_JOIN_FAILED"));
|
||||
break;
|
||||
case EV_REJOIN_FAILED:
|
||||
Serial.println(F("EV_REJOIN_FAILED"));
|
||||
break;
|
||||
case EV_TXCOMPLETE:
|
||||
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
|
||||
if (LMIC.txrxFlags & TXRX_ACK)
|
||||
Serial.println(F("Received ack"));
|
||||
if (LMIC.dataLen) {
|
||||
Serial.println(F("Received "));
|
||||
Serial.println(LMIC.dataLen);
|
||||
Serial.println(F(" bytes of payload"));
|
||||
}
|
||||
lora_package_sent = true;
|
||||
break;
|
||||
case EV_LOST_TSYNC:
|
||||
Serial.println(F("EV_LOST_TSYNC"));
|
||||
break;
|
||||
case EV_RESET:
|
||||
Serial.println(F("EV_RESET"));
|
||||
break;
|
||||
case EV_RXCOMPLETE:
|
||||
// data received in ping slot
|
||||
Serial.println(F("EV_RXCOMPLETE"));
|
||||
break;
|
||||
case EV_LINK_DEAD:
|
||||
Serial.println(F("EV_LINK_DEAD"));
|
||||
break;
|
||||
case EV_LINK_ALIVE:
|
||||
Serial.println(F("EV_LINK_ALIVE"));
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown event"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void do_send(osjob_t* j) {
|
||||
// Check if there is not a current TX/RX job running
|
||||
if (LMIC.opmode & OP_TXRXPEND) {
|
||||
Serial.println(F("OP_TXRXPEND, not sending"));
|
||||
} else {
|
||||
// Prepare upstream data transmission at the next possible time.
|
||||
//#define VBATPIN A7
|
||||
// float measuredvbat = analogRead(VBATPIN);
|
||||
// measuredvbat *= 2; // we divided by 2, so multiply back
|
||||
// measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
|
||||
// measuredvbat /= 1024; // convert to voltage
|
||||
// char buffer[8];
|
||||
|
||||
// dtostrf(measuredvbat, 1, 2, buffer);
|
||||
//String res = buffer;
|
||||
//res.getBytes(buffer, res.length() + 1);
|
||||
// Serial.print("VBat: " ); Serial.println(measuredvbat);
|
||||
//LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(lora_data), 0);
|
||||
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
|
||||
ShowLoraData();
|
||||
|
||||
Serial.println(F("Packet queued"));
|
||||
}
|
||||
// Next TX is scheduled after TX_COMPLETE event.
|
||||
}
|
||||
|
||||
// END LORA
|
||||
|
||||
|
||||
|
||||
// Error-Fuction
|
||||
void error(const __FlashStringHelper*err) {
|
||||
#if DEBUG
|
||||
Serial.println(err);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Print Function
|
||||
void print_debug(const char *InputString) {
|
||||
#if DEBUG
|
||||
//Serial.println(millis());
|
||||
Serial.println(InputString);
|
||||
#endif
|
||||
}
|
||||
|
||||
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, int which) {
|
||||
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 (which == APPSKEY) {
|
||||
memcpy(&fram_data.appskey, &cmd, 16);
|
||||
} else if (which == NWKSKEY) {
|
||||
memcpy(&fram_data.nwkskey, &cmd, 16);
|
||||
} else if (which == DEVADDR) {
|
||||
fram_data.devaddr = strtol(cmd, 0, 16);
|
||||
} else {
|
||||
print_debug("Invalid which");
|
||||
}
|
||||
Save2FRAM();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
float GetTemp() {
|
||||
return sensor.readTemperature();
|
||||
}
|
||||
|
||||
float 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;
|
||||
}
|
||||
|
||||
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\": \"");
|
||||
u1_t barr[4];
|
||||
memcpy(&barr, &fram_data.devaddr, 4);
|
||||
print_byte_array(barr, 4);
|
||||
Serial.println("\",");
|
||||
Serial.print(" \"framecounter\": ");
|
||||
Serial.print(fram_data.framecounter);
|
||||
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(" \"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.println("}");
|
||||
}
|
||||
|
||||
void Setup() {
|
||||
bool exitloop;
|
||||
Serial.println("{ \"msg\": \"Entering setup mode\" }");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
exitloop = (s == "exit");
|
||||
while (!exitloop) {
|
||||
|
||||
if (s == "") {
|
||||
//Serial.println("Leerzeile wird ignoriert...");
|
||||
}
|
||||
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 == "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") {
|
||||
Serial.println("getrawvalues...");
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
long raw_weight2 = scale2.read_average(3);
|
||||
Serial.println("getrawvalues after read scales..");
|
||||
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 == "getframdata") {
|
||||
ShowFRAMData();
|
||||
}
|
||||
else if (s == "sendloradata") {
|
||||
send_lora_data = true;
|
||||
exitloop = true;
|
||||
Serial.println("{ \"msg\": \"sendloradata sent\" }");
|
||||
}
|
||||
else if (s == "calibrate_zero_1") {
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
fram_data.cal_w1_0 = 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 = 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);
|
||||
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 = (w2_gramm.toFloat() / (float)raw_weight2);
|
||||
Serial.println("{ \"msg\": \"calibrate_2 was successful\" }");
|
||||
}
|
||||
else {
|
||||
Serial.println("{ \"msg\": \"You sent me an unknown command (exit to quit setup mode)\" }");
|
||||
}
|
||||
s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
exitloop == exitloop || (s == "exit");
|
||||
}
|
||||
Serial.println("We exited the setup loop...");
|
||||
}
|
||||
|
||||
void Save2FRAM()
|
||||
{
|
||||
fram.writeEnable(true);
|
||||
fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) );
|
||||
fram.writeEnable(false);
|
||||
}
|
||||
|
||||
void ShowLoraData()
|
||||
{
|
||||
Serial.println("Lora Packet:");
|
||||
Serial.println(lora_data.version);
|
||||
Serial.println(lora_data.weight[0]);
|
||||
Serial.println(lora_data.weight[1]);
|
||||
Serial.println(lora_data.weight[2]);
|
||||
Serial.println(lora_data.weight[3]);
|
||||
Serial.println(lora_data.weight[4]);
|
||||
Serial.println(lora_data.temperature[0]);
|
||||
Serial.println(lora_data.temperature[1]);
|
||||
Serial.println(lora_data.temperature[2]);
|
||||
Serial.println(lora_data.temperature[3]);
|
||||
Serial.println(lora_data.temperature[4]);
|
||||
Serial.println(lora_data.vbat);
|
||||
Serial.print("Size of Lora Package: ");
|
||||
Serial.println(sizeof(LORA_data));
|
||||
}
|
||||
|
||||
// Alles wird im Setup erledigt...
|
||||
void setup(void)
|
||||
{
|
||||
// DONEPIN must be low...
|
||||
pinMode(DONEPIN, OUTPUT);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
|
||||
// Reading battery; Value is in Millivolts
|
||||
float 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_power_only = (vbat >= 4400);
|
||||
|
||||
if (usb_power_only) {
|
||||
// Initialize serial and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(6000);
|
||||
while (!Serial);
|
||||
print_debug("Serial Port initialized");
|
||||
}
|
||||
// fuer Debugzwecke
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
if (usb_power_only) {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
} else {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
// FRAM
|
||||
// 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()) {
|
||||
print_debug("Found SPI FRAM");
|
||||
} else {
|
||||
print_debug("No SPI FRAM found ... check your connections\r\n");
|
||||
while (1);
|
||||
}
|
||||
|
||||
// wir lesen die FRAM-Werte
|
||||
fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data));
|
||||
|
||||
//ShowFRAMData();
|
||||
// END FRAM
|
||||
|
||||
// zuerst wird der HX711 initialisiert
|
||||
print_debug("Initializing the scale");
|
||||
scale1.begin(A3, A2);
|
||||
scale2.begin(A1, A0);
|
||||
|
||||
// Jetzt initialisieren wir den Si7021
|
||||
if (!sensor.begin()) {
|
||||
print_debug("Did not find Si7021 sensor!");
|
||||
}
|
||||
|
||||
boolean success;
|
||||
|
||||
fram_data.weight[fram_data.my_position] = GetWeight();
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
// Wenn die Differenz des Gewichts zu gross ist oder alle Messplaetze belegt sind senden wir das Paket...
|
||||
if (fram_data.my_position > 0) {
|
||||
if ((fram_data.weight[fram_data.my_position] - fram_data.weight[fram_data.my_position - 1]) >= 9999999) {
|
||||
Serial.println("CCC");
|
||||
Serial.println(fram_data.my_position);
|
||||
send_lora_data = true;
|
||||
}
|
||||
}
|
||||
fram_data.my_position++;
|
||||
|
||||
if (fram_data.my_position == MAX_VALUES_TO_SEND) {
|
||||
send_lora_data = true;
|
||||
}
|
||||
|
||||
Serial.print("send_lora_data:");
|
||||
Serial.print(send_lora_data);
|
||||
if (send_lora_data) {
|
||||
Serial.println("SendLoraPacket");
|
||||
lora_data.version = 1;
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
lora_data.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
lora_data.vbat = (byte)(vbat / 20);
|
||||
|
||||
|
||||
// LORA
|
||||
#ifdef VCC_ENABLE
|
||||
// For Pinoccio Scout boards
|
||||
pinMode(VCC_ENABLE, OUTPUT);
|
||||
digitalWrite(VCC_ENABLE, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
// LMIC init
|
||||
os_init();
|
||||
// Reset the MAC state. Session and pending data transfers will be discarded.
|
||||
LMIC_reset();
|
||||
|
||||
// Set static session parameters. Instead of dynamically establishing a session
|
||||
// by joining the network, precomputed session parameters are be provided.
|
||||
#ifdef PROGMEM
|
||||
Serial.println("PROGMEM...");
|
||||
// On AVR, these values are stored in flash and only copied to RAM
|
||||
// once. Copy them to a temporary buffer here, LMIC_setSession will
|
||||
// copy them into a buffer of its own again.
|
||||
uint8_t appskey[sizeof(fram_data.appskey)];
|
||||
uint8_t nwkskey[sizeof(fram_data.nwkskey)];
|
||||
memcpy_P(appskey, fram_data.appskey, sizeof(fram_data.appskey));
|
||||
memcpy_P(nwkskey, fram_data.nwkskey, sizeof(fram_data.nwkskey));
|
||||
LMIC_setSession (0x1, DEVADDRJOERG, nwkskey, appskey);
|
||||
#else
|
||||
// If not running an AVR with PROGMEM, just use the arrays directly
|
||||
Serial.println("NOT PROGMEM...");
|
||||
//LMIC_setSession (0x1, DEVADDRJOERG, NWKSKEYJOERG, APPSKEYJOERG);
|
||||
#endif
|
||||
LMIC_setSession (0x1, fram_data.devaddr, nwkskey, appskey);
|
||||
|
||||
#if defined(CFG_eu868)
|
||||
// Set up the channels used by the Things Network, which corresponds
|
||||
// to the defaults of most gateways. Without this, only three base
|
||||
// channels from the LoRaWAN specification are used, which certainly
|
||||
// works, so it is good for debugging, but can overload those
|
||||
// frequencies, so be sure to configure the full frequency range of
|
||||
// your network here (unless your network autoconfigures them).
|
||||
// Setting up channels should happen after LMIC_setSession, as that
|
||||
// configures the minimal channel set.
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
|
||||
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
|
||||
// devices' ping slots. LMIC does not have an easy way to define set this
|
||||
// frequency and support for class B is spotty and untested, so this
|
||||
// frequency is not configured here.
|
||||
#elif defined(CFG_us915)
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
// but only one group of 8 should (a subband) should be active
|
||||
// TTN recommends the second sub band, 1 in a zero based count.
|
||||
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
|
||||
LMIC_selectSubBand(1);
|
||||
#endif
|
||||
|
||||
// Disable link check validation
|
||||
LMIC_setLinkCheckMode(0);
|
||||
|
||||
// TTN uses SF9 for its RX2 window.
|
||||
LMIC.dn2Dr = DR_SF9;
|
||||
|
||||
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
|
||||
LMIC_setDrTxpow(DR_SF7, 14);
|
||||
|
||||
// Start job
|
||||
do_send(&sendjob);
|
||||
|
||||
// END LORA
|
||||
|
||||
while (!lora_package_sent || timeout_elapsed) {
|
||||
Serial.println("Run os_runloop_once...");
|
||||
os_runloop_once(); // EVTL. NOETIG?????
|
||||
}
|
||||
|
||||
}
|
||||
Serial.println("BLABLA");
|
||||
// soll evtl. das Setup durchgefuehrt werden?
|
||||
if (usb_power_only && Serial.available()) {
|
||||
Serial.println("GGGGBLABLA");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
if (s == "setup") {
|
||||
Serial.println("SETUPGGGGBLABLA");
|
||||
Setup();
|
||||
} else {
|
||||
Serial.println("{ \"msg\": \"Unknown command (only setup is allowed here)\"");
|
||||
}
|
||||
}
|
||||
|
||||
// dump the entire 8K of memory!
|
||||
if (usb_power_only) {
|
||||
uint8_t value;
|
||||
for (uint16_t a = 0; a < sizeof(FRAM_data); a++) {
|
||||
value = fram.read8(a);
|
||||
if ((a % 32) == 0) {
|
||||
Serial.print("\n 0x"); Serial.print(a, HEX); Serial.print(": ");
|
||||
}
|
||||
Serial.print("0x");
|
||||
if (value < 0x1)
|
||||
Serial.print('0');
|
||||
Serial.print(value, HEX); Serial.print(" ");
|
||||
}
|
||||
Serial.print("\nsizeof(FRAM_data): " + String(sizeof(FRAM_data)));
|
||||
}
|
||||
|
||||
// Jetzt sichern wir die Werte
|
||||
Save2FRAM();
|
||||
|
||||
//ShowFRAMData();
|
||||
|
||||
#if DEBUG
|
||||
delay(3000);
|
||||
#endif
|
||||
|
||||
// Jetzt koennen wir die FRAM-Werte wieder initialisieren
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
if (not(send_lora_data)) {
|
||||
print_debug("Jetzt senden wir das DONE Signal...");
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
// Hier haben wir nichts zu tun, wir machen alles im Setup...
|
||||
}
|
||||
|
|
@ -0,0 +1,713 @@
|
|||
/*********************************************************************
|
||||
BeieliScale by nbit Informatik GmbH
|
||||
|
||||
*********************************************************************/
|
||||
#include <lmic.h>
|
||||
#include <hal/hal.h>
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <avr/dtostrf.h>
|
||||
#include "HX711.h"
|
||||
#include "Adafruit_Si7021.h"
|
||||
#include "Adafruit_FRAM_SPI.h"
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
|
||||
// Required for Serial on Zero based boards
|
||||
#define Serial SERIAL_PORT_USBVIRTUAL
|
||||
#endif
|
||||
|
||||
#define MAX_VALUES_TO_SEND 5
|
||||
|
||||
bool send_lora_data = false;
|
||||
bool lora_package_sent = false;
|
||||
bool timeout_elapsed = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} LORA_data;
|
||||
|
||||
LORA_data lora_data;
|
||||
|
||||
typedef struct {
|
||||
u1_t nwkskey[16];
|
||||
u1_t appskey[16];
|
||||
u4_t devaddr;
|
||||
u1_t framecounter;
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
} FRAM_data;
|
||||
|
||||
FRAM_data fram_data;
|
||||
bool usb_power_only;
|
||||
|
||||
// Im DEBUG-Modus werden Meldungen auf der seriellen Schnittstelle ausgegeben
|
||||
#define DEBUG 1
|
||||
|
||||
#define DONEPIN A5
|
||||
#define VBATPIN A7
|
||||
#define LONG_OFFSET 2147483648L
|
||||
#define MAX_CHARS 80
|
||||
#define MAX_SHORT 32767
|
||||
|
||||
#define NWKSKEY 1
|
||||
#define APPSKEY 2
|
||||
#define DEVADDR 3
|
||||
|
||||
uint8_t FRAM_CS = 10;
|
||||
|
||||
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI
|
||||
|
||||
// Temperatursensor
|
||||
Adafruit_Si7021 sensor = Adafruit_Si7021();
|
||||
|
||||
// die beiden Waagen
|
||||
HX711 scale1;
|
||||
HX711 scale2;
|
||||
|
||||
char charVal[MAX_CHARS];
|
||||
|
||||
// LORA
|
||||
// LoRaWAN NwkSKey, network session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
static const PROGMEM u1_t NWKSKEYJOERG[16] = { 0x5F, 0x33, 0x20, 0x8C, 0x13, 0x3A, 0x39, 0x3F, 0xA5, 0x6F, 0xE1, 0xC7, 0x9B, 0x78, 0x2B, 0xDF };
|
||||
|
||||
// LoRaWAN AppSKey, application session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
static const u1_t PROGMEM APPSKEYJOERG[16] = { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
|
||||
;
|
||||
|
||||
// LoRaWAN end-device address (DevAddr)
|
||||
static const u4_t DEVADDRJOERG = 0x260112C8 ; // <-- Change this address for every node!
|
||||
|
||||
// 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) { }
|
||||
void os_getDevEui (u1_t* buf) { }
|
||||
void os_getDevKey (u1_t* buf) { }
|
||||
|
||||
static uint8_t mydata[] = "Hello, world!";
|
||||
static osjob_t sendjob;
|
||||
|
||||
// Pin mapping
|
||||
const lmic_pinmap lmic_pins = {
|
||||
.nss = 8,
|
||||
.rxtx = LMIC_UNUSED_PIN,
|
||||
.rst = 4,
|
||||
.dio = {3, 6, LMIC_UNUSED_PIN},
|
||||
};
|
||||
|
||||
void onEvent (ev_t ev) {
|
||||
Serial.print(os_getTime());
|
||||
Serial.println("Received Lora Event: ");
|
||||
switch (ev) {
|
||||
case EV_SCAN_TIMEOUT:
|
||||
Serial.println(F("EV_SCAN_TIMEOUT"));
|
||||
break;
|
||||
case EV_BEACON_FOUND:
|
||||
Serial.println(F("EV_BEACON_FOUND"));
|
||||
break;
|
||||
case EV_BEACON_MISSED:
|
||||
Serial.println(F("EV_BEACON_MISSED"));
|
||||
break;
|
||||
case EV_BEACON_TRACKED:
|
||||
Serial.println(F("EV_BEACON_TRACKED"));
|
||||
break;
|
||||
case EV_JOINING:
|
||||
Serial.println(F("EV_JOINING"));
|
||||
break;
|
||||
case EV_JOINED:
|
||||
Serial.println(F("EV_JOINED"));
|
||||
break;
|
||||
case EV_RFU1:
|
||||
Serial.println(F("EV_RFU1"));
|
||||
break;
|
||||
case EV_JOIN_FAILED:
|
||||
Serial.println(F("EV_JOIN_FAILED"));
|
||||
break;
|
||||
case EV_REJOIN_FAILED:
|
||||
Serial.println(F("EV_REJOIN_FAILED"));
|
||||
break;
|
||||
case EV_TXCOMPLETE:
|
||||
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
|
||||
if (LMIC.txrxFlags & TXRX_ACK)
|
||||
Serial.println(F("Received ack"));
|
||||
if (LMIC.dataLen) {
|
||||
Serial.println(F("Received "));
|
||||
Serial.println(LMIC.dataLen);
|
||||
Serial.println(F(" bytes of payload"));
|
||||
}
|
||||
lora_package_sent = true;
|
||||
break;
|
||||
case EV_LOST_TSYNC:
|
||||
Serial.println(F("EV_LOST_TSYNC"));
|
||||
break;
|
||||
case EV_RESET:
|
||||
Serial.println(F("EV_RESET"));
|
||||
break;
|
||||
case EV_RXCOMPLETE:
|
||||
// data received in ping slot
|
||||
Serial.println(F("EV_RXCOMPLETE"));
|
||||
break;
|
||||
case EV_LINK_DEAD:
|
||||
Serial.println(F("EV_LINK_DEAD"));
|
||||
break;
|
||||
case EV_LINK_ALIVE:
|
||||
Serial.println(F("EV_LINK_ALIVE"));
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown event"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void do_send(osjob_t* j) {
|
||||
// Check if there is not a current TX/RX job running
|
||||
if (LMIC.opmode & OP_TXRXPEND) {
|
||||
Serial.println(F("OP_TXRXPEND, not sending"));
|
||||
} else {
|
||||
// Prepare upstream data transmission at the next possible time.
|
||||
//#define VBATPIN A7
|
||||
// float measuredvbat = analogRead(VBATPIN);
|
||||
// measuredvbat *= 2; // we divided by 2, so multiply back
|
||||
// measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
|
||||
// measuredvbat /= 1024; // convert to voltage
|
||||
// char buffer[8];
|
||||
|
||||
// dtostrf(measuredvbat, 1, 2, buffer);
|
||||
//String res = buffer;
|
||||
//res.getBytes(buffer, res.length() + 1);
|
||||
// Serial.print("VBat: " ); Serial.println(measuredvbat);
|
||||
//LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(lora_data), 0);
|
||||
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
|
||||
ShowLoraData();
|
||||
|
||||
Serial.println(F("Packet queued"));
|
||||
}
|
||||
// Next TX is scheduled after TX_COMPLETE event.
|
||||
}
|
||||
|
||||
// END LORA
|
||||
|
||||
|
||||
|
||||
// Error-Fuction
|
||||
void error(const __FlashStringHelper*err) {
|
||||
#if DEBUG
|
||||
Serial.println(err);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Print Function
|
||||
void print_debug(const char *InputString) {
|
||||
#if DEBUG
|
||||
//Serial.println(millis());
|
||||
Serial.println(InputString);
|
||||
#endif
|
||||
}
|
||||
|
||||
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, int which) {
|
||||
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 (which == APPSKEY) {
|
||||
memcpy(&fram_data.appskey, &cmd, 16);
|
||||
} else if (which == NWKSKEY) {
|
||||
memcpy(&fram_data.nwkskey, &cmd, 16);
|
||||
} else if (which == DEVADDR) {
|
||||
print_debug("write device address");
|
||||
// Serial.println(hin)
|
||||
// Serial.println(strtol(hin, 0, 16));
|
||||
fram_data.devaddr = strtol(hin, 0, 16);
|
||||
} else {
|
||||
print_debug("Invalid which");
|
||||
}
|
||||
Save2FRAM();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
float GetTemp() {
|
||||
return sensor.readTemperature();
|
||||
}
|
||||
|
||||
float 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;
|
||||
}
|
||||
|
||||
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(" \"framecounter\": ");
|
||||
Serial.print(fram_data.framecounter);
|
||||
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(" \"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.println("}");
|
||||
}
|
||||
|
||||
void Setup() {
|
||||
bool exitloop;
|
||||
Serial.println("{ \"msg\": \"Entering setup mode\" }");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
exitloop = (s == "exit");
|
||||
while (!exitloop) {
|
||||
|
||||
if (s == "") {
|
||||
//Serial.println("Leerzeile wird ignoriert...");
|
||||
}
|
||||
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 == "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") {
|
||||
Serial.println("getrawvalues...");
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
long raw_weight2 = scale2.read_average(3);
|
||||
Serial.println("getrawvalues after read scales..");
|
||||
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 == "getframdata") {
|
||||
ShowFRAMData();
|
||||
}
|
||||
else if (s == "sendloradata") {
|
||||
send_lora_data = true;
|
||||
exitloop = true;
|
||||
Serial.println("{ \"msg\": \"sendloradata sent\" }");
|
||||
}
|
||||
else if (s == "calibrate_zero_1") {
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
fram_data.cal_w1_0 = 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 = 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);
|
||||
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 = (w2_gramm.toFloat() / (float)raw_weight2);
|
||||
Serial.println("{ \"msg\": \"calibrate_2 was successful\" }");
|
||||
}
|
||||
else {
|
||||
Serial.println("{ \"msg\": \"You sent me an unknown command (exit to quit setup mode)\" }");
|
||||
}
|
||||
s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
exitloop == exitloop || (s == "exit");
|
||||
}
|
||||
Serial.println("We exited the setup loop...");
|
||||
}
|
||||
|
||||
void Save2FRAM()
|
||||
{
|
||||
fram.writeEnable(true);
|
||||
fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) );
|
||||
fram.writeEnable(false);
|
||||
}
|
||||
|
||||
void ShowLoraData()
|
||||
{
|
||||
Serial.println("Lora Packet:");
|
||||
Serial.println(lora_data.version);
|
||||
Serial.println(lora_data.weight[0]);
|
||||
Serial.println(lora_data.weight[1]);
|
||||
Serial.println(lora_data.weight[2]);
|
||||
Serial.println(lora_data.weight[3]);
|
||||
Serial.println(lora_data.weight[4]);
|
||||
Serial.println(lora_data.temperature[0]);
|
||||
Serial.println(lora_data.temperature[1]);
|
||||
Serial.println(lora_data.temperature[2]);
|
||||
Serial.println(lora_data.temperature[3]);
|
||||
Serial.println(lora_data.temperature[4]);
|
||||
Serial.println(lora_data.vbat);
|
||||
Serial.print("Size of Lora Package: ");
|
||||
Serial.println(sizeof(LORA_data));
|
||||
}
|
||||
|
||||
// Alles wird im Setup erledigt...
|
||||
void setup(void)
|
||||
{
|
||||
// DONEPIN must be low...
|
||||
pinMode(DONEPIN, OUTPUT);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
|
||||
// Reading battery; Value is in Millivolts
|
||||
float 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_power_only = (vbat >= 4400);
|
||||
|
||||
if (usb_power_only) {
|
||||
// Initialize serial and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(6000);
|
||||
//while (!Serial);
|
||||
print_debug("Serial Port initialized");
|
||||
}
|
||||
// fuer Debugzwecke
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
if (usb_power_only) {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
} else {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
// FRAM
|
||||
// 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()) {
|
||||
print_debug("Found SPI FRAM");
|
||||
} else {
|
||||
print_debug("No SPI FRAM found ... check your connections\r\n");
|
||||
while (1);
|
||||
}
|
||||
|
||||
// wir lesen die FRAM-Werte
|
||||
fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data));
|
||||
|
||||
//ShowFRAMData();
|
||||
// END FRAM
|
||||
|
||||
// zuerst wird der HX711 initialisiert
|
||||
print_debug("Initializing the scale");
|
||||
scale1.begin(A3, A2);
|
||||
scale2.begin(A1, A0);
|
||||
|
||||
// Jetzt initialisieren wir den Si7021
|
||||
if (!sensor.begin()) {
|
||||
print_debug("Did not find Si7021 sensor!");
|
||||
}
|
||||
|
||||
boolean success;
|
||||
|
||||
fram_data.weight[fram_data.my_position] = GetWeight();
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
// Wenn die Differenz des Gewichts zu gross ist oder alle Messplaetze belegt sind senden wir das Paket...
|
||||
if (fram_data.my_position > 0) {
|
||||
if ((fram_data.weight[fram_data.my_position] - fram_data.weight[fram_data.my_position - 1]) >= 9999999) {
|
||||
Serial.println("CCC");
|
||||
Serial.println(fram_data.my_position);
|
||||
send_lora_data = true;
|
||||
}
|
||||
}
|
||||
fram_data.my_position++;
|
||||
|
||||
if (fram_data.my_position == MAX_VALUES_TO_SEND) {
|
||||
send_lora_data = true;
|
||||
}
|
||||
|
||||
Serial.print("send_lora_data:");
|
||||
Serial.print(send_lora_data);
|
||||
if (send_lora_data) {
|
||||
Serial.println("SendLoraPacket");
|
||||
lora_data.version = 1;
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
lora_data.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
lora_data.vbat = (byte)(vbat / 20);
|
||||
|
||||
|
||||
// LORA
|
||||
#ifdef VCC_ENABLE
|
||||
// For Pinoccio Scout boards
|
||||
pinMode(VCC_ENABLE, OUTPUT);
|
||||
digitalWrite(VCC_ENABLE, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
// LMIC init
|
||||
os_init();
|
||||
// Reset the MAC state. Session and pending data transfers will be discarded.
|
||||
LMIC_reset();
|
||||
|
||||
// Set static session parameters. Instead of dynamically establishing a session
|
||||
// by joining the network, precomputed session parameters are be provided.
|
||||
#ifdef PROGMEM
|
||||
Serial.println("PROGMEM...");
|
||||
// On AVR, these values are stored in flash and only copied to RAM
|
||||
// once. Copy them to a temporary buffer here, LMIC_setSession will
|
||||
// copy them into a buffer of its own again.
|
||||
uint8_t appskey[sizeof(fram_data.appskey)];
|
||||
uint8_t nwkskey[sizeof(fram_data.nwkskey)];
|
||||
memcpy_P(appskey, fram_data.appskey, sizeof(fram_data.appskey));
|
||||
memcpy_P(nwkskey, fram_data.nwkskey, sizeof(fram_data.nwkskey));
|
||||
LMIC_setSession (0x1, DEVADDRJOERG, nwkskey, appskey);
|
||||
#else
|
||||
// If not running an AVR with PROGMEM, just use the arrays directly
|
||||
Serial.println("NOT PROGMEM...");
|
||||
//LMIC_setSession (0x1, DEVADDRJOERG, NWKSKEYJOERG, APPSKEYJOERG);
|
||||
#endif
|
||||
LMIC_setSession (0x1, fram_data.devaddr, nwkskey, appskey);
|
||||
|
||||
#if defined(CFG_eu868)
|
||||
// Set up the channels used by the Things Network, which corresponds
|
||||
// to the defaults of most gateways. Without this, only three base
|
||||
// channels from the LoRaWAN specification are used, which certainly
|
||||
// works, so it is good for debugging, but can overload those
|
||||
// frequencies, so be sure to configure the full frequency range of
|
||||
// your network here (unless your network autoconfigures them).
|
||||
// Setting up channels should happen after LMIC_setSession, as that
|
||||
// configures the minimal channel set.
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
|
||||
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
|
||||
// devices' ping slots. LMIC does not have an easy way to define set this
|
||||
// frequency and support for class B is spotty and untested, so this
|
||||
// frequency is not configured here.
|
||||
#elif defined(CFG_us915)
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
// but only one group of 8 should (a subband) should be active
|
||||
// TTN recommends the second sub band, 1 in a zero based count.
|
||||
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
|
||||
LMIC_selectSubBand(1);
|
||||
#endif
|
||||
|
||||
// Disable link check validation
|
||||
LMIC_setLinkCheckMode(0);
|
||||
|
||||
// TTN uses SF9 for its RX2 window.
|
||||
LMIC.dn2Dr = DR_SF9;
|
||||
|
||||
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
|
||||
LMIC_setDrTxpow(DR_SF7, 14);
|
||||
|
||||
// Start job
|
||||
do_send(&sendjob);
|
||||
|
||||
// END LORA
|
||||
|
||||
while (!lora_package_sent || timeout_elapsed) {
|
||||
Serial.println("Run os_runloop_once...");
|
||||
os_runloop_once(); // EVTL. NOETIG?????
|
||||
}
|
||||
|
||||
}
|
||||
Serial.println("BLABLA");
|
||||
// soll evtl. das Setup durchgefuehrt werden?
|
||||
if (usb_power_only && Serial.available()) {
|
||||
Serial.println("GGGGBLABLA");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
if (s == "setup") {
|
||||
Serial.println("SETUPGGGGBLABLA");
|
||||
Setup();
|
||||
} else {
|
||||
Serial.println("{ \"msg\": \"Unknown command (only setup is allowed here)\"");
|
||||
}
|
||||
}
|
||||
|
||||
// dump the entire 8K of memory!
|
||||
if (usb_power_only) {
|
||||
uint8_t value;
|
||||
for (uint16_t a = 0; a < sizeof(FRAM_data); a++) {
|
||||
value = fram.read8(a);
|
||||
if ((a % 32) == 0) {
|
||||
Serial.print("\n 0x"); Serial.print(a, HEX); Serial.print(": ");
|
||||
}
|
||||
Serial.print("0x");
|
||||
if (value < 0x1)
|
||||
Serial.print('0');
|
||||
Serial.print(value, HEX); Serial.print(" ");
|
||||
}
|
||||
Serial.print("\nsizeof(FRAM_data): " + String(sizeof(FRAM_data)));
|
||||
}
|
||||
|
||||
// Jetzt sichern wir die Werte
|
||||
Save2FRAM();
|
||||
|
||||
//ShowFRAMData();
|
||||
|
||||
#if DEBUG
|
||||
delay(3000);
|
||||
#endif
|
||||
|
||||
// Jetzt koennen wir die FRAM-Werte wieder initialisieren
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
if (not(send_lora_data)) {
|
||||
print_debug("Jetzt senden wir das DONE Signal...");
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
// Hier haben wir nichts zu tun, wir machen alles im Setup...
|
||||
}
|
||||
|
|
@ -0,0 +1,705 @@
|
|||
/*********************************************************************
|
||||
BeieliScale by nbit Informatik GmbH
|
||||
|
||||
*********************************************************************/
|
||||
#include <lmic.h>
|
||||
#include <hal/hal.h>
|
||||
#include <Arduino.h>
|
||||
#include <avr/dtostrf.h>
|
||||
#include "HX711.h"
|
||||
#include "Adafruit_Si7021.h"
|
||||
#include "Adafruit_FRAM_SPI.h"
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
|
||||
// Required for Serial on Zero based boards
|
||||
#define Serial SERIAL_PORT_USBVIRTUAL
|
||||
#endif
|
||||
|
||||
#define MAX_VALUES_TO_SEND 5
|
||||
#define TIMEOUTMS 60000
|
||||
|
||||
bool send_lora_data = false;
|
||||
bool lora_data_sent = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} LORA_data;
|
||||
|
||||
LORA_data lora_data;
|
||||
|
||||
typedef struct {
|
||||
u1_t nwkskey[16];
|
||||
u1_t appskey[16];
|
||||
u4_t devaddr;
|
||||
u1_t framecounter;
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
} FRAM_data;
|
||||
|
||||
FRAM_data fram_data;
|
||||
bool usb_power_only;
|
||||
|
||||
// Im DEBUG-Modus werden Meldungen auf der seriellen Schnittstelle ausgegeben
|
||||
#define DEBUG 1
|
||||
|
||||
#define DONEPIN A5
|
||||
#define VBATPIN A7
|
||||
#define LONG_OFFSET 2147483648L
|
||||
#define MAX_CHARS 80
|
||||
#define MAX_SHORT 32767
|
||||
|
||||
#define NWKSKEY 1
|
||||
#define APPSKEY 2
|
||||
#define DEVADDR 3
|
||||
|
||||
uint8_t FRAM_CS = 10;
|
||||
|
||||
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI
|
||||
|
||||
|
||||
// Temperatursensor
|
||||
Adafruit_Si7021 sensor = Adafruit_Si7021();
|
||||
|
||||
// die beiden Waagen
|
||||
HX711 scale1;
|
||||
HX711 scale2;
|
||||
|
||||
char charVal[MAX_CHARS];
|
||||
|
||||
// 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) { }
|
||||
void os_getDevEui (u1_t* buf) { }
|
||||
void os_getDevKey (u1_t* buf) { }
|
||||
|
||||
static uint8_t mydata[] = "Hello, world!";
|
||||
static osjob_t sendjob;
|
||||
|
||||
// Pin mapping
|
||||
const lmic_pinmap lmic_pins = {
|
||||
.nss = 8,
|
||||
.rxtx = LMIC_UNUSED_PIN,
|
||||
.rst = 4,
|
||||
.dio = {3, 6, LMIC_UNUSED_PIN},
|
||||
};
|
||||
|
||||
void onEvent (ev_t ev) {
|
||||
Serial.print(os_getTime());
|
||||
Serial.println("Received Lora Event: ");
|
||||
switch (ev) {
|
||||
case EV_SCAN_TIMEOUT:
|
||||
Serial.println(F("EV_SCAN_TIMEOUT"));
|
||||
break;
|
||||
case EV_BEACON_FOUND:
|
||||
Serial.println(F("EV_BEACON_FOUND"));
|
||||
break;
|
||||
case EV_BEACON_MISSED:
|
||||
Serial.println(F("EV_BEACON_MISSED"));
|
||||
break;
|
||||
case EV_BEACON_TRACKED:
|
||||
Serial.println(F("EV_BEACON_TRACKED"));
|
||||
break;
|
||||
case EV_JOINING:
|
||||
Serial.println(F("EV_JOINING"));
|
||||
break;
|
||||
case EV_JOINED:
|
||||
Serial.println(F("EV_JOINED"));
|
||||
break;
|
||||
case EV_RFU1:
|
||||
Serial.println(F("EV_RFU1"));
|
||||
break;
|
||||
case EV_JOIN_FAILED:
|
||||
Serial.println(F("EV_JOIN_FAILED"));
|
||||
break;
|
||||
case EV_REJOIN_FAILED:
|
||||
Serial.println(F("EV_REJOIN_FAILED"));
|
||||
break;
|
||||
case EV_TXCOMPLETE:
|
||||
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
|
||||
if (LMIC.txrxFlags & TXRX_ACK)
|
||||
Serial.println(F("Received ack"));
|
||||
if (LMIC.dataLen) {
|
||||
Serial.println(F("Received "));
|
||||
Serial.println(LMIC.dataLen);
|
||||
Serial.println(F(" bytes of payload"));
|
||||
}
|
||||
lora_data_sent = true;
|
||||
break;
|
||||
case EV_LOST_TSYNC:
|
||||
Serial.println(F("EV_LOST_TSYNC"));
|
||||
break;
|
||||
case EV_RESET:
|
||||
Serial.println(F("EV_RESET"));
|
||||
break;
|
||||
case EV_RXCOMPLETE:
|
||||
// data received in ping slot
|
||||
Serial.println(F("EV_RXCOMPLETE"));
|
||||
break;
|
||||
case EV_LINK_DEAD:
|
||||
Serial.println(F("EV_LINK_DEAD"));
|
||||
break;
|
||||
case EV_LINK_ALIVE:
|
||||
Serial.println(F("EV_LINK_ALIVE"));
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown event"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void do_send(osjob_t* j) {
|
||||
// Check if there is not a current TX/RX job running
|
||||
if (LMIC.opmode & OP_TXRXPEND) {
|
||||
Serial.println(F("OP_TXRXPEND, not sending"));
|
||||
} else {
|
||||
// Prepare upstream data transmission at the next possible time.
|
||||
//#define VBATPIN A7
|
||||
// float measuredvbat = analogRead(VBATPIN);
|
||||
// measuredvbat *= 2; // we divided by 2, so multiply back
|
||||
// measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
|
||||
// measuredvbat /= 1024; // convert to voltage
|
||||
// char buffer[8];
|
||||
|
||||
// dtostrf(measuredvbat, 1, 2, buffer);
|
||||
//String res = buffer;
|
||||
//res.getBytes(buffer, res.length() + 1);
|
||||
// Serial.print("VBat: " ); Serial.println(measuredvbat);
|
||||
//LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(lora_data), 0);
|
||||
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
|
||||
//ShowLoraData();
|
||||
|
||||
Serial.println(F("Packet queued"));
|
||||
}
|
||||
// Next TX is scheduled after TX_COMPLETE event.
|
||||
}
|
||||
|
||||
// END LORA
|
||||
|
||||
|
||||
|
||||
// Print Function
|
||||
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, int which) {
|
||||
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 (which == APPSKEY) {
|
||||
memcpy(&fram_data.appskey, &cmd, 16);
|
||||
} else if (which == NWKSKEY) {
|
||||
memcpy(&fram_data.nwkskey, &cmd, 16);
|
||||
} else if (which == DEVADDR) {
|
||||
Serial.println("write device address");
|
||||
// Serial.println(hin)
|
||||
// Serial.println(strtol(hin, 0, 16));
|
||||
fram_data.devaddr = strtol(hin, 0, 16);
|
||||
} else {
|
||||
Serial.println("Invalid which");
|
||||
}
|
||||
Save2FRAM();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
float GetTemp() {
|
||||
return sensor.readTemperature();
|
||||
}
|
||||
|
||||
float 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;
|
||||
}
|
||||
|
||||
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(" \"framecounter\": ");
|
||||
Serial.print(fram_data.framecounter);
|
||||
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(" \"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.println("}");
|
||||
}
|
||||
|
||||
void Setup() {
|
||||
bool exitloop;
|
||||
Serial.println("{ \"msg\": \"Entering setup mode\" }");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
exitloop = (s == "exit");
|
||||
while (!exitloop) {
|
||||
|
||||
if (s == "") {
|
||||
//Serial.println("Leerzeile wird ignoriert...");
|
||||
}
|
||||
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 == "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") {
|
||||
Serial.println("getrawvalues...");
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
long raw_weight2 = scale2.read_average(3);
|
||||
Serial.println("getrawvalues after read scales..");
|
||||
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 == "getframdata") {
|
||||
ShowFRAMData();
|
||||
}
|
||||
else if (s == "sendloradata") {
|
||||
send_lora_data = true;
|
||||
exitloop = true;
|
||||
Serial.println("{ \"msg\": \"sendloradata sent\" }");
|
||||
}
|
||||
else if (s == "calibrate_zero_1") {
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
fram_data.cal_w1_0 = 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 = 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);
|
||||
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 = (w2_gramm.toFloat() / (float)raw_weight2);
|
||||
Serial.println("{ \"msg\": \"calibrate_2 was successful\" }");
|
||||
}
|
||||
else {
|
||||
Serial.println("{ \"msg\": \"You sent me an unknown command (exit to quit setup mode)\" }");
|
||||
}
|
||||
s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
exitloop = (exitloop || (s == "exit"));
|
||||
Serial.println("EXITLOOP:");
|
||||
Serial.print("@");
|
||||
Serial.print(s);
|
||||
Serial.println("@");
|
||||
Serial.println(exitloop);
|
||||
Serial.println(s == "exit");
|
||||
}
|
||||
Serial.println("We exited the setup loop...");
|
||||
}
|
||||
|
||||
void ReadFromFRAM()
|
||||
{
|
||||
// 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()) {
|
||||
Serial.println("Found SPI FRAM");
|
||||
} else {
|
||||
Serial.println("No SPI FRAM found ... check your connections\r\n");
|
||||
while (1);
|
||||
}
|
||||
|
||||
// wir lesen die FRAM-Werte
|
||||
Serial.println("PK3");
|
||||
fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data));
|
||||
Serial.println("PK4");
|
||||
}
|
||||
|
||||
void Save2FRAM()
|
||||
{
|
||||
// we set #8 to High (CS of Lora Module), to be able to use FRAM
|
||||
Serial.println("SAVE2FRAM10");
|
||||
pinMode(8, INPUT_PULLUP);
|
||||
digitalWrite(8, HIGH);
|
||||
Serial.println("SAVE2FRAM11");
|
||||
Serial.println("SAVE2FRAM12");
|
||||
|
||||
if (fram.begin()) {
|
||||
Serial.println("Found SPI FRAM");
|
||||
} else {
|
||||
Serial.println("No SPI FRAM found ... check your connections\r\n");
|
||||
while (1);
|
||||
}
|
||||
Serial.println("SAVE2FRAM1");
|
||||
fram.writeEnable(true);
|
||||
Serial.println("SAVE2FRAM2");
|
||||
fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) );
|
||||
Serial.println("SAVE2FRAM3");
|
||||
fram.writeEnable(false);
|
||||
Serial.println("SAVE2FRAM4");
|
||||
}
|
||||
|
||||
void ShowLoraData()
|
||||
{
|
||||
Serial.println("Lora Packet:");
|
||||
Serial.println(lora_data.version);
|
||||
Serial.println(lora_data.weight[0]);
|
||||
Serial.println(lora_data.weight[1]);
|
||||
Serial.println(lora_data.weight[2]);
|
||||
Serial.println(lora_data.weight[3]);
|
||||
Serial.println(lora_data.weight[4]);
|
||||
Serial.println(lora_data.temperature[0]);
|
||||
Serial.println(lora_data.temperature[1]);
|
||||
Serial.println(lora_data.temperature[2]);
|
||||
Serial.println(lora_data.temperature[3]);
|
||||
Serial.println(lora_data.temperature[4]);
|
||||
Serial.println(lora_data.vbat);
|
||||
Serial.print("Size of Lora Package: ");
|
||||
Serial.println(sizeof(LORA_data));
|
||||
}
|
||||
|
||||
// Alles wird im Setup erledigt...
|
||||
void setup(void)
|
||||
{
|
||||
// DONEPIN must be low...
|
||||
pinMode(DONEPIN, OUTPUT);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
|
||||
// Reading battery; Value is in Millivolts
|
||||
float 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_power_only = (vbat >= 4400);
|
||||
|
||||
if (usb_power_only) {
|
||||
// Initialize serial and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(6000);
|
||||
while (!Serial);
|
||||
Serial.println("Serial Port initialized");
|
||||
}
|
||||
Serial.println("PK2");
|
||||
|
||||
ReadFromFRAM();
|
||||
Serial.println("PK");
|
||||
Serial.println(usb_power_only);
|
||||
// fuer Debugzwecke
|
||||
// pinMode(LED_BUILTIN, OUTPUT);
|
||||
// if (usb_power_only) {
|
||||
// digitalWrite(LED_BUILTIN, HIGH);
|
||||
// } else {
|
||||
// digitalWrite(LED_BUILTIN, LOW);
|
||||
// }
|
||||
|
||||
// FRAM
|
||||
|
||||
//ShowFRAMData();
|
||||
// END FRAM
|
||||
|
||||
// zuerst wird der HX711 initialisiert
|
||||
Serial.println("Initializing the scale");
|
||||
scale1.begin(A3, A2);
|
||||
scale2.begin(A1, A0);
|
||||
|
||||
// Jetzt initialisieren wir den Si7021
|
||||
if (!sensor.begin()) {
|
||||
Serial.println("Did not find Si7021 sensor!");
|
||||
}
|
||||
|
||||
boolean success;
|
||||
|
||||
fram_data.weight[fram_data.my_position] = GetWeight();
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
// Wenn die Differenz des Gewichts zu gross ist oder alle Messplaetze belegt sind senden wir das Paket...
|
||||
Serial.println("PK5");
|
||||
if (fram_data.my_position > 0) {
|
||||
if ((fram_data.weight[fram_data.my_position] - fram_data.weight[fram_data.my_position - 1]) >= 9999999) {
|
||||
Serial.println("CCC");
|
||||
Serial.println(fram_data.my_position);
|
||||
send_lora_data = true;
|
||||
}
|
||||
}
|
||||
fram_data.my_position++;
|
||||
//fram_data.my_position=1;
|
||||
|
||||
if (fram_data.my_position >= MAX_VALUES_TO_SEND) {
|
||||
send_lora_data = true;
|
||||
}
|
||||
|
||||
Serial.print("send_lora_data:");
|
||||
Serial.print(send_lora_data);
|
||||
if (send_lora_data) {
|
||||
Serial.println("SendLoraPacket");
|
||||
|
||||
lora_data.version = 1;
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
lora_data.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
lora_data.vbat = (byte)(vbat / 20);
|
||||
|
||||
// LORA
|
||||
#ifdef VCC_ENABLE
|
||||
// For Pinoccio Scout boards
|
||||
Serial.println("PK6");
|
||||
pinMode(VCC_ENABLE, OUTPUT);
|
||||
digitalWrite(VCC_ENABLE, HIGH);
|
||||
delay(3000);
|
||||
#endif
|
||||
|
||||
// LMIC init
|
||||
|
||||
Serial.println("PK7");
|
||||
os_init();
|
||||
// Reset the MAC state. Session and pending data transfers will be discarded.
|
||||
Serial.println("PK8");
|
||||
LMIC_reset();
|
||||
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
|
||||
|
||||
// Set static session parameters. Instead of dynamically establishing a session
|
||||
// by joining the network, precomputed session parameters are be provided.
|
||||
LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
||||
|
||||
#if defined(CFG_eu868)
|
||||
// Set up the channels used by the Things Network, which corresponds
|
||||
// to the defaults of most gateways. Without this, only three base
|
||||
// channels from the LoRaWAN specification are used, which certainly
|
||||
// works, so it is good for debugging, but can overload those
|
||||
// frequencies, so be sure to configure the full frequency range of
|
||||
// your network here (unless your network autoconfigures them).
|
||||
// Setting up channels should happen after LMIC_setSession, as that
|
||||
// configures the minimal channel set.
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
|
||||
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
|
||||
// devices' ping slots. LMIC does not have an easy way to define set this
|
||||
// frequency and support for class B is spotty and untested, so this
|
||||
// frequency is not configured here.
|
||||
#elif defined(CFG_us915)
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
// but only one group of 8 should (a subband) should be active
|
||||
// TTN recommends the second sub band, 1 in a zero based count.
|
||||
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
|
||||
LMIC_selectSubBand(1);
|
||||
#endif
|
||||
|
||||
// Disable link check validation
|
||||
Serial.println("PK10");
|
||||
LMIC_setLinkCheckMode(0);
|
||||
|
||||
// TTN uses SF9 for its RX2 window.
|
||||
LMIC.dn2Dr = DR_SF9;
|
||||
|
||||
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
|
||||
LMIC_setDrTxpow(DR_SF7, 14);
|
||||
|
||||
// Start job
|
||||
Serial.println("PK11");
|
||||
do_send(&sendjob);
|
||||
|
||||
// END LORA
|
||||
unsigned long starttime;
|
||||
starttime = millis();
|
||||
while (!lora_data_sent && ((millis() - starttime) < TIMEOUTMS)) {
|
||||
os_runloop_once();
|
||||
if ((millis() % 1000) == 0) {
|
||||
Serial.println("Loop until sent...");
|
||||
}
|
||||
}
|
||||
if (lora_data_sent) {
|
||||
Serial.println("Lora Data was sent!");
|
||||
} else {
|
||||
Serial.println("Timeout elapsed...");
|
||||
}
|
||||
// Jetzt koennen wir die FRAM-Werte wieder initialisieren
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
}
|
||||
|
||||
Serial.println("BLABLA");
|
||||
// soll evtl. das Setup durchgefuehrt werden?
|
||||
if (usb_power_only && Serial.available()) {
|
||||
Serial.println("GGGGBLABLA");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
if (s == "setup") {
|
||||
Serial.println("SETUPGGGGBLABLA");
|
||||
Setup();
|
||||
} else {
|
||||
Serial.println("{ \"msg\": \"Unknown command (only setup is allowed here)\"");
|
||||
}
|
||||
}
|
||||
|
||||
Serial.println("DUMP 8K");
|
||||
|
||||
// Jetzt sichern wir die Werte
|
||||
Serial.println("SAVE FRAM");
|
||||
Save2FRAM();
|
||||
|
||||
//ShowFRAMData();
|
||||
|
||||
#if DEBUG
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
Serial.println("Jetzt senden wir das DONE Signal...");
|
||||
delay(1000);
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
// Hier haben wir nichts zu tun, wir machen alles im Setup...
|
||||
}
|
||||
|
|
@ -0,0 +1,676 @@
|
|||
/*********************************************************************
|
||||
BeieliScale by nbit Informatik GmbH
|
||||
|
||||
*********************************************************************/
|
||||
#include <lmic.h>
|
||||
#include <hal/hal.h>
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <avr/dtostrf.h>
|
||||
#include "HX711.h"
|
||||
#include "Adafruit_Si7021.h"
|
||||
#include "Adafruit_FRAM_SPI.h"
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
|
||||
// Required for Serial on Zero based boards
|
||||
#define Serial SERIAL_PORT_USBVIRTUAL
|
||||
#endif
|
||||
|
||||
#define MAX_VALUES_TO_SEND 5
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
int weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
int temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} LORA_data;
|
||||
|
||||
LORA_data lora_data;
|
||||
|
||||
typedef struct {
|
||||
u1_t nwkskey[16];
|
||||
u1_t appskey[16];
|
||||
u4_t devaddr;
|
||||
u1_t framecounter;
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
unsigned int weight[MAX_VALUES_TO_SEND];
|
||||
int temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
} FRAM_data;
|
||||
|
||||
FRAM_data fram_data;
|
||||
bool usb_power_only;
|
||||
|
||||
// Im DEBUG-Modus werden Meldungen auf der seriellen Schnittstelle ausgegeben
|
||||
#define DEBUG 1
|
||||
|
||||
#define DONEPIN A5
|
||||
#define VBATPIN A7
|
||||
#define LONG_OFFSET 2147483648L
|
||||
#define INT_OFFSET 32768
|
||||
#define MAX_CHARS 80
|
||||
|
||||
#define NWKSKEY 1
|
||||
#define APPSKEY 2
|
||||
#define DEVADDR 3
|
||||
|
||||
uint8_t FRAM_CS = 10;
|
||||
|
||||
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI
|
||||
|
||||
// Temperatursensor
|
||||
Adafruit_Si7021 sensor = Adafruit_Si7021();
|
||||
|
||||
// die beiden Waagen
|
||||
HX711 scale1;
|
||||
HX711 scale2;
|
||||
|
||||
char charVal[MAX_CHARS];
|
||||
|
||||
// LORA
|
||||
// LoRaWAN NwkSKey, network session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
//static const PROGMEM u1_t NWKSKEY[16] = { 0x5F, 0x33, 0x20, 0x8C, 0x13, 0x3A, 0x39, 0x3F, 0xA5, 0x6F, 0xE1, 0xC7, 0x9B, 0x78, 0x2B, 0xDF };
|
||||
|
||||
// LoRaWAN AppSKey, application session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
//static const u1_t PROGMEM APPSKEY[16] = { 0x17, 0x57, 0x92, 0x91, 0x43, 0x9C, 0xC3, 0xFC, 0x34, 0x50, 0xCD, 0x64, 0x14, 0xC4, 0xC8, 0xAB };
|
||||
|
||||
// LoRaWAN end-device address (DevAddr)
|
||||
//static const u4_t DEVADDR = 0x260112C8 ; // <-- Change this address for every node!
|
||||
|
||||
// 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) { }
|
||||
void os_getDevEui (u1_t* buf) { }
|
||||
void os_getDevKey (u1_t* buf) { }
|
||||
|
||||
static uint8_t mydata[] = "Hello, world!";
|
||||
static osjob_t sendjob;
|
||||
|
||||
// Schedule TX every this many seconds (might become longer due to duty
|
||||
// cycle limitations).
|
||||
const unsigned TX_INTERVAL = 60;
|
||||
|
||||
// Pin mapping
|
||||
const lmic_pinmap lmic_pins = {
|
||||
.nss = 8,
|
||||
.rxtx = LMIC_UNUSED_PIN,
|
||||
.rst = 4,
|
||||
.dio = {7, 6, LMIC_UNUSED_PIN},
|
||||
};
|
||||
|
||||
void onEvent (ev_t ev) {
|
||||
Serial.print(os_getTime());
|
||||
Serial.print(": ");
|
||||
switch (ev) {
|
||||
case EV_SCAN_TIMEOUT:
|
||||
Serial.println(F("EV_SCAN_TIMEOUT"));
|
||||
break;
|
||||
case EV_BEACON_FOUND:
|
||||
Serial.println(F("EV_BEACON_FOUND"));
|
||||
break;
|
||||
case EV_BEACON_MISSED:
|
||||
Serial.println(F("EV_BEACON_MISSED"));
|
||||
break;
|
||||
case EV_BEACON_TRACKED:
|
||||
Serial.println(F("EV_BEACON_TRACKED"));
|
||||
break;
|
||||
case EV_JOINING:
|
||||
Serial.println(F("EV_JOINING"));
|
||||
break;
|
||||
case EV_JOINED:
|
||||
Serial.println(F("EV_JOINED"));
|
||||
break;
|
||||
case EV_RFU1:
|
||||
Serial.println(F("EV_RFU1"));
|
||||
break;
|
||||
case EV_JOIN_FAILED:
|
||||
Serial.println(F("EV_JOIN_FAILED"));
|
||||
break;
|
||||
case EV_REJOIN_FAILED:
|
||||
Serial.println(F("EV_REJOIN_FAILED"));
|
||||
break;
|
||||
case EV_TXCOMPLETE:
|
||||
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
|
||||
if (LMIC.txrxFlags & TXRX_ACK)
|
||||
Serial.println(F("Received ack"));
|
||||
if (LMIC.dataLen) {
|
||||
Serial.println(F("Received "));
|
||||
Serial.println(LMIC.dataLen);
|
||||
Serial.println(F(" bytes of payload"));
|
||||
}
|
||||
// Schedule next transmission
|
||||
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
|
||||
break;
|
||||
case EV_LOST_TSYNC:
|
||||
Serial.println(F("EV_LOST_TSYNC"));
|
||||
break;
|
||||
case EV_RESET:
|
||||
Serial.println(F("EV_RESET"));
|
||||
break;
|
||||
case EV_RXCOMPLETE:
|
||||
// data received in ping slot
|
||||
Serial.println(F("EV_RXCOMPLETE"));
|
||||
break;
|
||||
case EV_LINK_DEAD:
|
||||
Serial.println(F("EV_LINK_DEAD"));
|
||||
break;
|
||||
case EV_LINK_ALIVE:
|
||||
Serial.println(F("EV_LINK_ALIVE"));
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown event"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void do_send(osjob_t* j) {
|
||||
// Check if there is not a current TX/RX job running
|
||||
if (LMIC.opmode & OP_TXRXPEND) {
|
||||
Serial.println(F("OP_TXRXPEND, not sending"));
|
||||
} else {
|
||||
// Prepare upstream data transmission at the next possible time.
|
||||
//#define VBATPIN A7
|
||||
// float measuredvbat = analogRead(VBATPIN);
|
||||
// measuredvbat *= 2; // we divided by 2, so multiply back
|
||||
// measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
|
||||
// measuredvbat /= 1024; // convert to voltage
|
||||
// char buffer[8];
|
||||
|
||||
// dtostrf(measuredvbat, 1, 2, buffer);
|
||||
//String res = buffer;
|
||||
//res.getBytes(buffer, res.length() + 1);
|
||||
// Serial.print("VBat: " ); Serial.println(measuredvbat);
|
||||
LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(lora_data), 0);
|
||||
Serial.println("Lora Packet:");
|
||||
Serial.println(lora_data.version);
|
||||
Serial.println(lora_data.weight[0]);
|
||||
Serial.println(lora_data.weight[1]);
|
||||
Serial.println(lora_data.weight[2]);
|
||||
Serial.println(lora_data.weight[3]);
|
||||
Serial.println(lora_data.weight[4]);
|
||||
Serial.println(lora_data.temperature[0]);
|
||||
Serial.println(lora_data.temperature[1]);
|
||||
Serial.println(lora_data.temperature[2]);
|
||||
Serial.println(lora_data.temperature[3]);
|
||||
Serial.println(lora_data.temperature[4]);
|
||||
Serial.println(lora_data.vbat);
|
||||
|
||||
Serial.println(F("Packet queued"));
|
||||
}
|
||||
// Next TX is scheduled after TX_COMPLETE event.
|
||||
}
|
||||
|
||||
// END LORA
|
||||
|
||||
|
||||
|
||||
// Error-Fuction
|
||||
void error(const __FlashStringHelper*err) {
|
||||
#if DEBUG
|
||||
Serial.println(err);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Print Function
|
||||
void print_debug(const char *InputString) {
|
||||
#if DEBUG
|
||||
//Serial.println(millis());
|
||||
Serial.println(InputString);
|
||||
#endif
|
||||
}
|
||||
|
||||
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, int which) {
|
||||
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 (which == APPSKEY) {
|
||||
memcpy(&fram_data.appskey, &cmd, 16);
|
||||
} else if (which == NWKSKEY) {
|
||||
memcpy(&fram_data.nwkskey, &cmd, 16);
|
||||
} else if (which == DEVADDR) {
|
||||
memcpy(&fram_data.devaddr, &cmd, 4);
|
||||
} else {
|
||||
print_debug("Invalid which");
|
||||
}
|
||||
Save2FRAM();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
String 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);
|
||||
}
|
||||
}
|
||||
|
||||
float GetWeight() {
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
long raw_weight2 = scale2.read_average(3);
|
||||
return (raw_weight1 + raw_weight2);
|
||||
}
|
||||
|
||||
float GetTemp() {
|
||||
return sensor.readTemperature();
|
||||
}
|
||||
|
||||
float 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;
|
||||
}
|
||||
|
||||
void Setup() {
|
||||
Serial.println("Entering setup mode...");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
while (s != "exit") {
|
||||
|
||||
if (s == "") {
|
||||
//Serial.println("Leerzeile wird ignoriert...");
|
||||
}
|
||||
else if (s.startsWith("setnwkskey")) {
|
||||
String key;
|
||||
key = s.substring(11);
|
||||
if (isValidHexKey(key, 32)) {
|
||||
WriteKey(key, NWKSKEY);
|
||||
}
|
||||
else {
|
||||
Serial.println("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);
|
||||
}
|
||||
else {
|
||||
Serial.println("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);
|
||||
}
|
||||
else {
|
||||
Serial.println("Ist kein gueltiger Hex Key mit 8 Zeichen");
|
||||
}
|
||||
}
|
||||
else if (s == "getkeys") {
|
||||
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\": \"");
|
||||
u1_t barr[4];
|
||||
memcpy(&barr, &fram_data.devaddr, 4);
|
||||
print_byte_array(barr, 4);
|
||||
Serial.println("\"");
|
||||
Serial.println("}");
|
||||
}
|
||||
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 == "initvalues") {
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i]=INT_MAX;
|
||||
fram_data.temperature[i]=INT_MAX;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
}
|
||||
else {
|
||||
Serial.println("You sent me an unknown command (\"exit\" to quit): \"" + s + "\"");
|
||||
}
|
||||
s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
}
|
||||
}
|
||||
|
||||
void Save2FRAM()
|
||||
{
|
||||
fram.writeEnable(true);
|
||||
fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) );
|
||||
fram.writeEnable(false);
|
||||
}
|
||||
|
||||
void SendLoraData()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Alles wird im Setup erledigt...
|
||||
void setup(void)
|
||||
{
|
||||
// DONEPIN must be low...
|
||||
pinMode(DONEPIN, OUTPUT);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
|
||||
// Reading battery; Value is in Millivolts
|
||||
float 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_power_only = (vbat >= 4400);
|
||||
|
||||
if (usb_power_only) {
|
||||
// Initialize serial and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(6000);
|
||||
while (!Serial);
|
||||
print_debug("Serial Port initialized");
|
||||
}
|
||||
// fuer Debugzwecke
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
if (usb_power_only) {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
} else {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
// FRAM
|
||||
// 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()) {
|
||||
print_debug("Found SPI FRAM");
|
||||
} else {
|
||||
print_debug("No SPI FRAM found ... check your connections\r\n");
|
||||
while (1);
|
||||
}
|
||||
|
||||
// wir lesen die FRAM-Werte
|
||||
fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data));
|
||||
|
||||
// END FRAM
|
||||
|
||||
// zuerst wird der HX711 initialisiert
|
||||
print_debug("Initializing the scale");
|
||||
scale1.begin(A3, A2);
|
||||
scale2.begin(A1, A0);
|
||||
|
||||
// Jetzt initialisieren wir den Si7021
|
||||
if (!sensor.begin()) {
|
||||
print_debug("Did not find Si7021 sensor!");
|
||||
}
|
||||
|
||||
boolean success;
|
||||
|
||||
float h = sensor.readHumidity();
|
||||
// Read temperature as Celsius (the default)
|
||||
float t = GetTemp();
|
||||
|
||||
// Check if any reads failed and exit early (to try again).
|
||||
if (isnan(h) || isnan(t)) {
|
||||
print_debug("Failed to read from temperature/humidity sensor!");
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
print_debug("Updating temperature value to ");
|
||||
//4 is mininum width, 3 is precision; float value is copied onto buff
|
||||
dtostrf(t, 4, 3, charVal);
|
||||
print_debug(charVal);
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
print_debug("Updating humidity value to ");
|
||||
//4 is mininum width, 3 is precision; float value is copied onto buff
|
||||
dtostrf(h, 4, 3, charVal);
|
||||
print_debug(charVal);
|
||||
#endif
|
||||
|
||||
#if DEBUG
|
||||
print_debug("Updating battery value to ");
|
||||
//4 is minimum width, 3 is precision; float value is copied onto buff
|
||||
dtostrf(vbat, 4, 3, charVal);
|
||||
print_debug(charVal);
|
||||
#endif
|
||||
|
||||
|
||||
fram_data.weight[fram_data.my_position] = GetWeight();
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
// Wenn die Differenz des Gewichts zu gross ist oder alle Messplaetze belegt sind senden wir das Paket...
|
||||
bool send_lora_data = false;
|
||||
Serial.print("AAA");
|
||||
Serial.print(send_lora_data);
|
||||
Serial.print("BBB");
|
||||
Serial.print(fram_data.my_position);
|
||||
if (fram_data.my_position > 0) {
|
||||
if ((fram_data.weight[fram_data.my_position] - fram_data.weight[fram_data.my_position - 1]) >= 99999) {
|
||||
Serial.print("CCC");
|
||||
Serial.print(fram_data.my_position);
|
||||
send_lora_data = true;
|
||||
}
|
||||
}
|
||||
fram_data.my_position++;
|
||||
|
||||
if (fram_data.my_position == MAX_VALUES_TO_SEND) {
|
||||
send_lora_data = true;
|
||||
}
|
||||
|
||||
|
||||
//print_debug("Setting Advertising Data to wwwwwwwwWWWWWWWWTTTTHHHBBB");
|
||||
//print_debug("wwwwwwww: raw1, WWWWWWWW: raw2, TTTT: Temperatur in 1/10 Grad, HHH: Humidity in Promille, BBB Batteriespannung in 1/100 Volt");
|
||||
//char command[MAX_CHARS] = "AT+GAPSETADVDATA=";
|
||||
|
||||
//char rawstring[MAX_CHARS] = "";
|
||||
|
||||
//sprintf(rawstring, "11FF1234%08lX%08lX%04X%04X%04X", long(LONG_OFFSET + raw_weight1), long(LONG_OFFSET + raw_weight2), int(t * 10 + INT_OFFSET), int(h * 10), int(vbat / 10));
|
||||
|
||||
//char data[MAX_CHARS];
|
||||
//int i = 0;
|
||||
//char* c = rawstring;
|
||||
//while (*c) {
|
||||
// data[i++] = *c++;
|
||||
// data[i++] = *c++;
|
||||
// if (*c) {
|
||||
// data[i++] = '-';
|
||||
// }
|
||||
//}
|
||||
//data[i] = 0;
|
||||
|
||||
//strncat(command, data, MAX_CHARS - 1);
|
||||
|
||||
|
||||
Serial.print("send_lora_data:");
|
||||
Serial.print(send_lora_data);
|
||||
if (send_lora_data) {
|
||||
Serial.println("SendLoraPacket");
|
||||
lora_data.version = 1;
|
||||
memcpy(&lora_data.weight, &fram_data.weight, 10);
|
||||
memcpy(&lora_data.temperature, &lora_data.temperature, 10);
|
||||
lora_data.vbat = (byte)(vbat / 20);
|
||||
|
||||
|
||||
// LORA
|
||||
#ifdef VCC_ENABLE
|
||||
// For Pinoccio Scout boards
|
||||
pinMode(VCC_ENABLE, OUTPUT);
|
||||
digitalWrite(VCC_ENABLE, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
// LMIC init
|
||||
os_init();
|
||||
// Reset the MAC state. Session and pending data transfers will be discarded.
|
||||
LMIC_reset();
|
||||
|
||||
// Set static session parameters. Instead of dynamically establishing a session
|
||||
// by joining the network, precomputed session parameters are be provided.
|
||||
LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
||||
|
||||
#if defined(CFG_eu868)
|
||||
// Set up the channels used by the Things Network, which corresponds
|
||||
// to the defaults of most gateways. Without this, only three base
|
||||
// channels from the LoRaWAN specification are used, which certainly
|
||||
// works, so it is good for debugging, but can overload those
|
||||
// frequencies, so be sure to configure the full frequency range of
|
||||
// your network here (unless your network autoconfigures them).
|
||||
// Setting up channels should happen after LMIC_setSession, as that
|
||||
// configures the minimal channel set.
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
|
||||
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
|
||||
// devices' ping slots. LMIC does not have an easy way to define set this
|
||||
// frequency and support for class B is spotty and untested, so this
|
||||
// frequency is not configured here.
|
||||
#elif defined(CFG_us915)
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
// but only one group of 8 should (a subband) should be active
|
||||
// TTN recommends the second sub band, 1 in a zero based count.
|
||||
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
|
||||
LMIC_selectSubBand(1);
|
||||
#endif
|
||||
|
||||
// Disable link check validation
|
||||
LMIC_setLinkCheckMode(0);
|
||||
|
||||
// TTN uses SF9 for its RX2 window.
|
||||
LMIC.dn2Dr = DR_SF9;
|
||||
|
||||
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
|
||||
LMIC_setDrTxpow(DR_SF7, 14);
|
||||
|
||||
// Start job
|
||||
do_send(&sendjob);
|
||||
|
||||
// END LORA
|
||||
// Jetzt koennen wir die FRAM-Werte wieder initialisieren
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i] = INT_MAX;
|
||||
fram_data.temperature[i] = INT_MAX;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
|
||||
}
|
||||
delay(2000);
|
||||
// soll evtl. das Setup durchgefuehrt werden?
|
||||
if (usb_power_only && Serial.available()) {
|
||||
String s = Serial.readStringUntil('\n');
|
||||
if (s == "setup") {
|
||||
Setup();
|
||||
} else {
|
||||
Serial.println("Unknown command (only setup is allowed here): " + s);
|
||||
}
|
||||
}
|
||||
|
||||
// dump the entire 8K of memory!
|
||||
if (usb_power_only) {
|
||||
uint8_t value;
|
||||
for (uint16_t a = 0; a < sizeof(FRAM_data); a++) {
|
||||
value = fram.read8(a);
|
||||
if ((a % 32) == 0) {
|
||||
Serial.print("\n 0x"); Serial.print(a, HEX); Serial.print(": ");
|
||||
}
|
||||
Serial.print("0x");
|
||||
if (value < 0x1)
|
||||
Serial.print('0');
|
||||
Serial.print(value, HEX); Serial.print(" ");
|
||||
}
|
||||
Serial.print("\nsizeof(FRAM_data): " + String(sizeof(FRAM_data)));
|
||||
}
|
||||
|
||||
// Jetzt sichern wir die Werte
|
||||
Save2FRAM();
|
||||
|
||||
Serial.println("FRAM Values:");
|
||||
Serial.println(fram_data.my_position);
|
||||
Serial.println(fram_data.weight[0]);
|
||||
Serial.println(fram_data.weight[1]);
|
||||
Serial.println(fram_data.weight[2]);
|
||||
Serial.println(fram_data.weight[3]);
|
||||
Serial.println(fram_data.weight[4]);
|
||||
Serial.println(fram_data.temperature[0]);
|
||||
Serial.println(fram_data.temperature[1]);
|
||||
Serial.println(fram_data.temperature[2]);
|
||||
Serial.println(fram_data.temperature[3]);
|
||||
Serial.println(fram_data.temperature[4]);
|
||||
|
||||
|
||||
/* Jetzt signalisieren wir, dass wir fertig sind... */
|
||||
print_debug("Jetzt senden wir das DONE Signal...");
|
||||
|
||||
|
||||
// while (1) {
|
||||
// print_debug("ENDLOS LOOP AM ENDE DES SETUPS");
|
||||
// delay(500);
|
||||
// }
|
||||
|
||||
#if DEBUG
|
||||
delay(10000);
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
// Hier haben wir nichts zu tun, wir machen alles im Setup...
|
||||
}
|
||||
|
|
@ -0,0 +1,697 @@
|
|||
/*********************************************************************
|
||||
BeieliScale by nbit Informatik GmbH
|
||||
|
||||
*********************************************************************/
|
||||
#include <lmic.h>
|
||||
#include <hal/hal.h>
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <avr/dtostrf.h>
|
||||
#include "HX711.h"
|
||||
#include "Adafruit_Si7021.h"
|
||||
#include "Adafruit_FRAM_SPI.h"
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
|
||||
// Required for Serial on Zero based boards
|
||||
#define Serial SERIAL_PORT_USBVIRTUAL
|
||||
#endif
|
||||
|
||||
#define MAX_VALUES_TO_SEND 5
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} LORA_data;
|
||||
|
||||
LORA_data lora_data;
|
||||
|
||||
typedef struct {
|
||||
u1_t nwkskey[16];
|
||||
u1_t appskey[16];
|
||||
u4_t devaddr;
|
||||
u1_t framecounter;
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
} FRAM_data;
|
||||
|
||||
FRAM_data fram_data;
|
||||
bool usb_power_only;
|
||||
|
||||
// Im DEBUG-Modus werden Meldungen auf der seriellen Schnittstelle ausgegeben
|
||||
#define DEBUG 1
|
||||
|
||||
#define DONEPIN A5
|
||||
#define VBATPIN A7
|
||||
#define LONG_OFFSET 2147483648L
|
||||
#define MAX_CHARS 80
|
||||
#define MAX_SHORT 32767
|
||||
|
||||
#define NWKSKEY 1
|
||||
#define APPSKEY 2
|
||||
#define DEVADDR 3
|
||||
|
||||
uint8_t FRAM_CS = 10;
|
||||
|
||||
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI
|
||||
|
||||
// Temperatursensor
|
||||
Adafruit_Si7021 sensor = Adafruit_Si7021();
|
||||
|
||||
// die beiden Waagen
|
||||
HX711 scale1;
|
||||
HX711 scale2;
|
||||
|
||||
char charVal[MAX_CHARS];
|
||||
|
||||
// LORA
|
||||
// LoRaWAN NwkSKey, network session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
//static const PROGMEM u1_t NWKSKEY[16] = { 0x5F, 0x33, 0x20, 0x8C, 0x13, 0x3A, 0x39, 0x3F, 0xA5, 0x6F, 0xE1, 0xC7, 0x9B, 0x78, 0x2B, 0xDF };
|
||||
|
||||
// LoRaWAN AppSKey, application session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
//static const u1_t PROGMEM APPSKEY[16] = { 0x17, 0x57, 0x92, 0x91, 0x43, 0x9C, 0xC3, 0xFC, 0x34, 0x50, 0xCD, 0x64, 0x14, 0xC4, 0xC8, 0xAB };
|
||||
|
||||
// LoRaWAN end-device address (DevAddr)
|
||||
//static const u4_t DEVADDR = 0x260112C8 ; // <-- Change this address for every node!
|
||||
|
||||
// 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) { }
|
||||
void os_getDevEui (u1_t* buf) { }
|
||||
void os_getDevKey (u1_t* buf) { }
|
||||
|
||||
static uint8_t mydata[] = "Hello, world!";
|
||||
static osjob_t sendjob;
|
||||
|
||||
// Schedule TX every this many seconds (might become longer due to duty
|
||||
// cycle limitations).
|
||||
const unsigned TX_INTERVAL = 60;
|
||||
|
||||
// Pin mapping
|
||||
const lmic_pinmap lmic_pins = {
|
||||
.nss = 8,
|
||||
.rxtx = LMIC_UNUSED_PIN,
|
||||
.rst = 4,
|
||||
.dio = {3, 6, LMIC_UNUSED_PIN},
|
||||
};
|
||||
|
||||
void onEvent (ev_t ev) {
|
||||
Serial.print(os_getTime());
|
||||
Serial.print(": ");
|
||||
switch (ev) {
|
||||
case EV_SCAN_TIMEOUT:
|
||||
Serial.println(F("EV_SCAN_TIMEOUT"));
|
||||
break;
|
||||
case EV_BEACON_FOUND:
|
||||
Serial.println(F("EV_BEACON_FOUND"));
|
||||
break;
|
||||
case EV_BEACON_MISSED:
|
||||
Serial.println(F("EV_BEACON_MISSED"));
|
||||
break;
|
||||
case EV_BEACON_TRACKED:
|
||||
Serial.println(F("EV_BEACON_TRACKED"));
|
||||
break;
|
||||
case EV_JOINING:
|
||||
Serial.println(F("EV_JOINING"));
|
||||
break;
|
||||
case EV_JOINED:
|
||||
Serial.println(F("EV_JOINED"));
|
||||
break;
|
||||
case EV_RFU1:
|
||||
Serial.println(F("EV_RFU1"));
|
||||
break;
|
||||
case EV_JOIN_FAILED:
|
||||
Serial.println(F("EV_JOIN_FAILED"));
|
||||
break;
|
||||
case EV_REJOIN_FAILED:
|
||||
Serial.println(F("EV_REJOIN_FAILED"));
|
||||
break;
|
||||
case EV_TXCOMPLETE:
|
||||
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
|
||||
if (LMIC.txrxFlags & TXRX_ACK)
|
||||
Serial.println(F("Received ack"));
|
||||
if (LMIC.dataLen) {
|
||||
Serial.println(F("Received "));
|
||||
Serial.println(LMIC.dataLen);
|
||||
Serial.println(F(" bytes of payload"));
|
||||
}
|
||||
// Schedule next transmission
|
||||
os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
|
||||
break;
|
||||
case EV_LOST_TSYNC:
|
||||
Serial.println(F("EV_LOST_TSYNC"));
|
||||
break;
|
||||
case EV_RESET:
|
||||
Serial.println(F("EV_RESET"));
|
||||
break;
|
||||
case EV_RXCOMPLETE:
|
||||
// data received in ping slot
|
||||
Serial.println(F("EV_RXCOMPLETE"));
|
||||
break;
|
||||
case EV_LINK_DEAD:
|
||||
Serial.println(F("EV_LINK_DEAD"));
|
||||
break;
|
||||
case EV_LINK_ALIVE:
|
||||
Serial.println(F("EV_LINK_ALIVE"));
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown event"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void do_send(osjob_t* j) {
|
||||
// Check if there is not a current TX/RX job running
|
||||
if (LMIC.opmode & OP_TXRXPEND) {
|
||||
Serial.println(F("OP_TXRXPEND, not sending"));
|
||||
} else {
|
||||
// Prepare upstream data transmission at the next possible time.
|
||||
//#define VBATPIN A7
|
||||
// float measuredvbat = analogRead(VBATPIN);
|
||||
// measuredvbat *= 2; // we divided by 2, so multiply back
|
||||
// measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
|
||||
// measuredvbat /= 1024; // convert to voltage
|
||||
// char buffer[8];
|
||||
|
||||
// dtostrf(measuredvbat, 1, 2, buffer);
|
||||
//String res = buffer;
|
||||
//res.getBytes(buffer, res.length() + 1);
|
||||
// Serial.print("VBat: " ); Serial.println(measuredvbat);
|
||||
//LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(lora_data), 0);
|
||||
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
|
||||
ShowLoraData();
|
||||
|
||||
Serial.println(F("Packet queued"));
|
||||
}
|
||||
// Next TX is scheduled after TX_COMPLETE event.
|
||||
}
|
||||
|
||||
// END LORA
|
||||
|
||||
|
||||
|
||||
// Error-Fuction
|
||||
void error(const __FlashStringHelper*err) {
|
||||
#if DEBUG
|
||||
Serial.println(err);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Print Function
|
||||
void print_debug(const char *InputString) {
|
||||
#if DEBUG
|
||||
//Serial.println(millis());
|
||||
Serial.println(InputString);
|
||||
#endif
|
||||
}
|
||||
|
||||
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, int which) {
|
||||
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 (which == APPSKEY) {
|
||||
memcpy(&fram_data.appskey, &cmd, 16);
|
||||
} else if (which == NWKSKEY) {
|
||||
memcpy(&fram_data.nwkskey, &cmd, 16);
|
||||
} else if (which == DEVADDR) {
|
||||
memcpy(&fram_data.devaddr, &cmd, 4);
|
||||
} else {
|
||||
print_debug("Invalid which");
|
||||
}
|
||||
Save2FRAM();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
float GetTemp() {
|
||||
return sensor.readTemperature();
|
||||
}
|
||||
|
||||
float 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;
|
||||
}
|
||||
|
||||
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\": \"");
|
||||
u1_t barr[4];
|
||||
memcpy(&barr, &fram_data.devaddr, 4);
|
||||
print_byte_array(barr, 4);
|
||||
Serial.println("\",");
|
||||
Serial.print(" \"framecounter\": ");
|
||||
Serial.print(fram_data.framecounter);
|
||||
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(" \"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.println("}");
|
||||
}
|
||||
|
||||
void Setup() {
|
||||
Serial.println("{ \"msg\": \"Entering setup mode\" }");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
while (s != "exit") {
|
||||
|
||||
if (s == "") {
|
||||
//Serial.println("Leerzeile wird ignoriert...");
|
||||
}
|
||||
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 == "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") {
|
||||
Serial.println("getrawvalues...");
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
long raw_weight2 = scale2.read_average(3);
|
||||
Serial.println("getrawvalues after read scales..");
|
||||
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 == "initvalues") {
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i]=MAX_SHORT;
|
||||
fram_data.temperature[i]=MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
Serial.println("{ \"msg\": \"initvalues was successful\" }");
|
||||
}
|
||||
else if (s == "calibrate_zero_1") {
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
fram_data.cal_w1_0 = 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 = 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);
|
||||
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 = (w2_gramm.toFloat() / (float)raw_weight2);
|
||||
Serial.println("{ \"msg\": \"calibrate_2 was successful\" }");
|
||||
}
|
||||
else {
|
||||
Serial.println("{ \"msg\": \"You sent me an unknown command (exit to quit setup mode)\" }");
|
||||
}
|
||||
s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
}
|
||||
}
|
||||
|
||||
void Save2FRAM()
|
||||
{
|
||||
fram.writeEnable(true);
|
||||
fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) );
|
||||
fram.writeEnable(false);
|
||||
}
|
||||
|
||||
void ShowLoraData()
|
||||
{
|
||||
Serial.println("Lora Packet:");
|
||||
Serial.println(lora_data.version);
|
||||
Serial.println(lora_data.weight[0]);
|
||||
Serial.println(lora_data.weight[1]);
|
||||
Serial.println(lora_data.weight[2]);
|
||||
Serial.println(lora_data.weight[3]);
|
||||
Serial.println(lora_data.weight[4]);
|
||||
Serial.println(lora_data.temperature[0]);
|
||||
Serial.println(lora_data.temperature[1]);
|
||||
Serial.println(lora_data.temperature[2]);
|
||||
Serial.println(lora_data.temperature[3]);
|
||||
Serial.println(lora_data.temperature[4]);
|
||||
Serial.println(lora_data.vbat);
|
||||
Serial.print("Size of Lora Package: ");
|
||||
Serial.println(sizeof(LORA_data));
|
||||
}
|
||||
|
||||
// Alles wird im Setup erledigt...
|
||||
void setup(void)
|
||||
{
|
||||
// DONEPIN must be low...
|
||||
pinMode(DONEPIN, OUTPUT);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
|
||||
// Reading battery; Value is in Millivolts
|
||||
float 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_power_only = (vbat >= 4400);
|
||||
|
||||
if (usb_power_only) {
|
||||
// Initialize serial and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(6000);
|
||||
while (!Serial);
|
||||
print_debug("Serial Port initialized");
|
||||
}
|
||||
// fuer Debugzwecke
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
if (usb_power_only) {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
} else {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
// FRAM
|
||||
// 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()) {
|
||||
print_debug("Found SPI FRAM");
|
||||
} else {
|
||||
print_debug("No SPI FRAM found ... check your connections\r\n");
|
||||
while (1);
|
||||
}
|
||||
|
||||
// wir lesen die FRAM-Werte
|
||||
fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data));
|
||||
|
||||
//ShowFRAMData();
|
||||
// END FRAM
|
||||
|
||||
// zuerst wird der HX711 initialisiert
|
||||
print_debug("Initializing the scale");
|
||||
scale1.begin(A3, A2);
|
||||
scale2.begin(A1, A0);
|
||||
|
||||
// Jetzt initialisieren wir den Si7021
|
||||
if (!sensor.begin()) {
|
||||
print_debug("Did not find Si7021 sensor!");
|
||||
}
|
||||
|
||||
boolean success;
|
||||
|
||||
fram_data.weight[fram_data.my_position] = GetWeight();
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
// Wenn die Differenz des Gewichts zu gross ist oder alle Messplaetze belegt sind senden wir das Paket...
|
||||
bool send_lora_data = false;
|
||||
if (fram_data.my_position > 0) {
|
||||
if ((fram_data.weight[fram_data.my_position] - fram_data.weight[fram_data.my_position - 1]) >= 9999999) {
|
||||
Serial.print("CCC");
|
||||
Serial.print(fram_data.my_position);
|
||||
send_lora_data = true;
|
||||
}
|
||||
}
|
||||
fram_data.my_position++;
|
||||
|
||||
if (fram_data.my_position == MAX_VALUES_TO_SEND) {
|
||||
send_lora_data = true;
|
||||
}
|
||||
|
||||
Serial.print("send_lora_data:");
|
||||
Serial.print(send_lora_data);
|
||||
if (send_lora_data) {
|
||||
Serial.println("SendLoraPacket");
|
||||
lora_data.version = 1;
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
lora_data.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
lora_data.vbat = (byte)(vbat / 20);
|
||||
|
||||
|
||||
// LORA
|
||||
#ifdef VCC_ENABLE
|
||||
// For Pinoccio Scout boards
|
||||
pinMode(VCC_ENABLE, OUTPUT);
|
||||
digitalWrite(VCC_ENABLE, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
// LMIC init
|
||||
os_init();
|
||||
// Reset the MAC state. Session and pending data transfers will be discarded.
|
||||
LMIC_reset();
|
||||
|
||||
// Set static session parameters. Instead of dynamically establishing a session
|
||||
// by joining the network, precomputed session parameters are be provided.
|
||||
|
||||
LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
||||
|
||||
#if defined(CFG_eu868)
|
||||
// Set up the channels used by the Things Network, which corresponds
|
||||
// to the defaults of most gateways. Without this, only three base
|
||||
// channels from the LoRaWAN specification are used, which certainly
|
||||
// works, so it is good for debugging, but can overload those
|
||||
// frequencies, so be sure to configure the full frequency range of
|
||||
// your network here (unless your network autoconfigures them).
|
||||
// Setting up channels should happen after LMIC_setSession, as that
|
||||
// configures the minimal channel set.
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
|
||||
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
|
||||
// devices' ping slots. LMIC does not have an easy way to define set this
|
||||
// frequency and support for class B is spotty and untested, so this
|
||||
// frequency is not configured here.
|
||||
#elif defined(CFG_us915)
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
// but only one group of 8 should (a subband) should be active
|
||||
// TTN recommends the second sub band, 1 in a zero based count.
|
||||
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
|
||||
LMIC_selectSubBand(1);
|
||||
#endif
|
||||
|
||||
// Disable link check validation
|
||||
LMIC_setLinkCheckMode(0);
|
||||
|
||||
// TTN uses SF9 for its RX2 window.
|
||||
LMIC.dn2Dr = DR_SF9;
|
||||
|
||||
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
|
||||
LMIC_setDrTxpow(DR_SF7, 14);
|
||||
|
||||
// Start job
|
||||
do_send(&sendjob);
|
||||
|
||||
// END LORA
|
||||
// Jetzt koennen wir die FRAM-Werte wieder initialisieren
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
|
||||
}
|
||||
delay(2000);
|
||||
Serial.println("BLABLA");
|
||||
// soll evtl. das Setup durchgefuehrt werden?
|
||||
if (usb_power_only && Serial.available()) {
|
||||
Serial.println("GGGGBLABLA");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
if (s == "setup") {
|
||||
Serial.println("SETUPGGGGBLABLA");
|
||||
Setup();
|
||||
} else {
|
||||
Serial.println("{ \"msg\": \"Unknown command (only setup is allowed here)\"");
|
||||
}
|
||||
}
|
||||
|
||||
// dump the entire 8K of memory!
|
||||
if (usb_power_only) {
|
||||
uint8_t value;
|
||||
for (uint16_t a = 0; a < sizeof(FRAM_data); a++) {
|
||||
value = fram.read8(a);
|
||||
if ((a % 32) == 0) {
|
||||
Serial.print("\n 0x"); Serial.print(a, HEX); Serial.print(": ");
|
||||
}
|
||||
Serial.print("0x");
|
||||
if (value < 0x1)
|
||||
Serial.print('0');
|
||||
Serial.print(value, HEX); Serial.print(" ");
|
||||
}
|
||||
Serial.print("\nsizeof(FRAM_data): " + String(sizeof(FRAM_data)));
|
||||
}
|
||||
|
||||
// Jetzt sichern wir die Werte
|
||||
Save2FRAM();
|
||||
|
||||
//ShowFRAMData();
|
||||
|
||||
/* Jetzt signalisieren wir, dass wir fertig sind... */
|
||||
delay(5000);
|
||||
print_debug("Jetzt senden wir das DONE Signal...");
|
||||
|
||||
|
||||
// while (1) {
|
||||
// print_debug("ENDLOS LOOP AM ENDE DES SETUPS");
|
||||
// delay(500);
|
||||
// }
|
||||
|
||||
#if DEBUG
|
||||
delay(3000);
|
||||
#endif
|
||||
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
// Hier haben wir nichts zu tun, wir machen alles im Setup...
|
||||
}
|
||||
|
|
@ -0,0 +1,698 @@
|
|||
/*********************************************************************
|
||||
BeieliScale by nbit Informatik GmbH
|
||||
|
||||
*********************************************************************/
|
||||
#include <lmic.h>
|
||||
#include <hal/hal.h>
|
||||
#include <Arduino.h>
|
||||
#include <SPI.h>
|
||||
#include <avr/dtostrf.h>
|
||||
#include "HX711.h"
|
||||
#include "Adafruit_Si7021.h"
|
||||
#include "Adafruit_FRAM_SPI.h"
|
||||
|
||||
#if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL)
|
||||
// Required for Serial on Zero based boards
|
||||
#define Serial SERIAL_PORT_USBVIRTUAL
|
||||
#endif
|
||||
|
||||
#define MAX_VALUES_TO_SEND 5
|
||||
|
||||
bool send_lora_data = false;
|
||||
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} LORA_data;
|
||||
|
||||
LORA_data lora_data;
|
||||
|
||||
typedef struct {
|
||||
u1_t nwkskey[16];
|
||||
u1_t appskey[16];
|
||||
u4_t devaddr;
|
||||
u1_t framecounter;
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
} FRAM_data;
|
||||
|
||||
FRAM_data fram_data;
|
||||
bool usb_power_only;
|
||||
|
||||
// Im DEBUG-Modus werden Meldungen auf der seriellen Schnittstelle ausgegeben
|
||||
#define DEBUG 1
|
||||
|
||||
#define DONEPIN A5
|
||||
#define VBATPIN A7
|
||||
#define LONG_OFFSET 2147483648L
|
||||
#define MAX_CHARS 80
|
||||
#define MAX_SHORT 32767
|
||||
|
||||
#define NWKSKEY 1
|
||||
#define APPSKEY 2
|
||||
#define DEVADDR 3
|
||||
|
||||
uint8_t FRAM_CS = 10;
|
||||
|
||||
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI
|
||||
|
||||
// Temperatursensor
|
||||
Adafruit_Si7021 sensor = Adafruit_Si7021();
|
||||
|
||||
// die beiden Waagen
|
||||
HX711 scale1;
|
||||
HX711 scale2;
|
||||
|
||||
char charVal[MAX_CHARS];
|
||||
|
||||
// LORA
|
||||
// LoRaWAN NwkSKey, network session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
//static const PROGMEM u1_t NWKSKEY[16] = { 0x5F, 0x33, 0x20, 0x8C, 0x13, 0x3A, 0x39, 0x3F, 0xA5, 0x6F, 0xE1, 0xC7, 0x9B, 0x78, 0x2B, 0xDF };
|
||||
|
||||
// LoRaWAN AppSKey, application session key
|
||||
// This is the default Semtech key, which is used by the early prototype TTN
|
||||
// network.
|
||||
//static const u1_t PROGMEM APPSKEY[16] = { 0x17, 0x57, 0x92, 0x91, 0x43, 0x9C, 0xC3, 0xFC, 0x34, 0x50, 0xCD, 0x64, 0x14, 0xC4, 0xC8, 0xAB };
|
||||
|
||||
// LoRaWAN end-device address (DevAddr)
|
||||
//static const u4_t DEVADDR = 0x260112C8 ; // <-- Change this address for every node!
|
||||
|
||||
// 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) { }
|
||||
void os_getDevEui (u1_t* buf) { }
|
||||
void os_getDevKey (u1_t* buf) { }
|
||||
|
||||
static uint8_t mydata[] = "Hello, world!";
|
||||
static osjob_t sendjob;
|
||||
|
||||
// Schedule TX every this many seconds (might become longer due to duty
|
||||
// cycle limitations).
|
||||
const unsigned TX_INTERVAL = 60;
|
||||
|
||||
// Pin mapping
|
||||
const lmic_pinmap lmic_pins = {
|
||||
.nss = 8,
|
||||
.rxtx = LMIC_UNUSED_PIN,
|
||||
.rst = 4,
|
||||
.dio = {3, 6, LMIC_UNUSED_PIN},
|
||||
};
|
||||
|
||||
void onEvent (ev_t ev) {
|
||||
Serial.print(os_getTime());
|
||||
Serial.print(": ");
|
||||
switch (ev) {
|
||||
case EV_SCAN_TIMEOUT:
|
||||
Serial.println(F("EV_SCAN_TIMEOUT"));
|
||||
break;
|
||||
case EV_BEACON_FOUND:
|
||||
Serial.println(F("EV_BEACON_FOUND"));
|
||||
break;
|
||||
case EV_BEACON_MISSED:
|
||||
Serial.println(F("EV_BEACON_MISSED"));
|
||||
break;
|
||||
case EV_BEACON_TRACKED:
|
||||
Serial.println(F("EV_BEACON_TRACKED"));
|
||||
break;
|
||||
case EV_JOINING:
|
||||
Serial.println(F("EV_JOINING"));
|
||||
break;
|
||||
case EV_JOINED:
|
||||
Serial.println(F("EV_JOINED"));
|
||||
break;
|
||||
case EV_RFU1:
|
||||
Serial.println(F("EV_RFU1"));
|
||||
break;
|
||||
case EV_JOIN_FAILED:
|
||||
Serial.println(F("EV_JOIN_FAILED"));
|
||||
break;
|
||||
case EV_REJOIN_FAILED:
|
||||
Serial.println(F("EV_REJOIN_FAILED"));
|
||||
break;
|
||||
case EV_TXCOMPLETE:
|
||||
Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
|
||||
if (LMIC.txrxFlags & TXRX_ACK)
|
||||
Serial.println(F("Received ack"));
|
||||
if (LMIC.dataLen) {
|
||||
Serial.println(F("Received "));
|
||||
Serial.println(LMIC.dataLen);
|
||||
Serial.println(F(" bytes of payload"));
|
||||
}
|
||||
Serial.println(F(" TXCOMPETE, wir schalten ab..."));
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
break;
|
||||
case EV_LOST_TSYNC:
|
||||
Serial.println(F("EV_LOST_TSYNC"));
|
||||
break;
|
||||
case EV_RESET:
|
||||
Serial.println(F("EV_RESET"));
|
||||
break;
|
||||
case EV_RXCOMPLETE:
|
||||
// data received in ping slot
|
||||
Serial.println(F("EV_RXCOMPLETE"));
|
||||
break;
|
||||
case EV_LINK_DEAD:
|
||||
Serial.println(F("EV_LINK_DEAD"));
|
||||
break;
|
||||
case EV_LINK_ALIVE:
|
||||
Serial.println(F("EV_LINK_ALIVE"));
|
||||
break;
|
||||
default:
|
||||
Serial.println(F("Unknown event"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void do_send(osjob_t* j) {
|
||||
// Check if there is not a current TX/RX job running
|
||||
if (LMIC.opmode & OP_TXRXPEND) {
|
||||
Serial.println(F("OP_TXRXPEND, not sending"));
|
||||
} else {
|
||||
// Prepare upstream data transmission at the next possible time.
|
||||
//#define VBATPIN A7
|
||||
// float measuredvbat = analogRead(VBATPIN);
|
||||
// measuredvbat *= 2; // we divided by 2, so multiply back
|
||||
// measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
|
||||
// measuredvbat /= 1024; // convert to voltage
|
||||
// char buffer[8];
|
||||
|
||||
// dtostrf(measuredvbat, 1, 2, buffer);
|
||||
//String res = buffer;
|
||||
//res.getBytes(buffer, res.length() + 1);
|
||||
// Serial.print("VBat: " ); Serial.println(measuredvbat);
|
||||
//LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(lora_data), 0);
|
||||
LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0);
|
||||
ShowLoraData();
|
||||
|
||||
Serial.println(F("Packet queued"));
|
||||
}
|
||||
// Next TX is scheduled after TX_COMPLETE event.
|
||||
}
|
||||
|
||||
// END LORA
|
||||
|
||||
|
||||
|
||||
// Error-Fuction
|
||||
void error(const __FlashStringHelper*err) {
|
||||
#if DEBUG
|
||||
Serial.println(err);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Print Function
|
||||
void print_debug(const char *InputString) {
|
||||
#if DEBUG
|
||||
//Serial.println(millis());
|
||||
Serial.println(InputString);
|
||||
#endif
|
||||
}
|
||||
|
||||
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, int which) {
|
||||
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 (which == APPSKEY) {
|
||||
memcpy(&fram_data.appskey, &cmd, 16);
|
||||
} else if (which == NWKSKEY) {
|
||||
memcpy(&fram_data.nwkskey, &cmd, 16);
|
||||
} else if (which == DEVADDR) {
|
||||
memcpy(&fram_data.devaddr, &cmd, 4);
|
||||
} else {
|
||||
print_debug("Invalid which");
|
||||
}
|
||||
Save2FRAM();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
float GetTemp() {
|
||||
return sensor.readTemperature();
|
||||
}
|
||||
|
||||
float 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;
|
||||
}
|
||||
|
||||
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\": \"");
|
||||
u1_t barr[4];
|
||||
memcpy(&barr, &fram_data.devaddr, 4);
|
||||
print_byte_array(barr, 4);
|
||||
Serial.println("\",");
|
||||
Serial.print(" \"framecounter\": ");
|
||||
Serial.print(fram_data.framecounter);
|
||||
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(" \"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.println("}");
|
||||
}
|
||||
|
||||
void Setup() {
|
||||
Serial.println("{ \"msg\": \"Entering setup mode\" }");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
while (s != "exit") {
|
||||
|
||||
if (s == "") {
|
||||
//Serial.println("Leerzeile wird ignoriert...");
|
||||
}
|
||||
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 == "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") {
|
||||
Serial.println("getrawvalues...");
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
long raw_weight2 = scale2.read_average(3);
|
||||
Serial.println("getrawvalues after read scales..");
|
||||
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 == "initvalues") {
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i]=MAX_SHORT;
|
||||
fram_data.temperature[i]=MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
Serial.println("{ \"msg\": \"initvalues was successful\" }");
|
||||
}
|
||||
else if (s == "calibrate_zero_1") {
|
||||
long raw_weight1 = scale1.read_average(3);
|
||||
fram_data.cal_w1_0 = 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 = 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);
|
||||
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 = (w2_gramm.toFloat() / (float)raw_weight2);
|
||||
Serial.println("{ \"msg\": \"calibrate_2 was successful\" }");
|
||||
}
|
||||
else {
|
||||
Serial.println("{ \"msg\": \"You sent me an unknown command (exit to quit setup mode)\" }");
|
||||
}
|
||||
s = Serial.readStringUntil('\n');
|
||||
s.trim();
|
||||
}
|
||||
}
|
||||
|
||||
void Save2FRAM()
|
||||
{
|
||||
fram.writeEnable(true);
|
||||
fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) );
|
||||
fram.writeEnable(false);
|
||||
}
|
||||
|
||||
void ShowLoraData()
|
||||
{
|
||||
Serial.println("Lora Packet:");
|
||||
Serial.println(lora_data.version);
|
||||
Serial.println(lora_data.weight[0]);
|
||||
Serial.println(lora_data.weight[1]);
|
||||
Serial.println(lora_data.weight[2]);
|
||||
Serial.println(lora_data.weight[3]);
|
||||
Serial.println(lora_data.weight[4]);
|
||||
Serial.println(lora_data.temperature[0]);
|
||||
Serial.println(lora_data.temperature[1]);
|
||||
Serial.println(lora_data.temperature[2]);
|
||||
Serial.println(lora_data.temperature[3]);
|
||||
Serial.println(lora_data.temperature[4]);
|
||||
Serial.println(lora_data.vbat);
|
||||
Serial.print("Size of Lora Package: ");
|
||||
Serial.println(sizeof(LORA_data));
|
||||
}
|
||||
|
||||
// Alles wird im Setup erledigt...
|
||||
void setup(void)
|
||||
{
|
||||
// DONEPIN must be low...
|
||||
pinMode(DONEPIN, OUTPUT);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
|
||||
// Reading battery; Value is in Millivolts
|
||||
float 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_power_only = (vbat >= 4400);
|
||||
|
||||
if (usb_power_only) {
|
||||
// Initialize serial and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
Serial.setTimeout(6000);
|
||||
while (!Serial);
|
||||
print_debug("Serial Port initialized");
|
||||
}
|
||||
// fuer Debugzwecke
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
if (usb_power_only) {
|
||||
digitalWrite(LED_BUILTIN, HIGH);
|
||||
} else {
|
||||
digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
// FRAM
|
||||
// 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()) {
|
||||
print_debug("Found SPI FRAM");
|
||||
} else {
|
||||
print_debug("No SPI FRAM found ... check your connections\r\n");
|
||||
while (1);
|
||||
}
|
||||
|
||||
// wir lesen die FRAM-Werte
|
||||
fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data));
|
||||
|
||||
//ShowFRAMData();
|
||||
// END FRAM
|
||||
|
||||
// zuerst wird der HX711 initialisiert
|
||||
print_debug("Initializing the scale");
|
||||
scale1.begin(A3, A2);
|
||||
scale2.begin(A1, A0);
|
||||
|
||||
// Jetzt initialisieren wir den Si7021
|
||||
if (!sensor.begin()) {
|
||||
print_debug("Did not find Si7021 sensor!");
|
||||
}
|
||||
|
||||
boolean success;
|
||||
|
||||
fram_data.weight[fram_data.my_position] = GetWeight();
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
// Wenn die Differenz des Gewichts zu gross ist oder alle Messplaetze belegt sind senden wir das Paket...
|
||||
if (fram_data.my_position > 0) {
|
||||
if ((fram_data.weight[fram_data.my_position] - fram_data.weight[fram_data.my_position - 1]) >= 9999999) {
|
||||
Serial.print("CCC");
|
||||
Serial.print(fram_data.my_position);
|
||||
send_lora_data = true;
|
||||
}
|
||||
}
|
||||
fram_data.my_position++;
|
||||
|
||||
if (fram_data.my_position == MAX_VALUES_TO_SEND) {
|
||||
send_lora_data = true;
|
||||
}
|
||||
|
||||
Serial.print("send_lora_data:");
|
||||
Serial.print(send_lora_data);
|
||||
if (send_lora_data) {
|
||||
Serial.println("SendLoraPacket");
|
||||
lora_data.version = 1;
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
lora_data.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
lora_data.vbat = (byte)(vbat / 20);
|
||||
|
||||
|
||||
// LORA
|
||||
#ifdef VCC_ENABLE
|
||||
// For Pinoccio Scout boards
|
||||
pinMode(VCC_ENABLE, OUTPUT);
|
||||
digitalWrite(VCC_ENABLE, HIGH);
|
||||
delay(1000);
|
||||
#endif
|
||||
|
||||
// LMIC init
|
||||
os_init();
|
||||
// Reset the MAC state. Session and pending data transfers will be discarded.
|
||||
LMIC_reset();
|
||||
|
||||
// Set static session parameters. Instead of dynamically establishing a session
|
||||
// by joining the network, precomputed session parameters are be provided.
|
||||
|
||||
LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
||||
|
||||
#if defined(CFG_eu868)
|
||||
// Set up the channels used by the Things Network, which corresponds
|
||||
// to the defaults of most gateways. Without this, only three base
|
||||
// channels from the LoRaWAN specification are used, which certainly
|
||||
// works, so it is good for debugging, but can overload those
|
||||
// frequencies, so be sure to configure the full frequency range of
|
||||
// your network here (unless your network autoconfigures them).
|
||||
// Setting up channels should happen after LMIC_setSession, as that
|
||||
// configures the minimal channel set.
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI); // g-band
|
||||
LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK, DR_FSK), BAND_MILLI); // g2-band
|
||||
// TTN defines an additional channel at 869.525Mhz using SF9 for class B
|
||||
// devices' ping slots. LMIC does not have an easy way to define set this
|
||||
// frequency and support for class B is spotty and untested, so this
|
||||
// frequency is not configured here.
|
||||
#elif defined(CFG_us915)
|
||||
// NA-US channels 0-71 are configured automatically
|
||||
// but only one group of 8 should (a subband) should be active
|
||||
// TTN recommends the second sub band, 1 in a zero based count.
|
||||
// https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
|
||||
LMIC_selectSubBand(1);
|
||||
#endif
|
||||
|
||||
// Disable link check validation
|
||||
LMIC_setLinkCheckMode(0);
|
||||
|
||||
// TTN uses SF9 for its RX2 window.
|
||||
LMIC.dn2Dr = DR_SF9;
|
||||
|
||||
// Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library)
|
||||
LMIC_setDrTxpow(DR_SF7, 14);
|
||||
|
||||
// Start job
|
||||
do_send(&sendjob);
|
||||
|
||||
// END LORA
|
||||
// Jetzt koennen wir die FRAM-Werte wieder initialisieren
|
||||
for (int i=0; i < MAX_VALUES_TO_SEND; i++) {
|
||||
fram_data.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
|
||||
}
|
||||
delay(2000);
|
||||
Serial.println("BLABLA");
|
||||
// soll evtl. das Setup durchgefuehrt werden?
|
||||
if (usb_power_only && Serial.available()) {
|
||||
Serial.println("GGGGBLABLA");
|
||||
String s = Serial.readStringUntil('\n');
|
||||
if (s == "setup") {
|
||||
Serial.println("SETUPGGGGBLABLA");
|
||||
Setup();
|
||||
} else {
|
||||
Serial.println("{ \"msg\": \"Unknown command (only setup is allowed here)\"");
|
||||
}
|
||||
}
|
||||
|
||||
// dump the entire 8K of memory!
|
||||
if (usb_power_only) {
|
||||
uint8_t value;
|
||||
for (uint16_t a = 0; a < sizeof(FRAM_data); a++) {
|
||||
value = fram.read8(a);
|
||||
if ((a % 32) == 0) {
|
||||
Serial.print("\n 0x"); Serial.print(a, HEX); Serial.print(": ");
|
||||
}
|
||||
Serial.print("0x");
|
||||
if (value < 0x1)
|
||||
Serial.print('0');
|
||||
Serial.print(value, HEX); Serial.print(" ");
|
||||
}
|
||||
Serial.print("\nsizeof(FRAM_data): " + String(sizeof(FRAM_data)));
|
||||
}
|
||||
|
||||
// Jetzt sichern wir die Werte
|
||||
Save2FRAM();
|
||||
|
||||
//ShowFRAMData();
|
||||
|
||||
#if DEBUG
|
||||
delay(3000);
|
||||
#endif
|
||||
|
||||
if (not(send_lora_data)) {
|
||||
print_debug("Jetzt senden wir das DONE Signal...");
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loop(void)
|
||||
{
|
||||
// Hier haben wir nichts zu tun, wir machen alles im Setup...
|
||||
os_runloop_once(); // EVTL. NOETIG?????
|
||||
}
|
||||
|
|
@ -0,0 +1,796 @@
|
|||
#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) > 5) || (abs(weight - last_weight) > 2)) {
|
||||
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() {
|
||||
}
|
||||
|
|
@ -0,0 +1,796 @@
|
|||
#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() {
|
||||
}
|
||||
|
|
@ -0,0 +1,797 @@
|
|||
#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;
|
||||
|
||||
// we send it when all measurement slots are taken or if startup_counter expires
|
||||
if ((fram_data.my_position >= MAX_VALUES_TO_SEND) || ((fram_data.current_startup_counter >= JOIN_AFTER_N_STARTUPS))) {
|
||||
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) > 50)) {
|
||||
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() {
|
||||
}
|
||||
|
|
@ -0,0 +1,797 @@
|
|||
#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;
|
||||
|
||||
// we send it when all measurement slots are taken or if startup_counter expires
|
||||
if ((fram_data.my_position >= MAX_VALUES_TO_SEND) || ((fram_data.current_startup_counter >= JOIN_AFTER_N_STARTUPS))) {
|
||||
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) > 50) || (fram_data.current_startup_counter >= JOIN_AFTER_N_STARTUPS)) {
|
||||
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() {
|
||||
}
|
||||
|
|
@ -0,0 +1,672 @@
|
|||
#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 5
|
||||
#define MAX_SHORT 32767
|
||||
#define JOIN_AFTER_N_STARTUPS 10
|
||||
|
||||
// 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
|
||||
|
||||
// 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];
|
||||
|
||||
u4_t framecounterup;
|
||||
u4_t framecounterdown;
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
byte logging; // 0: no, otherwise: yes
|
||||
byte startup_counter;
|
||||
} FRAM_data;
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} 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 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.framecounterup);
|
||||
Serial.println(",");
|
||||
Serial.print(" \"framecounterdown\": ");
|
||||
Serial.print(fram_data.framecounterdown);
|
||||
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(" \"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(" \"startup_counter\": ");
|
||||
Serial.println(fram_data.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);
|
||||
}
|
||||
|
||||
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 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.startup_counter == 0) {
|
||||
// do nothing
|
||||
logit("we do nothing => OTAA");
|
||||
}
|
||||
else if (fram_data.startup_counter >= JOIN_AFTER_N_STARTUPS) {
|
||||
fram_data.startup_counter = 0;
|
||||
} else {
|
||||
LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
||||
// We set the frame counters
|
||||
LMIC.seqnoUp = fram_data.framecounterup;
|
||||
LMIC.seqnoDn = fram_data.framecounterdown;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
fram_data.weight[fram_data.my_position] = (int)GetWeight() / 10;
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
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.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
|
||||
// Start job
|
||||
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.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
// We set the frame counters
|
||||
fram_data.framecounterup = LMIC.seqnoUp;
|
||||
fram_data.framecounterdown = LMIC.seqnoDn;
|
||||
}
|
||||
|
||||
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.startup_counter = 0;
|
||||
InitFeather();
|
||||
if (isUSBCableAttached()) {
|
||||
InitSerial();
|
||||
}
|
||||
InitAndReaFRAM();
|
||||
//fram_data.startup_counter = 0;
|
||||
ReadSensors();
|
||||
if (isUSBCableAttached() && Serial.available()) {
|
||||
Setup();
|
||||
}
|
||||
if (ShouldDataBeSent()) {
|
||||
InitLORA();
|
||||
SendLoraData();
|
||||
}
|
||||
fram_data.startup_counter++;
|
||||
SaveFRAM();
|
||||
TurnOff();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
|
@ -0,0 +1,674 @@
|
|||
#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 5
|
||||
#define MAX_SHORT 32767
|
||||
#define JOIN_AFTER_N_STARTUPS 10
|
||||
|
||||
// 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
|
||||
|
||||
// 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];
|
||||
|
||||
// session information
|
||||
u4_t framecounterup;
|
||||
u4_t framecounterdown;
|
||||
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
byte logging; // 0: no, otherwise: yes
|
||||
byte startup_counter;
|
||||
} FRAM_data;
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} 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 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.framecounterup);
|
||||
Serial.println(",");
|
||||
Serial.print(" \"framecounterdown\": ");
|
||||
Serial.print(fram_data.framecounterdown);
|
||||
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(" \"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(" \"startup_counter\": ");
|
||||
Serial.println(fram_data.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);
|
||||
}
|
||||
|
||||
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 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.startup_counter == 0) {
|
||||
// do nothing
|
||||
logit("we do nothing => OTAA");
|
||||
}
|
||||
else if (fram_data.startup_counter >= JOIN_AFTER_N_STARTUPS) {
|
||||
fram_data.startup_counter = 0;
|
||||
} else {
|
||||
LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
||||
// We set the frame counters
|
||||
LMIC.seqnoUp = fram_data.framecounterup;
|
||||
LMIC.seqnoDn = fram_data.framecounterdown;
|
||||
}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
fram_data.weight[fram_data.my_position] = (int)GetWeight() / 10;
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
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.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
|
||||
// Start job
|
||||
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.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
// We set the frame counters
|
||||
fram_data.framecounterup = LMIC.seqnoUp;
|
||||
fram_data.framecounterdown = LMIC.seqnoDn;
|
||||
}
|
||||
|
||||
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.startup_counter = 0;
|
||||
InitFeather();
|
||||
if (isUSBCableAttached()) {
|
||||
InitSerial();
|
||||
}
|
||||
InitAndReaFRAM();
|
||||
//fram_data.startup_counter = 0;
|
||||
ReadSensors();
|
||||
if (isUSBCableAttached() && Serial.available()) {
|
||||
Setup();
|
||||
}
|
||||
if (ShouldDataBeSent()) {
|
||||
InitLORA();
|
||||
SendLoraData();
|
||||
}
|
||||
fram_data.startup_counter++;
|
||||
SaveFRAM();
|
||||
TurnOff();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
|
@ -0,0 +1,688 @@
|
|||
#include <lmic.h>
|
||||
#include <hal/hal.h>
|
||||
#include <SPI.h>
|
||||
#include "HX711.h"
|
||||
#include "Adafruit_Si7021.h"
|
||||
#include "Adafruit_FRAM_SPI.h"
|
||||
#include <Adafruit_SleepyDog.h>
|
||||
|
||||
// Defines
|
||||
#define TIMEOUTMS 60000
|
||||
#define SENDDIFFTHRESHOLD 999
|
||||
#define DONEPIN A5
|
||||
#define VBATPIN A7
|
||||
#define MAX_VALUES_TO_SEND 5
|
||||
#define MAX_SHORT 32767
|
||||
#define JOIN_AFTER_N_STARTUPS 10
|
||||
|
||||
// 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
|
||||
|
||||
// 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];
|
||||
|
||||
// session information
|
||||
u4_t framecounterup;
|
||||
u4_t framecounterdown;
|
||||
|
||||
long cal_w1_0;
|
||||
long cal_w2_0;
|
||||
float cal_w1_factor;
|
||||
float cal_w2_factor;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
byte logging; // 0: no, otherwise: yes
|
||||
byte startup_counter;
|
||||
} FRAM_data;
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} 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 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.framecounterup);
|
||||
Serial.println(",");
|
||||
Serial.print(" \"framecounterdown\": ");
|
||||
Serial.print(fram_data.framecounterdown);
|
||||
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(" \"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(" \"startup_counter\": ");
|
||||
Serial.println(fram_data.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);
|
||||
}
|
||||
|
||||
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 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.startup_counter == 0) {
|
||||
// do nothing
|
||||
// logit("we do nothing => OTAA");
|
||||
//}
|
||||
//else if (fram_data.startup_counter >= JOIN_AFTER_N_STARTUPS) {
|
||||
// fram_data.startup_counter = 0;
|
||||
//} else {
|
||||
// LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey);
|
||||
// We set the frame counters
|
||||
// LMIC.seqnoUp = fram_data.framecounterup;
|
||||
// LMIC.seqnoDn = fram_data.framecounterdown;
|
||||
//}
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
fram_data.weight[fram_data.my_position] = (int)GetWeight() / 10;
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
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.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
|
||||
// Start job
|
||||
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.weight[i] = MAX_SHORT;
|
||||
fram_data.temperature[i] = MAX_SHORT;
|
||||
}
|
||||
fram_data.my_position = 0;
|
||||
|
||||
// We set the frame counters
|
||||
fram_data.framecounterup = LMIC.seqnoUp;
|
||||
fram_data.framecounterdown = LMIC.seqnoDn;
|
||||
}
|
||||
|
||||
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.startup_counter = 0;
|
||||
InitFeather();
|
||||
if (isUSBCableAttached()) {
|
||||
InitSerial();
|
||||
}
|
||||
InitAndReaFRAM();
|
||||
// TurnOff();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
bool logged = false;
|
||||
unsigned long starttime;
|
||||
starttime = millis();
|
||||
//fram_data.startup_counter = 0;
|
||||
ReadSensors();
|
||||
if (isUSBCableAttached() && Serial.available()) {
|
||||
Setup();
|
||||
}
|
||||
if (ShouldDataBeSent()) {
|
||||
InitLORA();
|
||||
SendLoraData();
|
||||
}
|
||||
// We sleep until 5 minutes are over...
|
||||
while ((millis() - starttime) < 5*60*1000) {
|
||||
int sleepMS = Watchdog.sleep();
|
||||
if ((millis() % 1000) == 0) {
|
||||
if (!logged) {
|
||||
logit("wait for 5 minutes passed...");
|
||||
logged = true;
|
||||
}
|
||||
} else {
|
||||
logged = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,711 @@
|
|||
#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 5
|
||||
#define MAX_SHORT 32767
|
||||
#define JOIN_AFTER_N_STARTUPS 10
|
||||
|
||||
// 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;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
byte logging; // 0: no, otherwise: yes
|
||||
byte startup_counter;
|
||||
|
||||
// sesion info
|
||||
SESSION_info session_info;
|
||||
|
||||
} FRAM_data;
|
||||
|
||||
typedef struct {
|
||||
byte version; // Versionierung des Paketformats
|
||||
short weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm
|
||||
short temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius
|
||||
byte vbat; // Batteriespannung (1 Einheit => 20 mV)
|
||||
} 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 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(" \"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(" \"startup_counter\": ");
|
||||
Serial.println(fram_data.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.startup_counter == 0) {
|
||||
// do nothing
|
||||
logit("we do nothing => OTAA");
|
||||
}
|
||||
else if (fram_data.startup_counter >= JOIN_AFTER_N_STARTUPS) {
|
||||
fram_data.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);
|
||||
fram_data.weight[fram_data.my_position] = (int)GetWeight() / 10;
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
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.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
|
||||
// Start job
|
||||
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.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.startup_counter = 0;
|
||||
InitFeather();
|
||||
if (isUSBCableAttached()) {
|
||||
InitSerial();
|
||||
}
|
||||
InitAndReaFRAM();
|
||||
//fram_data.startup_counter = 0;
|
||||
ReadSensors();
|
||||
if (isUSBCableAttached() && Serial.available()) {
|
||||
Setup();
|
||||
}
|
||||
if (ShouldDataBeSent()) {
|
||||
InitLORA();
|
||||
SendLoraData();
|
||||
}
|
||||
fram_data.startup_counter++;
|
||||
SaveFRAM();
|
||||
TurnOff();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
|
@ -0,0 +1,715 @@
|
|||
#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 5
|
||||
#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;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
byte logging; // 0: no, otherwise: yes
|
||||
byte 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)
|
||||
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 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(" \"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(" \"startup_counter\": ");
|
||||
Serial.println(fram_data.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.startup_counter == 0) {
|
||||
// do nothing
|
||||
logit("we do nothing => OTAA");
|
||||
}
|
||||
else if (fram_data.startup_counter >= JOIN_AFTER_N_STARTUPS) {
|
||||
logit("we do a OTAA Join again, as the startup counter is high enough...");
|
||||
fram_data.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);
|
||||
fram_data.weight[fram_data.my_position] = (int)GetWeight() / 10;
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
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.weight[i] = fram_data.weight[i];
|
||||
lora_data.temperature[i] = fram_data.temperature[i];
|
||||
}
|
||||
|
||||
// Start job
|
||||
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.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.startup_counter = 0;
|
||||
InitFeather();
|
||||
if (isUSBCableAttached()) {
|
||||
InitSerial();
|
||||
}
|
||||
InitAndReaFRAM();
|
||||
//fram_data.startup_counter = 0;
|
||||
ReadSensors();
|
||||
if (isUSBCableAttached() && Serial.available()) {
|
||||
Setup();
|
||||
}
|
||||
if (ShouldDataBeSent()) {
|
||||
InitLORA();
|
||||
SendLoraData();
|
||||
}
|
||||
fram_data.startup_counter++;
|
||||
|
||||
ShowFRAMData();
|
||||
|
||||
SaveFRAM();
|
||||
TurnOff();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
|
@ -0,0 +1,723 @@
|
|||
#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 5
|
||||
#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;
|
||||
short weight[MAX_VALUES_TO_SEND];
|
||||
short temperature[MAX_VALUES_TO_SEND];
|
||||
byte my_position;
|
||||
byte logging; // 0: no, otherwise: yes
|
||||
byte 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)
|
||||
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 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(" \"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(" \"startup_counter\": ");
|
||||
Serial.println(fram_data.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.startup_counter == 0) {
|
||||
// do nothing
|
||||
logit("we do nothing => OTAA");
|
||||
}
|
||||
else if (fram_data.startup_counter >= JOIN_AFTER_N_STARTUPS) {
|
||||
logit("we do a OTAA Join again, as the startup counter is high enough...");
|
||||
fram_data.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);
|
||||
fram_data.weight[fram_data.my_position] = (int)GetWeight() / 10;
|
||||
fram_data.temperature[fram_data.my_position] = (int)GetTemp() * 10;
|
||||
|
||||
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.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;
|
||||
//}
|
||||
|
||||
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.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.startup_counter = 0;
|
||||
InitFeather();
|
||||
if (isUSBCableAttached()) {
|
||||
InitSerial();
|
||||
}
|
||||
InitAndReaFRAM();
|
||||
//fram_data.startup_counter = 0;
|
||||
ReadSensors();
|
||||
if (isUSBCableAttached() && Serial.available()) {
|
||||
Setup();
|
||||
}
|
||||
if (ShouldDataBeSent()) {
|
||||
InitLORA();
|
||||
SendLoraData();
|
||||
}
|
||||
fram_data.startup_counter++;
|
||||
|
||||
ShowFRAMData();
|
||||
|
||||
SaveFRAM();
|
||||
TurnOff();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
#define DONEPIN A5
|
||||
|
||||
void InitFeather() {
|
||||
// DONEPIN must be low...
|
||||
pinMode(DONEPIN, OUTPUT);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
}
|
||||
|
||||
void TurnOff() {
|
||||
delay(500);
|
||||
while (1) {
|
||||
digitalWrite(DONEPIN, HIGH);
|
||||
delay(5);
|
||||
digitalWrite(DONEPIN, LOW);
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
InitFeather();
|
||||
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
|
||||
delay(1000);
|
||||
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
|
||||
delay(1000);
|
||||
TurnOff();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
|
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 33.866666 33.866668"
|
||||
version="1.1"
|
||||
id="svg96"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="bee-logo-128.svg">
|
||||
<defs
|
||||
id="defs90" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="6.4296875"
|
||||
inkscape:cx="64"
|
||||
inkscape:cy="63.780818"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata93">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-263.13332)">
|
||||
<path
|
||||
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:1.45152938;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 27.852303,290.47624 -3.615553,2.08743 -3.615561,2.08745 c -3.541335,2.0446 -3.924636,1.909 -7.231118,0 l -3.6155541,-2.08745 -3.6155596,-2.08743 c -3.5413426,-2.04459 -3.61556,-2.44436 -3.61556,-6.26234 v -4.17488 -4.17489 c 0,-4.08919 0.3090839,-4.35333 3.61556,-6.26233 l 3.6155596,-2.08744 3.6155541,-2.08745 c 3.541341,-2.04459 3.924643,-1.909 7.231118,0 l 3.615561,2.08745 3.615559,2.08744 c 3.541336,2.04459 3.615553,2.44433 3.615553,6.26233 v 4.17489 4.17488 c 0,4.08919 -0.309077,4.35335 -3.615559,6.26234 z"
|
||||
id="path815"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scsscsscsscsscsscss" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:3.98839998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 9.0483397,276.19348 c 2.3849623,-1.00878 5.0071013,-1.5666 7.7594953,-1.5666 2.752428,0 5.374568,0.55782 7.759531,1.5666"
|
||||
id="path846-4"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:3.98839998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 11.201755,281.29108 c 1.72309,-0.72881 3.617523,-1.13183 5.60608,-1.13183 1.988585,0 3.883024,0.40302 5.606108,1.13183"
|
||||
id="path846-1-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:3.98839998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 13.357941,286.39523 c 1.060379,-0.44849 2.226175,-0.69651 3.449894,-0.69651 1.223753,0 2.38955,0.24802 3.449902,0.69651"
|
||||
id="path846-8-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:2.90305877;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10.674714,268.54246 8.8603022,264.91363"
|
||||
id="path899"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#483737;stroke-width:2.90305877;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 23.375595,268.54246 1.814411,-3.62883"
|
||||
id="path899-8"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 8.4666667 8.4666667"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="bee-logo-favicon.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32.219661"
|
||||
inkscape:cx="15.994178"
|
||||
inkscape:cy="15.908881"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1137"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-288.53333)">
|
||||
<path
|
||||
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:0.34297523;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
inkscape:transform-center-y="2.7179872e-006"
|
||||
d="m 6.7947044,295.25694 -0.8543043,0.49323 -0.8543032,0.49323 c -0.836767,0.4831 -0.9273356,0.45107 -1.7086083,0 l -0.8543034,-0.49323 -0.8543037,-0.49323 c -0.83676778,-0.48312 -0.85430358,-0.57757 -0.85430358,-1.4797 v -0.98647 -0.98647 c 0,-0.96621 0.0730309,-1.02863 0.85430358,-1.47969 l 0.8543037,-0.49324 0.8543034,-0.49323 c 0.836767,-0.4831 0.9273356,-0.45106 1.7086083,0 l 0.8543032,0.49323 0.8543043,0.49324 c 0.8367663,0.48311 0.8543033,0.57756 0.8543033,1.47969 v 0.98647 0.98647 c 0,0.96621 -0.073032,1.02862 -0.8543033,1.4797 z"
|
||||
id="path815"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scsscsscsscsscsscss" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:0.94240111;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 2.3516008,291.88212 c 0.5635314,-0.23836 1.1831042,-0.37016 1.8334565,-0.37016 0.6503582,0 1.2699309,0.1318 1.8334624,0.37016"
|
||||
id="path846-4"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:0.94240111;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 2.8604217,293.08661 c 0.4071407,-0.1722 0.854768,-0.26743 1.3246356,-0.26743 0.4698734,0 0.9175007,0.0952 1.3246415,0.26743"
|
||||
id="path846-1-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:0.94240111;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 3.3698959,294.29265 c 0.250552,-0.10598 0.5260134,-0.16458 0.8151614,-0.16458 0.2891538,0 0.5646152,0.0586 0.8151621,0.16458"
|
||||
id="path846-8-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:0.68595046;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.7358891,290.0743 -0.428719,-0.85744"
|
||||
id="path899"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#483737;stroke-width:0.68595046;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 5.7369225,290.0743 0.428719,-0.85744"
|
||||
id="path899-8"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
|
@ -0,0 +1,171 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="83.081947mm"
|
||||
height="52.319721mm"
|
||||
viewBox="0 0 83.081947 52.319721"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="bee-logo-gehaeusedeckel.svg"
|
||||
inkscape:export-filename="/home/joerg/bee-logo-box.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2">
|
||||
<marker
|
||||
inkscape:stockid="DotL"
|
||||
orient="auto"
|
||||
refY="0"
|
||||
refX="0"
|
||||
id="DotL"
|
||||
style="overflow:visible"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
id="path978"
|
||||
d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
|
||||
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
|
||||
transform="matrix(0.8,0,0,0.8,5.92,0.8)"
|
||||
inkscape:connector-curvature="0" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.1106813"
|
||||
inkscape:cx="196.93893"
|
||||
inkscape:cy="42.625215"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="10"
|
||||
fit-margin-left="10"
|
||||
fit-margin-right="10"
|
||||
fit-margin-bottom="10">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid895"
|
||||
spacingx="1"
|
||||
spacingy="1"
|
||||
originx="15.558254"
|
||||
originy="-258.4087" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(15.558254,13.728425)">
|
||||
<path
|
||||
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:1.06282127;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
inkscape:transform-center-x="1.3710029e-06"
|
||||
d="m 13.504535,16.230091 -2.647331,1.528433 -2.6473398,1.528437 c -2.5929935,1.49706 -2.8736515,1.397772 -5.294677,0 L 0.26784776,17.758524 -2.3794896,16.230091 C -4.9724841,14.73304 -5.0268281,14.440334 -5.0268281,11.64479 V 8.5879209 5.5310587 c 0,-2.9941162 0.2263126,-3.1875342 2.6473385,-4.58530622 L 0.26784776,-0.58267793 2.9151872,-2.111116 c 2.5929945,-1.49705 2.8736492,-1.397765 5.294677,0 l 2.6473398,1.52843807 2.647331,1.52843041 c 2.59298,1.49705902 2.647322,1.78975872 2.647322,4.58530622 V 8.5879209 11.64479 c 0,2.99411 -0.22631,3.187537 -2.647322,4.585301 z"
|
||||
id="path815"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scsscsscsscsscsscss"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.92033887;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="M -0.26386191,5.7722064 C 1.4824251,5.033584 3.4023729,4.6251423 5.4177007,4.6251423 c 2.0153468,0 3.9352949,0.4084417 5.6815823,1.1470641"
|
||||
id="path846-4"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.92033887;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="M 1.3128851,9.5046913 C 2.5745445,8.9710536 3.9616626,8.6759639 5.4177007,8.6759639 c 1.4560561,0 2.8431755,0.2950897 4.1048356,0.8287274"
|
||||
id="path846-1-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.92033887;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 2.8916582,13.241963 c 0.7764154,-0.328392 1.6300231,-0.509984 2.5260425,-0.509984 0.8960384,0 1.7496451,0.181592 2.5260445,0.509984"
|
||||
id="path846-8-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:2.12564254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 0.92698043,0.17010281 -0.40154491,-2.4869316"
|
||||
id="path899"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<path
|
||||
style="fill:none;stroke:#483737;stroke-width:2.12564254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 10.226662,0.17010281 11.555185,-2.4869316"
|
||||
id="path899-8"
|
||||
inkscape:connector-curvature="0"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96" />
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.44661283px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#483737;stroke-width:0.11134037;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
x="4.0603533"
|
||||
y="27.739807"
|
||||
id="text18"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan16"
|
||||
x="4.0603533"
|
||||
y="27.739807"
|
||||
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93888903px;font-family:Carlito;-inkscape-font-specification:'Carlito Italic';text-align:start;text-anchor:start;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:0.11134037;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">https://www.mini-beieli.ch</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;font-size:4.44661283px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.11116531"
|
||||
x="20.300133"
|
||||
y="20.920719"
|
||||
id="text28"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan26"
|
||||
x="20.300133"
|
||||
y="20.920719"
|
||||
style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8.46666622px;font-family:Carlito;-inkscape-font-specification:'Carlito Bold Italic';fill:#483737;fill-opacity:1;stroke-width:0.11116531">BeieliScale</tspan></text>
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot844"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
|
||||
id="flowRegion846"><rect
|
||||
id="rect848"
|
||||
width="718.11023"
|
||||
height="548.03149"
|
||||
x="37.795277"
|
||||
y="404.40945" /></flowRegion><flowPara
|
||||
id="flowPara850" /></flowRoot> </g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -0,0 +1,132 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="640"
|
||||
height="160"
|
||||
viewBox="0 0 169.33333 42.333335"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="bee-nav-logo.svg"
|
||||
inkscape:export-filename="/home/joerg/bee-nav-logo.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.2"
|
||||
inkscape:cx="327.95454"
|
||||
inkscape:cy="80"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:measure-start="0,0"
|
||||
inkscape:measure-end="0,0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-254.66665)">
|
||||
<path
|
||||
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:1.61171854;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
inkscape:transform-center-y="-1.1884881e-06"
|
||||
d="m 30.950797,287.76775 -4.014567,2.31781 -4.014568,2.31781 c -3.932156,2.27023 -4.35776,2.11967 -8.029136,0 l -4.014568,-2.31781 -4.0145672,-2.31781 c -3.9321571,-2.27023 -4.0145674,-2.7141 -4.0145674,-6.95345 v -4.63561 -4.63562 c 0,-4.54047 0.3431917,-4.83378 4.0145674,-6.95345 l 4.0145682,-2.31781 4.014567,-2.31781 c 3.932158,-2.27023 4.357759,-2.11966 8.029136,0 l 4.014568,2.31781 4.014568,2.31781 c 3.932156,2.27024 4.014567,2.71411 4.014567,6.95345 v 4.63562 4.63561 c 0,4.54047 -0.343191,4.83378 -4.014568,6.95345 z"
|
||||
id="path815"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scsscsscsscsscsscss" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:4.42855644;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 10.071645,271.90876 c 2.648164,-1.1201 5.559677,-1.73948 8.61583,-1.73948 3.05618,0 5.967692,0.61938 8.615856,1.73948"
|
||||
id="path846-4"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:4.42855644;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 12.462709,277.56893 c 1.913249,-0.80924 4.01675,-1.25674 6.224766,-1.25674 2.208042,0 4.311543,0.4475 6.224792,1.25674"
|
||||
id="path846-1-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:4.42855644;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 14.856847,283.23636 c 1.177398,-0.49799 2.471855,-0.77336 3.830628,-0.77336 1.358799,0 2.653257,0.27537 3.83063,0.77336"
|
||||
id="path846-8-2"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="csc" />
|
||||
<path
|
||||
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:3.22343707;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 11.877504,263.41338 9.8628558,259.38409"
|
||||
id="path899"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#483737;stroke-width:3.22343707;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 25.980042,263.41338 2.014649,-4.02929"
|
||||
id="path899-8"
|
||||
inkscape:connector-curvature="0" />
|
||||
<flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot73"
|
||||
style="fill:black;fill-opacity:1;stroke:none;font-family:'DejaVu Sans';font-style:normal;font-weight:bold;font-size:53.33333333px;line-height:1.25;letter-spacing:0px;word-spacing:0px;-inkscape-font-specification:'DejaVu Sans Bold';font-stretch:normal;font-variant:normal;"><flowRegion
|
||||
id="flowRegion75"
|
||||
style="-inkscape-font-specification:'DejaVu Sans Bold';font-family:'DejaVu Sans';font-weight:bold;font-style:normal;font-stretch:normal;font-variant:normal;font-size:53.33333333px;"><rect
|
||||
id="rect77"
|
||||
width="443.63635"
|
||||
height="125"
|
||||
x="168.18182"
|
||||
y="17.272728"
|
||||
style="-inkscape-font-specification:'DejaVu Sans Bold';font-family:'DejaVu Sans';font-weight:bold;font-style:normal;font-stretch:normal;font-variant:normal;font-size:53.33333333px;" /></flowRegion><flowPara
|
||||
id="flowPara79" /></flowRoot> <flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot81"
|
||||
style="fill:black;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px"><flowRegion
|
||||
id="flowRegion83"><rect
|
||||
id="rect85"
|
||||
width="404.54544"
|
||||
height="111.81818"
|
||||
x="176.81818"
|
||||
y="21.818182" /></flowRegion><flowPara
|
||||
id="flowPara87" /></flowRoot> <flowRoot
|
||||
xml:space="preserve"
|
||||
id="flowRoot89"
|
||||
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
|
||||
transform="matrix(0.26458333,0,0,0.26458333,0.52916667,261.01669)"><flowRegion
|
||||
id="flowRegion91"><rect
|
||||
id="rect93"
|
||||
width="428.63635"
|
||||
height="114.09091"
|
||||
x="169.54546"
|
||||
y="21.818182" /></flowRegion><flowPara
|
||||
id="flowPara95"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:53.33333588px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold'">mini-beieli.ch</flowPara></flowRoot> </g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 778 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 540 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
DEVICE=/dev/ttyACM0
|
||||
while [ ! -e ${DEVICE} ]; do
|
||||
sleep 0.2
|
||||
done
|
||||
minicom -D ${DEVICE} -b 115200
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/python3
|
||||
from time import sleep
|
||||
import serial
|
||||
import sys
|
||||
|
||||
exceptions = 0
|
||||
while (exceptions < 10):
|
||||
try:
|
||||
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
|
||||
except:
|
||||
exceptions = exceptions + 1
|
||||
sleep(0.5)
|
||||
else:
|
||||
exceptions = 999
|
||||
|
||||
if (exceptions != 999):
|
||||
print("Cannot open Serial Port")
|
||||
sys.exit()
|
||||
|
||||
|
||||
# Wir leeren den Input Buffer
|
||||
while True:
|
||||
try:
|
||||
while ser.inWaiting():
|
||||
print(ser.read().decode('utf-8'), end='')
|
||||
sleep(0.1)
|
||||
except:
|
||||
sys.exit()
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/python3
|
||||
from time import sleep
|
||||
import serial
|
||||
import sys
|
||||
|
||||
arg1 = sys.argv[1] + "\n"
|
||||
|
||||
exceptions = 0
|
||||
while (exceptions < 10):
|
||||
try:
|
||||
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
|
||||
except:
|
||||
exceptions = exceptions + 1
|
||||
sleep(0.5)
|
||||
else:
|
||||
exceptions = 999
|
||||
|
||||
if (exceptions != 999):
|
||||
print("Cannot open Serial Port")
|
||||
sys.exit()
|
||||
|
||||
|
||||
sleep(0.1)
|
||||
# Wir leeren den Input Buffer
|
||||
while ser.inWaiting():
|
||||
#print("AAA: "+ser.readline().decode('utf-8'), end='')
|
||||
ser.readline()
|
||||
|
||||
ser.write(arg1.encode('utf-8'))
|
||||
sleep(0.1)
|
||||
ch=" "
|
||||
while (ch != b'}'):
|
||||
try:
|
||||
data_available = ser.inWaiting()
|
||||
except:
|
||||
sys.exit()
|
||||
else:
|
||||
while data_available:
|
||||
try:
|
||||
ch=ser.read()
|
||||
except:
|
||||
sys.exit()
|
||||
else:
|
||||
print(ch.decode('utf-8'), end='')
|
||||
if ch == b'}':
|
||||
print()
|
||||
sys.exit()
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/python3
|
||||
from time import sleep
|
||||
import serial
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
while (True):
|
||||
serial_not_open = True
|
||||
while serial_not_open:
|
||||
try:
|
||||
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
|
||||
except:
|
||||
sleep(0.5)
|
||||
else:
|
||||
serial_not_open = False
|
||||
|
||||
no_exception = True
|
||||
while no_exception:
|
||||
try:
|
||||
while True:
|
||||
while ser.inWaiting():
|
||||
print(ser.read().decode('utf-8'), end='')
|
||||
sleep(0.1)
|
||||
except:
|
||||
print(datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")," - Serial Device disappeared...")
|
||||
ser.close()
|
||||
no_exception = False
|
||||