first commit

This commit is contained in:
2018-11-22 16:52:28 +01:00
commit cd7de5dc64
55 changed files with 18301 additions and 0 deletions
@@ -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() {
}