first commit

This commit is contained in:
Joerg Lehmann 2018-11-22 16:52:28 +01:00
commit cd7de5dc64
55 changed files with 18301 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {
}

View File

@ -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() {
}

View File

@ -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() {
}

View File

@ -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() {
}

View File

@ -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() {
}

View File

@ -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() {
}

View File

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

View File

@ -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() {
}

View File

@ -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() {
}

View File

@ -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() {
}

View File

@ -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() {
}

BIN
Artwork/bee-logo-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

96
Artwork/bee-logo-128.svg Normal file
View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="128"
height="128"
viewBox="0 0 33.866666 33.866668"
version="1.1"
id="svg96"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="bee-logo-128.svg">
<defs
id="defs90" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="6.4296875"
inkscape:cx="64"
inkscape:cy="63.780818"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata93">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-263.13332)">
<path
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:1.45152938;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
d="m 27.852303,290.47624 -3.615553,2.08743 -3.615561,2.08745 c -3.541335,2.0446 -3.924636,1.909 -7.231118,0 l -3.6155541,-2.08745 -3.6155596,-2.08743 c -3.5413426,-2.04459 -3.61556,-2.44436 -3.61556,-6.26234 v -4.17488 -4.17489 c 0,-4.08919 0.3090839,-4.35333 3.61556,-6.26233 l 3.6155596,-2.08744 3.6155541,-2.08745 c 3.541341,-2.04459 3.924643,-1.909 7.231118,0 l 3.615561,2.08745 3.615559,2.08744 c 3.541336,2.04459 3.615553,2.44433 3.615553,6.26233 v 4.17489 4.17488 c 0,4.08919 -0.309077,4.35335 -3.615559,6.26234 z"
id="path815"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scsscsscsscsscsscss" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:3.98839998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 9.0483397,276.19348 c 2.3849623,-1.00878 5.0071013,-1.5666 7.7594953,-1.5666 2.752428,0 5.374568,0.55782 7.759531,1.5666"
id="path846-4"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:3.98839998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 11.201755,281.29108 c 1.72309,-0.72881 3.617523,-1.13183 5.60608,-1.13183 1.988585,0 3.883024,0.40302 5.606108,1.13183"
id="path846-1-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:3.98839998;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 13.357941,286.39523 c 1.060379,-0.44849 2.226175,-0.69651 3.449894,-0.69651 1.223753,0 2.38955,0.24802 3.449902,0.69651"
id="path846-8-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:2.90305877;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 10.674714,268.54246 8.8603022,264.91363"
id="path899"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#483737;stroke-width:2.90305877;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.375595,268.54246 1.814411,-3.62883"
id="path899-8"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
Artwork/bee-logo-28.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
Artwork/bee-logo-28.xcf Normal file

Binary file not shown.

BIN
Artwork/bee-logo-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
Artwork/bee-logo-64.xcf Normal file

Binary file not shown.

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="32"
height="32"
viewBox="0 0 8.4666667 8.4666667"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="bee-logo-favicon.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32.219661"
inkscape:cx="15.994178"
inkscape:cy="15.908881"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1137"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-288.53333)">
<path
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:0.34297523;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
inkscape:transform-center-y="2.7179872e-006"
d="m 6.7947044,295.25694 -0.8543043,0.49323 -0.8543032,0.49323 c -0.836767,0.4831 -0.9273356,0.45107 -1.7086083,0 l -0.8543034,-0.49323 -0.8543037,-0.49323 c -0.83676778,-0.48312 -0.85430358,-0.57757 -0.85430358,-1.4797 v -0.98647 -0.98647 c 0,-0.96621 0.0730309,-1.02863 0.85430358,-1.47969 l 0.8543037,-0.49324 0.8543034,-0.49323 c 0.836767,-0.4831 0.9273356,-0.45106 1.7086083,0 l 0.8543032,0.49323 0.8543043,0.49324 c 0.8367663,0.48311 0.8543033,0.57756 0.8543033,1.47969 v 0.98647 0.98647 c 0,0.96621 -0.073032,1.02862 -0.8543033,1.4797 z"
id="path815"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scsscsscsscsscsscss" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:0.94240111;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 2.3516008,291.88212 c 0.5635314,-0.23836 1.1831042,-0.37016 1.8334565,-0.37016 0.6503582,0 1.2699309,0.1318 1.8334624,0.37016"
id="path846-4"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:0.94240111;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 2.8604217,293.08661 c 0.4071407,-0.1722 0.854768,-0.26743 1.3246356,-0.26743 0.4698734,0 0.9175007,0.0952 1.3246415,0.26743"
id="path846-1-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:0.94240111;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 3.3698959,294.29265 c 0.250552,-0.10598 0.5260134,-0.16458 0.8151614,-0.16458 0.2891538,0 0.5646152,0.0586 0.8151621,0.16458"
id="path846-8-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:0.68595046;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 2.7358891,290.0743 -0.428719,-0.85744"
id="path899"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#483737;stroke-width:0.68595046;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 5.7369225,290.0743 0.428719,-0.85744"
id="path899-8"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

View File

@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="83.081947mm"
height="52.319721mm"
viewBox="0 0 83.081947 52.319721"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="bee-logo-gehaeusedeckel.svg"
inkscape:export-filename="/home/joerg/bee-logo-box.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
id="defs2">
<marker
inkscape:stockid="DotL"
orient="auto"
refY="0"
refX="0"
id="DotL"
style="overflow:visible"
inkscape:isstock="true">
<path
id="path978"
d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt;stroke-opacity:1"
transform="matrix(0.8,0,0,0.8,5.92,0.8)"
inkscape:connector-curvature="0" />
</marker>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="3.1106813"
inkscape:cx="196.93893"
inkscape:cy="42.625215"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
fit-margin-top="10"
fit-margin-left="10"
fit-margin-right="10"
fit-margin-bottom="10">
<inkscape:grid
type="xygrid"
id="grid895"
spacingx="1"
spacingy="1"
originx="15.558254"
originy="-258.4087" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(15.558254,13.728425)">
<path
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:1.06282127;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
inkscape:transform-center-x="1.3710029e-06"
d="m 13.504535,16.230091 -2.647331,1.528433 -2.6473398,1.528437 c -2.5929935,1.49706 -2.8736515,1.397772 -5.294677,0 L 0.26784776,17.758524 -2.3794896,16.230091 C -4.9724841,14.73304 -5.0268281,14.440334 -5.0268281,11.64479 V 8.5879209 5.5310587 c 0,-2.9941162 0.2263126,-3.1875342 2.6473385,-4.58530622 L 0.26784776,-0.58267793 2.9151872,-2.111116 c 2.5929945,-1.49705 2.8736492,-1.397765 5.294677,0 l 2.6473398,1.52843807 2.647331,1.52843041 c 2.59298,1.49705902 2.647322,1.78975872 2.647322,4.58530622 V 8.5879209 11.64479 c 0,2.99411 -0.22631,3.187537 -2.647322,4.585301 z"
id="path815"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scsscsscsscsscsscss"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.92033887;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="M -0.26386191,5.7722064 C 1.4824251,5.033584 3.4023729,4.6251423 5.4177007,4.6251423 c 2.0153468,0 3.9352949,0.4084417 5.6815823,1.1470641"
id="path846-4"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.92033887;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="M 1.3128851,9.5046913 C 2.5745445,8.9710536 3.9616626,8.6759639 5.4177007,8.6759639 c 1.4560561,0 2.8431755,0.2950897 4.1048356,0.8287274"
id="path846-1-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:2.92033887;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 2.8916582,13.241963 c 0.7764154,-0.328392 1.6300231,-0.509984 2.5260425,-0.509984 0.8960384,0 1.7496451,0.181592 2.5260445,0.509984"
id="path846-8-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:2.12564254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 0.92698043,0.17010281 -0.40154491,-2.4869316"
id="path899"
inkscape:connector-curvature="0"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<path
style="fill:none;stroke:#483737;stroke-width:2.12564254;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 10.226662,0.17010281 11.555185,-2.4869316"
id="path899-8"
inkscape:connector-curvature="0"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:4.44661283px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:#483737;stroke-width:0.11134037;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="4.0603533"
y="27.739807"
id="text18"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"><tspan
sodipodi:role="line"
id="tspan16"
x="4.0603533"
y="27.739807"
style="font-style:italic;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:4.93888903px;font-family:Carlito;-inkscape-font-specification:'Carlito Italic';text-align:start;text-anchor:start;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:0.11134037;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">https://www.mini-beieli.ch</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:4.44661283px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.11116531"
x="20.300133"
y="20.920719"
id="text28"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"><tspan
sodipodi:role="line"
id="tspan26"
x="20.300133"
y="20.920719"
style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8.46666622px;font-family:Carlito;-inkscape-font-specification:'Carlito Bold Italic';fill:#483737;fill-opacity:1;stroke-width:0.11116531">BeieliScale</tspan></text>
<flowRoot
xml:space="preserve"
id="flowRoot844"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"><flowRegion
id="flowRegion846"><rect
id="rect848"
width="718.11023"
height="548.03149"
x="37.795277"
y="404.40945" /></flowRegion><flowPara
id="flowPara850" /></flowRoot> </g>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
Artwork/bee-nav-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

132
Artwork/bee-nav-logo.svg Normal file
View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="640"
height="160"
viewBox="0 0 169.33333 42.333335"
version="1.1"
id="svg8"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="bee-nav-logo.svg"
inkscape:export-filename="/home/joerg/bee-nav-logo.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.2"
inkscape:cx="327.95454"
inkscape:cy="80"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:measure-start="0,0"
inkscape:measure-end="0,0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-254.66665)">
<path
style="opacity:1;fill:#483737;fill-opacity:1;stroke:#483737;stroke-width:1.61171854;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
inkscape:transform-center-y="-1.1884881e-06"
d="m 30.950797,287.76775 -4.014567,2.31781 -4.014568,2.31781 c -3.932156,2.27023 -4.35776,2.11967 -8.029136,0 l -4.014568,-2.31781 -4.0145672,-2.31781 c -3.9321571,-2.27023 -4.0145674,-2.7141 -4.0145674,-6.95345 v -4.63561 -4.63562 c 0,-4.54047 0.3431917,-4.83378 4.0145674,-6.95345 l 4.0145682,-2.31781 4.014567,-2.31781 c 3.932158,-2.27023 4.357759,-2.11966 8.029136,0 l 4.014568,2.31781 4.014568,2.31781 c 3.932156,2.27024 4.014567,2.71411 4.014567,6.95345 v 4.63562 4.63561 c 0,4.54047 -0.343191,4.83378 -4.014568,6.95345 z"
id="path815"
inkscape:connector-curvature="0"
sodipodi:nodetypes="scsscsscsscsscsscss" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:4.42855644;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 10.071645,271.90876 c 2.648164,-1.1201 5.559677,-1.73948 8.61583,-1.73948 3.05618,0 5.967692,0.61938 8.615856,1.73948"
id="path846-4"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:4.42855644;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 12.462709,277.56893 c 1.913249,-0.80924 4.01675,-1.25674 6.224766,-1.25674 2.208042,0 4.311543,0.4475 6.224792,1.25674"
id="path846-1-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#ffcc00;stroke-width:4.42855644;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
d="m 14.856847,283.23636 c 1.177398,-0.49799 2.471855,-0.77336 3.830628,-0.77336 1.358799,0 2.653257,0.27537 3.83063,0.77336"
id="path846-8-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="csc" />
<path
style="fill:#fc0ff0;fill-opacity:0;stroke:#483737;stroke-width:3.22343707;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 11.877504,263.41338 9.8628558,259.38409"
id="path899"
inkscape:connector-curvature="0" />
<path
style="fill:none;stroke:#483737;stroke-width:3.22343707;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 25.980042,263.41338 2.014649,-4.02929"
id="path899-8"
inkscape:connector-curvature="0" />
<flowRoot
xml:space="preserve"
id="flowRoot73"
style="fill:black;fill-opacity:1;stroke:none;font-family:'DejaVu Sans';font-style:normal;font-weight:bold;font-size:53.33333333px;line-height:1.25;letter-spacing:0px;word-spacing:0px;-inkscape-font-specification:'DejaVu Sans Bold';font-stretch:normal;font-variant:normal;"><flowRegion
id="flowRegion75"
style="-inkscape-font-specification:'DejaVu Sans Bold';font-family:'DejaVu Sans';font-weight:bold;font-style:normal;font-stretch:normal;font-variant:normal;font-size:53.33333333px;"><rect
id="rect77"
width="443.63635"
height="125"
x="168.18182"
y="17.272728"
style="-inkscape-font-specification:'DejaVu Sans Bold';font-family:'DejaVu Sans';font-weight:bold;font-style:normal;font-stretch:normal;font-variant:normal;font-size:53.33333333px;" /></flowRegion><flowPara
id="flowPara79" /></flowRoot> <flowRoot
xml:space="preserve"
id="flowRoot81"
style="fill:black;fill-opacity:1;stroke:none;font-family:sans-serif;font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;letter-spacing:0px;word-spacing:0px"><flowRegion
id="flowRegion83"><rect
id="rect85"
width="404.54544"
height="111.81818"
x="176.81818"
y="21.818182" /></flowRegion><flowPara
id="flowPara87" /></flowRoot> <flowRoot
xml:space="preserve"
id="flowRoot89"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
transform="matrix(0.26458333,0,0,0.26458333,0.52916667,261.01669)"><flowRegion
id="flowRegion91"><rect
id="rect93"
width="428.63635"
height="114.09091"
x="169.54546"
y="21.818182" /></flowRegion><flowPara
id="flowPara95"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:53.33333588px;line-height:1.25;font-family:'DejaVu Sans';-inkscape-font-specification:'DejaVu Sans Bold'">mini-beieli.ch</flowPara></flowRoot> </g>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
Artwork/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
Case/A9441107.pdf Normal file

Binary file not shown.

BIN
Case/A9441A01-OT.PDF Normal file

Binary file not shown.

BIN
Case/A9441A01.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
PCB/BeieliScale-v2.1.fzz Normal file

Binary file not shown.

BIN
PCB/pcb-2.1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

6
Python/minicom_follow.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
DEVICE=/dev/ttyACM0
while [ ! -e ${DEVICE} ]; do
sleep 0.2
done
minicom -D ${DEVICE} -b 115200

28
Python/see_serial_output.py Executable file
View File

@ -0,0 +1,28 @@
#!/usr/bin/python3
from time import sleep
import serial
import sys
exceptions = 0
while (exceptions < 10):
try:
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
except:
exceptions = exceptions + 1
sleep(0.5)
else:
exceptions = 999
if (exceptions != 999):
print("Cannot open Serial Port")
sys.exit()
# Wir leeren den Input Buffer
while True:
try:
while ser.inWaiting():
print(ser.read().decode('utf-8'), end='')
sleep(0.1)
except:
sys.exit()

47
Python/send2beieliscale.py Executable file
View File

@ -0,0 +1,47 @@
#!/usr/bin/python3
from time import sleep
import serial
import sys
arg1 = sys.argv[1] + "\n"
exceptions = 0
while (exceptions < 10):
try:
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
except:
exceptions = exceptions + 1
sleep(0.5)
else:
exceptions = 999
if (exceptions != 999):
print("Cannot open Serial Port")
sys.exit()
sleep(0.1)
# Wir leeren den Input Buffer
while ser.inWaiting():
#print("AAA: "+ser.readline().decode('utf-8'), end='')
ser.readline()
ser.write(arg1.encode('utf-8'))
sleep(0.1)
ch=" "
while (ch != b'}'):
try:
data_available = ser.inWaiting()
except:
sys.exit()
else:
while data_available:
try:
ch=ser.read()
except:
sys.exit()
else:
print(ch.decode('utf-8'), end='')
if ch == b'}':
print()
sys.exit()

27
Python/serial_monitor.py Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/python3
from time import sleep
import serial
import sys
import datetime
while (True):
serial_not_open = True
while serial_not_open:
try:
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1)
except:
sleep(0.5)
else:
serial_not_open = False
no_exception = True
while no_exception:
try:
while True:
while ser.inWaiting():
print(ser.read().decode('utf-8'), end='')
sleep(0.1)
except:
print(datetime.datetime.now().strftime("%d.%m.%Y %H:%M:%S")," - Serial Device disappeared...")
ser.close()
no_exception = False