commit cd7de5dc6472b9ca6f759cfcf974890a73d08eb8 Author: Joerg Lehmann Date: Thu Nov 22 16:52:28 2018 +0100 first commit diff --git a/Arduino/bee_scale_lora_12sep2018/bee_scale_lora_12sep2018.ino b/Arduino/bee_scale_lora_12sep2018/bee_scale_lora_12sep2018.ino new file mode 100644 index 0000000..e27b276 --- /dev/null +++ b/Arduino/bee_scale_lora_12sep2018/bee_scale_lora_12sep2018.ino @@ -0,0 +1,712 @@ +/********************************************************************* + BeieliScale by nbit Informatik GmbH + +*********************************************************************/ +#include +#include +#include +#include +#include +#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... +} diff --git a/Arduino/bee_scale_lora_15sep2018/bee_scale_lora_15sep2018.ino b/Arduino/bee_scale_lora_15sep2018/bee_scale_lora_15sep2018.ino new file mode 100644 index 0000000..34b3f64 --- /dev/null +++ b/Arduino/bee_scale_lora_15sep2018/bee_scale_lora_15sep2018.ino @@ -0,0 +1,713 @@ +/********************************************************************* + BeieliScale by nbit Informatik GmbH + +*********************************************************************/ +#include +#include +#include +#include +#include +#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... +} diff --git a/Arduino/bee_scale_lora_17sep2018/bee_scale_lora_17sep2018.ino b/Arduino/bee_scale_lora_17sep2018/bee_scale_lora_17sep2018.ino new file mode 100644 index 0000000..1bcec0a --- /dev/null +++ b/Arduino/bee_scale_lora_17sep2018/bee_scale_lora_17sep2018.ino @@ -0,0 +1,705 @@ + /********************************************************************* + BeieliScale by nbit Informatik GmbH + +*********************************************************************/ +#include +#include +#include +#include +#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... +} diff --git a/Arduino/bee_scale_lora_30aug2018/bee_scale_lora_30aug2018.ino b/Arduino/bee_scale_lora_30aug2018/bee_scale_lora_30aug2018.ino new file mode 100644 index 0000000..dad9e14 --- /dev/null +++ b/Arduino/bee_scale_lora_30aug2018/bee_scale_lora_30aug2018.ino @@ -0,0 +1,676 @@ +/********************************************************************* + BeieliScale by nbit Informatik GmbH + +*********************************************************************/ +#include +#include +#include +#include +#include +#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... +} diff --git a/Arduino/bee_scale_lora_31aug2018/bee_scale_lora_31aug2018.ino b/Arduino/bee_scale_lora_31aug2018/bee_scale_lora_31aug2018.ino new file mode 100644 index 0000000..29f35b5 --- /dev/null +++ b/Arduino/bee_scale_lora_31aug2018/bee_scale_lora_31aug2018.ino @@ -0,0 +1,697 @@ +/********************************************************************* + BeieliScale by nbit Informatik GmbH + +*********************************************************************/ +#include +#include +#include +#include +#include +#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... +} diff --git a/Arduino/bee_scale_lora_8sep2018/bee_scale_lora_8sep2018.ino b/Arduino/bee_scale_lora_8sep2018/bee_scale_lora_8sep2018.ino new file mode 100644 index 0000000..c91e74e --- /dev/null +++ b/Arduino/bee_scale_lora_8sep2018/bee_scale_lora_8sep2018.ino @@ -0,0 +1,698 @@ +/********************************************************************* + BeieliScale by nbit Informatik GmbH + +*********************************************************************/ +#include +#include +#include +#include +#include +#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????? +} diff --git a/Arduino/beescale_lora_13oct2018/beescale_lora_13oct2018.ino b/Arduino/beescale_lora_13oct2018/beescale_lora_13oct2018.ino new file mode 100644 index 0000000..f1253f2 --- /dev/null +++ b/Arduino/beescale_lora_13oct2018/beescale_lora_13oct2018.ino @@ -0,0 +1,796 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_17oct2018/beescale_lora_17oct2018.ino b/Arduino/beescale_lora_17oct2018/beescale_lora_17oct2018.ino new file mode 100644 index 0000000..5ea0ff7 --- /dev/null +++ b/Arduino/beescale_lora_17oct2018/beescale_lora_17oct2018.ino @@ -0,0 +1,796 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_18oct2018/beescale_lora_18oct2018.ino b/Arduino/beescale_lora_18oct2018/beescale_lora_18oct2018.ino new file mode 100644 index 0000000..38ac141 --- /dev/null +++ b/Arduino/beescale_lora_18oct2018/beescale_lora_18oct2018.ino @@ -0,0 +1,797 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_20oct2018/beescale_lora_20oct2018.ino b/Arduino/beescale_lora_20oct2018/beescale_lora_20oct2018.ino new file mode 100644 index 0000000..67cca03 --- /dev/null +++ b/Arduino/beescale_lora_20oct2018/beescale_lora_20oct2018.ino @@ -0,0 +1,797 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_26sep2018/beescale_lora_26sep2018.ino b/Arduino/beescale_lora_26sep2018/beescale_lora_26sep2018.ino new file mode 100644 index 0000000..0e2c4a1 --- /dev/null +++ b/Arduino/beescale_lora_26sep2018/beescale_lora_26sep2018.ino @@ -0,0 +1,672 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_27sep2018/beescale_lora_27sep2018.ino b/Arduino/beescale_lora_27sep2018/beescale_lora_27sep2018.ino new file mode 100644 index 0000000..984aaa0 --- /dev/null +++ b/Arduino/beescale_lora_27sep2018/beescale_lora_27sep2018.ino @@ -0,0 +1,674 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_29sep2018/beescale_lora_29sep2018.ino b/Arduino/beescale_lora_29sep2018/beescale_lora_29sep2018.ino new file mode 100644 index 0000000..09637de --- /dev/null +++ b/Arduino/beescale_lora_29sep2018/beescale_lora_29sep2018.ino @@ -0,0 +1,688 @@ +#include +#include +#include +#include "HX711.h" +#include "Adafruit_Si7021.h" +#include "Adafruit_FRAM_SPI.h" +#include + +// 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; + } + } +} diff --git a/Arduino/beescale_lora_3oct2018/beescale_lora_3oct2018.ino b/Arduino/beescale_lora_3oct2018/beescale_lora_3oct2018.ino new file mode 100644 index 0000000..d274846 --- /dev/null +++ b/Arduino/beescale_lora_3oct2018/beescale_lora_3oct2018.ino @@ -0,0 +1,711 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_4oct2018/beescale_lora_4oct2018.ino b/Arduino/beescale_lora_4oct2018/beescale_lora_4oct2018.ino new file mode 100644 index 0000000..50a2a73 --- /dev/null +++ b/Arduino/beescale_lora_4oct2018/beescale_lora_4oct2018.ino @@ -0,0 +1,715 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/beescale_lora_9oct2018/beescale_lora_9oct2018.ino b/Arduino/beescale_lora_9oct2018/beescale_lora_9oct2018.ino new file mode 100644 index 0000000..5f2600a --- /dev/null +++ b/Arduino/beescale_lora_9oct2018/beescale_lora_9oct2018.ino @@ -0,0 +1,723 @@ +#include +#include +#include +#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() { +} diff --git a/Arduino/low_power_tpl5111/low_power_tpl5111.ino b/Arduino/low_power_tpl5111/low_power_tpl5111.ino new file mode 100644 index 0000000..159835e --- /dev/null +++ b/Arduino/low_power_tpl5111/low_power_tpl5111.ino @@ -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() { +} diff --git a/Artwork/bee-logo-128.png b/Artwork/bee-logo-128.png new file mode 100644 index 0000000..bf89b8d Binary files /dev/null and b/Artwork/bee-logo-128.png differ diff --git a/Artwork/bee-logo-128.svg b/Artwork/bee-logo-128.svg new file mode 100644 index 0000000..a925f1e --- /dev/null +++ b/Artwork/bee-logo-128.svg @@ -0,0 +1,96 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/Artwork/bee-logo-28.png b/Artwork/bee-logo-28.png new file mode 100644 index 0000000..b707d72 Binary files /dev/null and b/Artwork/bee-logo-28.png differ diff --git a/Artwork/bee-logo-28.xcf b/Artwork/bee-logo-28.xcf new file mode 100644 index 0000000..6f88c8d Binary files /dev/null and b/Artwork/bee-logo-28.xcf differ diff --git a/Artwork/bee-logo-64.png b/Artwork/bee-logo-64.png new file mode 100644 index 0000000..22744fb Binary files /dev/null and b/Artwork/bee-logo-64.png differ diff --git a/Artwork/bee-logo-64.xcf b/Artwork/bee-logo-64.xcf new file mode 100644 index 0000000..b00cceb Binary files /dev/null and b/Artwork/bee-logo-64.xcf differ diff --git a/Artwork/bee-logo-favicon.svg b/Artwork/bee-logo-favicon.svg new file mode 100644 index 0000000..e794f7b --- /dev/null +++ b/Artwork/bee-logo-favicon.svg @@ -0,0 +1,93 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/Artwork/bee-logo-gehaeusedeckel.eps b/Artwork/bee-logo-gehaeusedeckel.eps new file mode 100644 index 0000000..1232a47 --- /dev/null +++ b/Artwork/bee-logo-gehaeusedeckel.eps @@ -0,0 +1,1189 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.15.12 (http://cairographics.org) +%%CreationDate: Tue Aug 7 18:34:57 2018 +%%Pages: 1 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%BoundingBox: 28 28 208 121 +%%EndComments +%%BeginProlog +50 dict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +/cairo_data_source { + CairoDataIndex CairoData length lt + { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } + { () } ifelse +} def +/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def +/cairo_image { image cairo_flush_ascii85_file } def +/cairo_imagemask { imagemask cairo_flush_ascii85_file } def +%%EndProlog +%%BeginSetup +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 28 28 208 121 +%%EndPageSetup +q 28 28 180 93 rectclip +1 0 0 -1 0 149 cm q +0.282353 0.215686 0.215686 rg +82.383 84.922 m 67.375 93.586 l 60.023 97.832 59.227 97.551 52.367 93.586 + c 44.863 89.254 l 37.355 84.922 l 30.008 80.68 29.852 79.848 29.852 71.926 + c 29.852 54.594 l 29.852 46.105 30.496 45.559 37.355 41.598 c 44.863 37.262 + l 52.367 32.93 l 59.715 28.688 60.512 28.969 67.375 32.93 c 74.879 37.262 + l 82.383 41.598 l 89.734 45.84 89.887 46.668 89.887 54.594 c 89.887 71.926 + l 89.887 80.41 89.246 80.961 82.383 84.922 c h +82.383 84.922 m f +3.012722 w +1 J +1 j +[] 0.0 d +4 M q 1 0 0 1 0 0 cm +82.383 84.922 m 67.375 93.586 l 60.023 97.832 59.227 97.551 52.367 93.586 + c 44.863 89.254 l 37.355 84.922 l 30.008 80.68 29.852 79.848 29.852 71.926 + c 29.852 54.594 l 29.852 46.105 30.496 45.559 37.355 41.598 c 44.863 37.262 + l 52.367 32.93 l 59.715 28.688 60.512 28.969 67.375 32.93 c 74.879 37.262 + l 82.383 41.598 l 89.734 45.84 89.887 46.668 89.887 54.594 c 89.887 71.926 + l 89.887 80.41 89.246 80.961 82.383 84.922 c h +82.383 84.922 m S Q +1 0.8 0 rg +8.278126 w +q 1 0 0 1 0 0 cm +43.355 55.277 m 48.305 53.184 53.746 52.027 59.461 52.027 c 65.172 52.027 + 70.613 53.184 75.566 55.277 c S Q +q 1 0 0 1 0 0 cm +47.824 65.859 m 51.398 64.344 55.332 63.508 59.461 63.508 c 63.586 63.508 + 67.52 64.344 71.094 65.859 c S Q +q 1 0 0 1 0 0 cm +52.301 76.453 m 54.5 75.52 56.918 75.008 59.461 75.008 c 62 75.008 64.418 + 75.52 66.621 76.453 c S Q +0.282353 0.215686 0.215686 rg +6.025443 w +0 j +q 1 0 0 1 0 0 cm +46.73 39.398 m 42.965 31.867 l S Q +q 1 0 0 1 0 0 cm +73.09 39.398 m 76.855 31.867 l S Q +56.16 117.547 m 57.332 107.828 l 58.52 107.828 l 57.973 112.359 l 58.336 + 111.84 58.738 111.445 59.176 111.172 c 59.613 110.902 60.055 110.766 60.504 + 110.766 c 60.793 110.766 61.051 110.824 61.27 110.938 c 61.496 111.043 +61.68 111.203 61.816 111.422 c 61.961 111.641 62.055 111.906 62.098 112.219 + c 62.148 112.531 62.152 112.887 62.113 113.281 c 61.613 117.547 l 60.41 + 117.547 l 60.91 113.281 l 60.973 112.762 60.934 112.375 60.801 112.125 +c 60.676 111.875 60.43 111.75 60.066 111.75 c 59.867 111.75 59.66 111.809 + 59.441 111.922 c 59.223 112.027 59.012 112.18 58.816 112.375 c 58.617 112.562 + 58.426 112.797 58.238 113.078 c 58.059 113.359 57.91 113.672 57.785 114.016 + c 57.363 117.547 l h +56.16 117.547 m f +0.31561 w +0 J +q 1 0 0 1 0 0 cm +56.16 117.547 m 57.332 107.828 l 58.52 107.828 l 57.973 112.359 l 58.336 + 111.84 58.738 111.445 59.176 111.172 c 59.613 110.902 60.055 110.766 60.504 + 110.766 c 60.793 110.766 61.051 110.824 61.27 110.938 c 61.496 111.043 +61.68 111.203 61.816 111.422 c 61.961 111.641 62.055 111.906 62.098 112.219 + c 62.148 112.531 62.152 112.887 62.113 113.281 c 61.613 117.547 l 60.41 + 117.547 l 60.91 113.281 l 60.973 112.762 60.934 112.375 60.801 112.125 +c 60.676 111.875 60.43 111.75 60.066 111.75 c 59.867 111.75 59.66 111.809 + 59.441 111.922 c 59.223 112.027 59.012 112.18 58.816 112.375 c 58.617 112.562 + 58.426 112.797 58.238 113.078 c 58.059 113.359 57.91 113.672 57.785 114.016 + c 57.363 117.547 l h +56.16 117.547 m S Q +65.309 117.656 m 64.797 117.656 64.422 117.512 64.184 117.219 c 63.941 +116.93 63.855 116.508 63.918 115.953 c 64.418 111.844 l 63.652 111.844 l + 63.578 111.844 63.52 111.824 63.48 111.781 c 63.438 111.73 63.422 111.664 + 63.434 111.578 c 63.496 111.125 l 64.574 110.984 l 65.105 108.906 l 65.125 + 108.844 65.156 108.793 65.199 108.75 c 65.25 108.711 65.312 108.688 65.387 + 108.688 c 65.965 108.688 l 65.684 111 l 68.746 111 l 69.277 108.906 l 69.285 + 108.844 69.316 108.793 69.371 108.75 c 69.422 108.711 69.484 108.688 69.559 + 108.688 c 70.152 108.688 l 69.871 111 l 71.746 111 l 71.637 111.844 l 69.762 + 111.844 l 69.262 115.875 l 69.23 116.156 69.27 116.367 69.387 116.5 c 69.5 + 116.637 69.66 116.703 69.871 116.703 c 69.996 116.703 70.098 116.688 70.184 + 116.656 c 70.277 116.625 70.355 116.59 70.418 116.547 c 70.488 116.508 +70.547 116.469 70.59 116.438 c 70.641 116.406 70.688 116.391 70.73 116.391 + c 70.762 116.391 70.785 116.402 70.809 116.422 c 70.828 116.434 70.848 +116.461 70.871 116.5 c 71.152 117.047 l 70.934 117.234 70.676 117.387 70.387 + 117.5 c 70.094 117.602 69.797 117.656 69.496 117.656 c 68.984 117.656 68.605 + 117.512 68.355 117.219 c 68.113 116.93 68.027 116.508 68.09 115.953 c 68.605 + 111.844 l 65.574 111.844 l 65.09 115.875 l 65.047 116.156 65.082 116.367 + 65.199 116.5 c 65.324 116.637 65.484 116.703 65.684 116.703 c 65.809 116.703 + 65.918 116.688 66.012 116.656 c 66.105 116.625 66.184 116.59 66.246 116.547 + c 66.316 116.508 66.375 116.469 66.418 116.438 c 66.469 116.406 66.516 +116.391 66.559 116.391 c 66.59 116.391 66.613 116.402 66.637 116.422 c 66.656 + 116.434 66.676 116.461 66.699 116.5 c 66.98 117.047 l 66.75 117.234 66.488 + 117.387 66.199 117.5 c 65.906 117.602 65.609 117.656 65.309 117.656 c h +65.309 117.656 m f +q 1 0 0 1 0 0 cm +65.309 117.656 m 64.797 117.656 64.422 117.512 64.184 117.219 c 63.941 +116.93 63.855 116.508 63.918 115.953 c 64.418 111.844 l 63.652 111.844 l + 63.578 111.844 63.52 111.824 63.48 111.781 c 63.438 111.73 63.422 111.664 + 63.434 111.578 c 63.496 111.125 l 64.574 110.984 l 65.105 108.906 l 65.125 + 108.844 65.156 108.793 65.199 108.75 c 65.25 108.711 65.312 108.688 65.387 + 108.688 c 65.965 108.688 l 65.684 111 l 68.746 111 l 69.277 108.906 l 69.285 + 108.844 69.316 108.793 69.371 108.75 c 69.422 108.711 69.484 108.688 69.559 + 108.688 c 70.152 108.688 l 69.871 111 l 71.746 111 l 71.637 111.844 l 69.762 + 111.844 l 69.262 115.875 l 69.23 116.156 69.27 116.367 69.387 116.5 c 69.5 + 116.637 69.66 116.703 69.871 116.703 c 69.996 116.703 70.098 116.688 70.184 + 116.656 c 70.277 116.625 70.355 116.59 70.418 116.547 c 70.488 116.508 +70.547 116.469 70.59 116.438 c 70.641 116.406 70.688 116.391 70.73 116.391 + c 70.762 116.391 70.785 116.402 70.809 116.422 c 70.828 116.434 70.848 +116.461 70.871 116.5 c 71.152 117.047 l 70.934 117.234 70.676 117.387 70.387 + 117.5 c 70.094 117.602 69.797 117.656 69.496 117.656 c 68.984 117.656 68.605 + 117.512 68.355 117.219 c 68.113 116.93 68.027 116.508 68.09 115.953 c 68.605 + 111.844 l 65.574 111.844 l 65.09 115.875 l 65.047 116.156 65.082 116.367 + 65.199 116.5 c 65.324 116.637 65.484 116.703 65.684 116.703 c 65.809 116.703 + 65.918 116.688 66.012 116.656 c 66.105 116.625 66.184 116.59 66.246 116.547 + c 66.316 116.508 66.375 116.469 66.418 116.438 c 66.469 116.406 66.516 +116.391 66.559 116.391 c 66.59 116.391 66.613 116.402 66.637 116.422 c 66.656 + 116.434 66.676 116.461 66.699 116.5 c 66.98 117.047 l 66.75 117.234 66.488 + 117.387 66.199 117.5 c 65.906 117.602 65.609 117.656 65.309 117.656 c h +65.309 117.656 m S Q +71.773 119.797 m 72.867 110.859 l 73.477 110.859 l 73.602 110.859 73.699 + 110.891 73.773 110.953 c 73.844 111.016 73.875 111.117 73.867 111.25 c +73.758 112.703 l 73.934 112.414 74.125 112.148 74.336 111.906 c 74.543 111.668 + 74.762 111.465 74.992 111.297 c 75.219 111.121 75.461 110.992 75.711 110.906 + c 75.961 110.812 76.215 110.766 76.477 110.766 c 77.09 110.766 77.562 110.977 + 77.898 111.391 c 78.23 111.809 78.398 112.418 78.398 113.219 c 78.398 113.586 + 78.355 113.949 78.273 114.312 c 78.199 114.668 78.09 115.012 77.945 115.344 + c 77.797 115.668 77.621 115.969 77.414 116.25 c 77.215 116.531 76.992 116.777 + 76.742 116.984 c 76.492 117.184 76.215 117.344 75.914 117.469 c 75.621 +117.582 75.312 117.641 74.992 117.641 c 74.648 117.641 74.328 117.578 74.039 + 117.453 c 73.746 117.32 73.5 117.133 73.305 116.891 c 72.945 119.797 l +h +76.039 111.719 m 75.809 111.719 75.578 111.789 75.352 111.922 c 75.121 +112.059 74.898 112.246 74.68 112.484 c 74.461 112.715 74.258 112.996 74.07 + 113.328 c 73.891 113.652 73.734 114.008 73.602 114.391 c 73.398 116.062 + l 73.586 116.305 73.805 116.477 74.055 116.578 c 74.305 116.672 74.559 +116.719 74.82 116.719 c 75.184 116.719 75.512 116.617 75.805 116.406 c 76.105 + 116.199 76.355 115.934 76.555 115.609 c 76.762 115.289 76.918 114.93 77.023 + 114.531 c 77.137 114.125 77.195 113.727 77.195 113.328 c 77.195 112.809 + 77.09 112.414 76.883 112.141 c 76.684 111.859 76.402 111.719 76.039 111.719 + c h +76.039 111.719 m f +q 1 0 0 1 0 0 cm +71.773 119.797 m 72.867 110.859 l 73.477 110.859 l 73.602 110.859 73.699 + 110.891 73.773 110.953 c 73.844 111.016 73.875 111.117 73.867 111.25 c +73.758 112.703 l 73.934 112.414 74.125 112.148 74.336 111.906 c 74.543 111.668 + 74.762 111.465 74.992 111.297 c 75.219 111.121 75.461 110.992 75.711 110.906 + c 75.961 110.812 76.215 110.766 76.477 110.766 c 77.09 110.766 77.562 110.977 + 77.898 111.391 c 78.23 111.809 78.398 112.418 78.398 113.219 c 78.398 113.586 + 78.355 113.949 78.273 114.312 c 78.199 114.668 78.09 115.012 77.945 115.344 + c 77.797 115.668 77.621 115.969 77.414 116.25 c 77.215 116.531 76.992 116.777 + 76.742 116.984 c 76.492 117.184 76.215 117.344 75.914 117.469 c 75.621 +117.582 75.312 117.641 74.992 117.641 c 74.648 117.641 74.328 117.578 74.039 + 117.453 c 73.746 117.32 73.5 117.133 73.305 116.891 c 72.945 119.797 l +h +76.039 111.719 m 75.809 111.719 75.578 111.789 75.352 111.922 c 75.121 +112.059 74.898 112.246 74.68 112.484 c 74.461 112.715 74.258 112.996 74.07 + 113.328 c 73.891 113.652 73.734 114.008 73.602 114.391 c 73.398 116.062 + l 73.586 116.305 73.805 116.477 74.055 116.578 c 74.305 116.672 74.559 +116.719 74.82 116.719 c 75.184 116.719 75.512 116.617 75.805 116.406 c 76.105 + 116.199 76.355 115.934 76.555 115.609 c 76.762 115.289 76.918 114.93 77.023 + 114.531 c 77.137 114.125 77.195 113.727 77.195 113.328 c 77.195 112.809 + 77.09 112.414 76.883 112.141 c 76.684 111.859 76.402 111.719 76.039 111.719 + c h +76.039 111.719 m S Q +83.707 111.938 m 83.676 111.992 83.637 112.031 83.598 112.062 c 83.566 +112.086 83.52 112.094 83.457 112.094 c 83.395 112.094 83.32 112.07 83.238 + 112.016 c 83.164 111.965 83.07 111.914 82.957 111.859 c 82.852 111.797 +82.723 111.746 82.566 111.703 c 82.41 111.652 82.215 111.625 81.988 111.625 + c 81.77 111.625 81.57 111.652 81.395 111.703 c 81.227 111.758 81.082 111.836 + 80.957 111.938 c 80.832 112.031 80.73 112.141 80.66 112.266 c 80.586 112.391 + 80.551 112.527 80.551 112.672 c 80.551 112.82 80.59 112.953 80.676 113.078 + c 80.758 113.195 80.867 113.297 81.004 113.391 c 81.148 113.484 81.309 +113.574 81.488 113.656 c 81.664 113.73 81.848 113.809 82.035 113.891 c 82.223 + 113.977 82.402 114.062 82.582 114.156 c 82.758 114.242 82.918 114.34 83.066 + 114.453 c 83.211 114.559 83.324 114.684 83.41 114.828 c 83.492 114.977 +83.535 115.141 83.535 115.328 c 83.535 115.641 83.465 115.938 83.332 116.219 + c 83.207 116.492 83.023 116.734 82.785 116.953 c 82.555 117.164 82.27 117.336 + 81.926 117.469 c 81.59 117.594 81.223 117.656 80.816 117.656 c 80.355 117.656 + 79.957 117.582 79.613 117.438 c 79.27 117.281 78.988 117.078 78.77 116.828 + c 79.066 116.391 l 79.137 116.266 79.242 116.203 79.379 116.203 c 79.449 + 116.203 79.523 116.234 79.598 116.297 c 79.668 116.359 79.762 116.43 79.879 + 116.5 c 79.992 116.574 80.133 116.641 80.301 116.703 c 80.477 116.766 80.695 + 116.797 80.957 116.797 c 81.195 116.797 81.402 116.766 81.582 116.703 c + 81.77 116.641 81.926 116.559 82.051 116.453 c 82.184 116.34 82.289 116.211 + 82.363 116.062 c 82.434 115.918 82.473 115.766 82.473 115.609 c 82.473 +115.383 82.398 115.199 82.254 115.062 c 82.105 114.918 81.918 114.793 81.691 + 114.688 c 81.473 114.586 81.23 114.492 80.973 114.406 c 80.723 114.312 +80.48 114.203 80.254 114.078 c 80.035 113.953 79.852 113.805 79.707 113.625 + c 79.559 113.449 79.488 113.219 79.488 112.938 c 79.488 112.656 79.543 +112.387 79.66 112.125 c 79.785 111.867 79.957 111.637 80.176 111.438 c 80.395 + 111.23 80.66 111.07 80.973 110.953 c 81.293 110.828 81.652 110.766 82.051 + 110.766 c 82.477 110.766 82.852 110.836 83.176 110.969 c 83.496 111.094 + 83.773 111.277 84.004 111.516 c h +83.707 111.938 m f +q 1 0 0 1 0 0 cm +83.707 111.938 m 83.676 111.992 83.637 112.031 83.598 112.062 c 83.566 +112.086 83.52 112.094 83.457 112.094 c 83.395 112.094 83.32 112.07 83.238 + 112.016 c 83.164 111.965 83.07 111.914 82.957 111.859 c 82.852 111.797 +82.723 111.746 82.566 111.703 c 82.41 111.652 82.215 111.625 81.988 111.625 + c 81.77 111.625 81.57 111.652 81.395 111.703 c 81.227 111.758 81.082 111.836 + 80.957 111.938 c 80.832 112.031 80.73 112.141 80.66 112.266 c 80.586 112.391 + 80.551 112.527 80.551 112.672 c 80.551 112.82 80.59 112.953 80.676 113.078 + c 80.758 113.195 80.867 113.297 81.004 113.391 c 81.148 113.484 81.309 +113.574 81.488 113.656 c 81.664 113.73 81.848 113.809 82.035 113.891 c 82.223 + 113.977 82.402 114.062 82.582 114.156 c 82.758 114.242 82.918 114.34 83.066 + 114.453 c 83.211 114.559 83.324 114.684 83.41 114.828 c 83.492 114.977 +83.535 115.141 83.535 115.328 c 83.535 115.641 83.465 115.938 83.332 116.219 + c 83.207 116.492 83.023 116.734 82.785 116.953 c 82.555 117.164 82.27 117.336 + 81.926 117.469 c 81.59 117.594 81.223 117.656 80.816 117.656 c 80.355 117.656 + 79.957 117.582 79.613 117.438 c 79.27 117.281 78.988 117.078 78.77 116.828 + c 79.066 116.391 l 79.137 116.266 79.242 116.203 79.379 116.203 c 79.449 + 116.203 79.523 116.234 79.598 116.297 c 79.668 116.359 79.762 116.43 79.879 + 116.5 c 79.992 116.574 80.133 116.641 80.301 116.703 c 80.477 116.766 80.695 + 116.797 80.957 116.797 c 81.195 116.797 81.402 116.766 81.582 116.703 c + 81.77 116.641 81.926 116.559 82.051 116.453 c 82.184 116.34 82.289 116.211 + 82.363 116.062 c 82.434 115.918 82.473 115.766 82.473 115.609 c 82.473 +115.383 82.398 115.199 82.254 115.062 c 82.105 114.918 81.918 114.793 81.691 + 114.688 c 81.473 114.586 81.23 114.492 80.973 114.406 c 80.723 114.312 +80.48 114.203 80.254 114.078 c 80.035 113.953 79.852 113.805 79.707 113.625 + c 79.559 113.449 79.488 113.219 79.488 112.938 c 79.488 112.656 79.543 +112.387 79.66 112.125 c 79.785 111.867 79.957 111.637 80.176 111.438 c 80.395 + 111.23 80.66 111.07 80.973 110.953 c 81.293 110.828 81.652 110.766 82.051 + 110.766 c 82.477 110.766 82.852 110.836 83.176 110.969 c 83.496 111.094 + 83.773 111.277 84.004 111.516 c h +83.707 111.938 m S Q +84.805 117.547 m h +86.492 116.812 m 86.492 116.93 86.469 117.039 86.43 117.141 c 86.387 117.246 + 86.324 117.336 86.242 117.406 c 86.156 117.48 86.062 117.539 85.961 117.578 + c 85.867 117.629 85.762 117.656 85.648 117.656 c 85.531 117.656 85.422 +117.629 85.32 117.578 c 85.227 117.539 85.137 117.48 85.055 117.406 c 84.98 + 117.336 84.918 117.246 84.867 117.141 c 84.824 117.039 84.805 116.93 84.805 + 116.812 c 84.805 116.699 84.824 116.59 84.867 116.484 c 84.918 116.383 +84.98 116.293 85.055 116.219 c 85.137 116.137 85.227 116.074 85.32 116.031 + c 85.422 115.98 85.531 115.953 85.648 115.953 c 85.762 115.953 85.871 115.98 + 85.977 116.031 c 86.078 116.074 86.168 116.137 86.242 116.219 c 86.324 +116.293 86.387 116.383 86.43 116.484 c 86.469 116.59 86.492 116.699 86.492 + 116.812 c h +87.055 112.172 m 87.055 112.289 87.031 112.398 86.992 112.5 c 86.949 112.605 + 86.887 112.695 86.805 112.766 c 86.719 112.84 86.625 112.902 86.523 112.953 + c 86.43 112.996 86.324 113.016 86.211 113.016 c 86.094 113.016 85.984 112.996 + 85.883 112.953 c 85.789 112.902 85.699 112.84 85.617 112.766 c 85.543 112.695 + 85.48 112.605 85.43 112.5 c 85.387 112.398 85.367 112.289 85.367 112.172 + c 85.367 112.059 85.387 111.949 85.43 111.844 c 85.48 111.742 85.543 111.652 + 85.617 111.578 c 85.699 111.496 85.789 111.434 85.883 111.391 c 85.984 +111.34 86.094 111.312 86.211 111.312 c 86.324 111.312 86.434 111.34 86.539 + 111.391 c 86.641 111.434 86.73 111.496 86.805 111.578 c 86.887 111.652 +86.949 111.742 86.992 111.844 c 87.031 111.949 87.055 112.059 87.055 112.172 + c h +87.055 112.172 m f +q 1 0 0 1 0 0 cm +84.805 117.547 m 86.492 116.812 m 86.492 116.93 86.469 117.039 86.43 117.141 + c 86.387 117.246 86.324 117.336 86.242 117.406 c 86.156 117.48 86.062 117.539 + 85.961 117.578 c 85.867 117.629 85.762 117.656 85.648 117.656 c 85.531 +117.656 85.422 117.629 85.32 117.578 c 85.227 117.539 85.137 117.48 85.055 + 117.406 c 84.98 117.336 84.918 117.246 84.867 117.141 c 84.824 117.039 +84.805 116.93 84.805 116.812 c 84.805 116.699 84.824 116.59 84.867 116.484 + c 84.918 116.383 84.98 116.293 85.055 116.219 c 85.137 116.137 85.227 116.074 + 85.32 116.031 c 85.422 115.98 85.531 115.953 85.648 115.953 c 85.762 115.953 + 85.871 115.98 85.977 116.031 c 86.078 116.074 86.168 116.137 86.242 116.219 + c 86.324 116.293 86.387 116.383 86.43 116.484 c 86.469 116.59 86.492 116.699 + 86.492 116.812 c h +87.055 112.172 m 87.055 112.289 87.031 112.398 86.992 112.5 c 86.949 112.605 + 86.887 112.695 86.805 112.766 c 86.719 112.84 86.625 112.902 86.523 112.953 + c 86.43 112.996 86.324 113.016 86.211 113.016 c 86.094 113.016 85.984 112.996 + 85.883 112.953 c 85.789 112.902 85.699 112.84 85.617 112.766 c 85.543 112.695 + 85.48 112.605 85.43 112.5 c 85.387 112.398 85.367 112.289 85.367 112.172 + c 85.367 112.059 85.387 111.949 85.43 111.844 c 85.48 111.742 85.543 111.652 + 85.617 111.578 c 85.699 111.496 85.789 111.434 85.883 111.391 c 85.984 +111.34 86.094 111.312 86.211 111.312 c 86.324 111.312 86.434 111.34 86.539 + 111.391 c 86.641 111.434 86.73 111.496 86.805 111.578 c 86.887 111.652 +86.949 111.742 86.992 111.844 c 87.031 111.949 87.055 112.059 87.055 112.172 + c h +87.055 112.172 m S Q +88.441 117.703 m 88.367 117.848 88.27 117.957 88.145 118.031 c 88.02 118.113 + 87.895 118.156 87.77 118.156 c 87.27 118.156 l 92.832 108.406 l 92.895 +108.273 92.98 108.168 93.098 108.094 c 93.223 108.012 93.355 107.969 93.504 + 107.969 c 93.988 107.969 l h +88.441 117.703 m f +q 1 0 0 1 0 0 cm +88.441 117.703 m 88.367 117.848 88.27 117.957 88.145 118.031 c 88.02 118.113 + 87.895 118.156 87.77 118.156 c 87.27 118.156 l 92.832 108.406 l 92.895 +108.273 92.98 108.168 93.098 108.094 c 93.223 108.012 93.355 107.969 93.504 + 107.969 c 93.988 107.969 l h +88.441 117.703 m S Q +93.883 117.703 m 93.809 117.848 93.711 117.957 93.586 118.031 c 93.461 +118.113 93.336 118.156 93.211 118.156 c 92.711 118.156 l 98.273 108.406 +l 98.336 108.273 98.422 108.168 98.539 108.094 c 98.664 108.012 98.797 107.969 + 98.945 107.969 c 99.43 107.969 l h +93.883 117.703 m f +q 1 0 0 1 0 0 cm +93.883 117.703 m 93.809 117.848 93.711 117.957 93.586 118.031 c 93.461 +118.113 93.336 118.156 93.211 118.156 c 92.711 118.156 l 98.273 108.406 +l 98.336 108.273 98.422 108.168 98.539 108.094 c 98.664 108.012 98.797 107.969 + 98.945 107.969 c 99.43 107.969 l h +93.883 117.703 m S Q +99.277 110.859 m 100.168 110.859 l 100.262 110.859 100.332 110.887 100.387 + 110.938 c 100.449 110.98 100.488 111.039 100.512 111.109 c 101.199 115.375 + l 101.219 115.531 101.234 115.684 101.246 115.828 c 101.266 115.977 101.277 + 116.121 101.277 116.266 c 101.328 116.121 101.387 115.977 101.449 115.828 + c 101.512 115.684 101.574 115.531 101.637 115.375 c 103.59 111.078 l 103.621 + 111.016 103.66 110.965 103.715 110.922 c 103.777 110.871 103.844 110.844 + 103.918 110.844 c 104.418 110.844 l 104.512 110.844 104.582 110.871 104.637 + 110.922 c 104.688 110.965 104.719 111.016 104.73 111.078 c 105.559 115.375 + l 105.59 115.531 105.613 115.684 105.637 115.828 c 105.668 115.977 105.688 + 116.121 105.699 116.266 c 105.738 116.121 105.781 115.98 105.824 115.844 + c 105.875 115.699 105.934 115.547 105.996 115.391 c 107.809 111.109 l 107.84 + 111.039 107.887 110.98 107.949 110.938 c 108.012 110.887 108.078 110.859 + 108.152 110.859 c 109.027 110.859 l 106.09 117.547 l 105.168 117.547 l +105.062 117.547 104.996 117.477 104.965 117.328 c 104.043 112.812 l 104.02 + 112.68 104 112.539 103.98 112.391 c 103.957 112.465 103.934 112.539 103.902 + 112.609 c 103.879 112.684 103.855 112.758 103.824 112.828 c 101.746 117.328 + l 101.684 117.477 101.594 117.547 101.48 117.547 c 100.605 117.547 l h +99.277 110.859 m f +q 1 0 0 1 0 0 cm +99.277 110.859 m 100.168 110.859 l 100.262 110.859 100.332 110.887 100.387 + 110.938 c 100.449 110.98 100.488 111.039 100.512 111.109 c 101.199 115.375 + l 101.219 115.531 101.234 115.684 101.246 115.828 c 101.266 115.977 101.277 + 116.121 101.277 116.266 c 101.328 116.121 101.387 115.977 101.449 115.828 + c 101.512 115.684 101.574 115.531 101.637 115.375 c 103.59 111.078 l 103.621 + 111.016 103.66 110.965 103.715 110.922 c 103.777 110.871 103.844 110.844 + 103.918 110.844 c 104.418 110.844 l 104.512 110.844 104.582 110.871 104.637 + 110.922 c 104.688 110.965 104.719 111.016 104.73 111.078 c 105.559 115.375 + l 105.59 115.531 105.613 115.684 105.637 115.828 c 105.668 115.977 105.688 + 116.121 105.699 116.266 c 105.738 116.121 105.781 115.98 105.824 115.844 + c 105.875 115.699 105.934 115.547 105.996 115.391 c 107.809 111.109 l 107.84 + 111.039 107.887 110.98 107.949 110.938 c 108.012 110.887 108.078 110.859 + 108.152 110.859 c 109.027 110.859 l 106.09 117.547 l 105.168 117.547 l +105.062 117.547 104.996 117.477 104.965 117.328 c 104.043 112.812 l 104.02 + 112.68 104 112.539 103.98 112.391 c 103.957 112.465 103.934 112.539 103.902 + 112.609 c 103.879 112.684 103.855 112.758 103.824 112.828 c 101.746 117.328 + l 101.684 117.477 101.594 117.547 101.48 117.547 c 100.605 117.547 l h +99.277 110.859 m S Q +109.312 110.859 m 110.203 110.859 l 110.297 110.859 110.367 110.887 110.422 + 110.938 c 110.484 110.98 110.523 111.039 110.547 111.109 c 111.234 115.375 + l 111.254 115.531 111.27 115.684 111.281 115.828 c 111.301 115.977 111.312 + 116.121 111.312 116.266 c 111.363 116.121 111.422 115.977 111.484 115.828 + c 111.547 115.684 111.609 115.531 111.672 115.375 c 113.625 111.078 l 113.656 + 111.016 113.695 110.965 113.75 110.922 c 113.812 110.871 113.879 110.844 + 113.953 110.844 c 114.453 110.844 l 114.547 110.844 114.617 110.871 114.672 + 110.922 c 114.723 110.965 114.754 111.016 114.766 111.078 c 115.594 115.375 + l 115.625 115.531 115.648 115.684 115.672 115.828 c 115.703 115.977 115.723 + 116.121 115.734 116.266 c 115.773 116.121 115.816 115.98 115.859 115.844 + c 115.91 115.699 115.969 115.547 116.031 115.391 c 117.844 111.109 l 117.875 + 111.039 117.922 110.98 117.984 110.938 c 118.047 110.887 118.113 110.859 + 118.188 110.859 c 119.062 110.859 l 116.125 117.547 l 115.203 117.547 l + 115.098 117.547 115.031 117.477 115 117.328 c 114.078 112.812 l 114.055 + 112.68 114.035 112.539 114.016 112.391 c 113.992 112.465 113.969 112.539 + 113.938 112.609 c 113.914 112.684 113.891 112.758 113.859 112.828 c 111.781 + 117.328 l 111.719 117.477 111.629 117.547 111.516 117.547 c 110.641 117.547 + l h +109.312 110.859 m f +q 1 0 0 1 0 0 cm +109.312 110.859 m 110.203 110.859 l 110.297 110.859 110.367 110.887 110.422 + 110.938 c 110.484 110.98 110.523 111.039 110.547 111.109 c 111.234 115.375 + l 111.254 115.531 111.27 115.684 111.281 115.828 c 111.301 115.977 111.312 + 116.121 111.312 116.266 c 111.363 116.121 111.422 115.977 111.484 115.828 + c 111.547 115.684 111.609 115.531 111.672 115.375 c 113.625 111.078 l 113.656 + 111.016 113.695 110.965 113.75 110.922 c 113.812 110.871 113.879 110.844 + 113.953 110.844 c 114.453 110.844 l 114.547 110.844 114.617 110.871 114.672 + 110.922 c 114.723 110.965 114.754 111.016 114.766 111.078 c 115.594 115.375 + l 115.625 115.531 115.648 115.684 115.672 115.828 c 115.703 115.977 115.723 + 116.121 115.734 116.266 c 115.773 116.121 115.816 115.98 115.859 115.844 + c 115.91 115.699 115.969 115.547 116.031 115.391 c 117.844 111.109 l 117.875 + 111.039 117.922 110.98 117.984 110.938 c 118.047 110.887 118.113 110.859 + 118.188 110.859 c 119.062 110.859 l 116.125 117.547 l 115.203 117.547 l + 115.098 117.547 115.031 117.477 115 117.328 c 114.078 112.812 l 114.055 + 112.68 114.035 112.539 114.016 112.391 c 113.992 112.465 113.969 112.539 + 113.938 112.609 c 113.914 112.684 113.891 112.758 113.859 112.828 c 111.781 + 117.328 l 111.719 117.477 111.629 117.547 111.516 117.547 c 110.641 117.547 + l h +109.312 110.859 m S Q +119.348 110.859 m 120.238 110.859 l 120.332 110.859 120.402 110.887 120.457 + 110.938 c 120.52 110.98 120.559 111.039 120.582 111.109 c 121.27 115.375 + l 121.289 115.531 121.305 115.684 121.316 115.828 c 121.336 115.977 121.348 + 116.121 121.348 116.266 c 121.398 116.121 121.457 115.977 121.52 115.828 + c 121.582 115.684 121.645 115.531 121.707 115.375 c 123.66 111.078 l 123.691 + 111.016 123.73 110.965 123.785 110.922 c 123.848 110.871 123.914 110.844 + 123.988 110.844 c 124.488 110.844 l 124.582 110.844 124.652 110.871 124.707 + 110.922 c 124.758 110.965 124.789 111.016 124.801 111.078 c 125.629 115.375 + l 125.66 115.531 125.684 115.684 125.707 115.828 c 125.738 115.977 125.758 + 116.121 125.77 116.266 c 125.809 116.121 125.852 115.98 125.895 115.844 + c 125.945 115.699 126.004 115.547 126.066 115.391 c 127.879 111.109 l 127.91 + 111.039 127.957 110.98 128.02 110.938 c 128.082 110.887 128.148 110.859 + 128.223 110.859 c 129.098 110.859 l 126.16 117.547 l 125.238 117.547 l +125.133 117.547 125.066 117.477 125.035 117.328 c 124.113 112.812 l 124.09 + 112.68 124.07 112.539 124.051 112.391 c 124.027 112.465 124.004 112.539 + 123.973 112.609 c 123.949 112.684 123.926 112.758 123.895 112.828 c 121.816 + 117.328 l 121.754 117.477 121.664 117.547 121.551 117.547 c 120.676 117.547 + l h +119.348 110.859 m f +q 1 0 0 1 0 0 cm +119.348 110.859 m 120.238 110.859 l 120.332 110.859 120.402 110.887 120.457 + 110.938 c 120.52 110.98 120.559 111.039 120.582 111.109 c 121.27 115.375 + l 121.289 115.531 121.305 115.684 121.316 115.828 c 121.336 115.977 121.348 + 116.121 121.348 116.266 c 121.398 116.121 121.457 115.977 121.52 115.828 + c 121.582 115.684 121.645 115.531 121.707 115.375 c 123.66 111.078 l 123.691 + 111.016 123.73 110.965 123.785 110.922 c 123.848 110.871 123.914 110.844 + 123.988 110.844 c 124.488 110.844 l 124.582 110.844 124.652 110.871 124.707 + 110.922 c 124.758 110.965 124.789 111.016 124.801 111.078 c 125.629 115.375 + l 125.66 115.531 125.684 115.684 125.707 115.828 c 125.738 115.977 125.758 + 116.121 125.77 116.266 c 125.809 116.121 125.852 115.98 125.895 115.844 + c 125.945 115.699 126.004 115.547 126.066 115.391 c 127.879 111.109 l 127.91 + 111.039 127.957 110.98 128.02 110.938 c 128.082 110.887 128.148 110.859 + 128.223 110.859 c 129.098 110.859 l 126.16 117.547 l 125.238 117.547 l +125.133 117.547 125.066 117.477 125.035 117.328 c 124.113 112.812 l 124.09 + 112.68 124.07 112.539 124.051 112.391 c 124.027 112.465 124.004 112.539 + 123.973 112.609 c 123.949 112.684 123.926 112.758 123.895 112.828 c 121.816 + 117.328 l 121.754 117.477 121.664 117.547 121.551 117.547 c 120.676 117.547 + l h +119.348 110.859 m S Q +128.836 117.547 m h +130.523 116.812 m 130.523 116.93 130.5 117.039 130.461 117.141 c 130.418 + 117.246 130.355 117.336 130.273 117.406 c 130.188 117.48 130.094 117.539 + 129.992 117.578 c 129.898 117.629 129.793 117.656 129.68 117.656 c 129.562 + 117.656 129.453 117.629 129.352 117.578 c 129.258 117.539 129.168 117.48 + 129.086 117.406 c 129.012 117.336 128.949 117.246 128.898 117.141 c 128.855 + 117.039 128.836 116.93 128.836 116.812 c 128.836 116.699 128.855 116.59 + 128.898 116.484 c 128.949 116.383 129.012 116.293 129.086 116.219 c 129.168 + 116.137 129.258 116.074 129.352 116.031 c 129.453 115.98 129.562 115.953 + 129.68 115.953 c 129.793 115.953 129.902 115.98 130.008 116.031 c 130.109 + 116.074 130.199 116.137 130.273 116.219 c 130.355 116.293 130.418 116.383 + 130.461 116.484 c 130.5 116.59 130.523 116.699 130.523 116.812 c h +130.523 116.812 m f +q 1 0 0 1 0 0 cm +128.836 117.547 m 130.523 116.812 m 130.523 116.93 130.5 117.039 130.461 + 117.141 c 130.418 117.246 130.355 117.336 130.273 117.406 c 130.188 117.48 + 130.094 117.539 129.992 117.578 c 129.898 117.629 129.793 117.656 129.68 + 117.656 c 129.562 117.656 129.453 117.629 129.352 117.578 c 129.258 117.539 + 129.168 117.48 129.086 117.406 c 129.012 117.336 128.949 117.246 128.898 + 117.141 c 128.855 117.039 128.836 116.93 128.836 116.812 c 128.836 116.699 + 128.855 116.59 128.898 116.484 c 128.949 116.383 129.012 116.293 129.086 + 116.219 c 129.168 116.137 129.258 116.074 129.352 116.031 c 129.453 115.98 + 129.562 115.953 129.68 115.953 c 129.793 115.953 129.902 115.98 130.008 + 116.031 c 130.109 116.074 130.199 116.137 130.273 116.219 c 130.355 116.293 + 130.418 116.383 130.461 116.484 c 130.5 116.59 130.523 116.699 130.523 +116.812 c h +130.523 116.812 m S Q +132.27 117.547 m 133.066 110.859 l 133.66 110.859 l 133.793 110.859 133.895 + 110.891 133.957 110.953 c 134.027 111.016 134.059 111.117 134.051 111.25 + c 133.973 112.5 l 134.316 111.93 134.699 111.496 135.129 111.203 c 135.555 + 110.914 136.008 110.766 136.488 110.766 c 136.977 110.766 137.336 110.922 + 137.566 111.234 c 137.793 111.547 137.91 112 137.91 112.594 c 138.254 111.969 + 138.648 111.512 139.098 111.219 c 139.543 110.918 140.02 110.766 140.52 + 110.766 c 141.133 110.766 141.566 110.984 141.816 111.422 c 142.074 111.859 + 142.152 112.48 142.051 113.281 c 141.551 117.547 l 140.363 117.547 l 140.879 + 113.281 l 140.91 113.023 140.918 112.797 140.91 112.609 c 140.898 112.422 + 140.863 112.266 140.801 112.141 c 140.746 112.008 140.664 111.906 140.551 + 111.844 c 140.434 111.781 140.289 111.75 140.113 111.75 c 139.902 111.75 + 139.695 111.805 139.488 111.906 c 139.277 112 139.074 112.141 138.879 112.328 + c 138.691 112.516 138.512 112.75 138.348 113.031 c 138.18 113.305 138.035 + 113.617 137.91 113.969 c 137.488 117.547 l 136.316 117.547 l 136.816 113.281 + l 136.848 113.023 136.855 112.797 136.848 112.609 c 136.848 112.422 136.82 + 112.266 136.77 112.141 c 136.715 112.008 136.633 111.906 136.52 111.844 + c 136.402 111.781 136.258 111.75 136.082 111.75 c 135.852 111.75 135.629 + 111.809 135.41 111.922 c 135.191 112.027 134.988 112.184 134.801 112.391 + c 134.613 112.59 134.434 112.836 134.27 113.125 c 134.113 113.418 133.965 + 113.75 133.832 114.125 c 133.441 117.547 l h +132.27 117.547 m f +q 1 0 0 1 0 0 cm +132.27 117.547 m 133.066 110.859 l 133.66 110.859 l 133.793 110.859 133.895 + 110.891 133.957 110.953 c 134.027 111.016 134.059 111.117 134.051 111.25 + c 133.973 112.5 l 134.316 111.93 134.699 111.496 135.129 111.203 c 135.555 + 110.914 136.008 110.766 136.488 110.766 c 136.977 110.766 137.336 110.922 + 137.566 111.234 c 137.793 111.547 137.91 112 137.91 112.594 c 138.254 111.969 + 138.648 111.512 139.098 111.219 c 139.543 110.918 140.02 110.766 140.52 + 110.766 c 141.133 110.766 141.566 110.984 141.816 111.422 c 142.074 111.859 + 142.152 112.48 142.051 113.281 c 141.551 117.547 l 140.363 117.547 l 140.879 + 113.281 l 140.91 113.023 140.918 112.797 140.91 112.609 c 140.898 112.422 + 140.863 112.266 140.801 112.141 c 140.746 112.008 140.664 111.906 140.551 + 111.844 c 140.434 111.781 140.289 111.75 140.113 111.75 c 139.902 111.75 + 139.695 111.805 139.488 111.906 c 139.277 112 139.074 112.141 138.879 112.328 + c 138.691 112.516 138.512 112.75 138.348 113.031 c 138.18 113.305 138.035 + 113.617 137.91 113.969 c 137.488 117.547 l 136.316 117.547 l 136.816 113.281 + l 136.848 113.023 136.855 112.797 136.848 112.609 c 136.848 112.422 136.82 + 112.266 136.77 112.141 c 136.715 112.008 136.633 111.906 136.52 111.844 + c 136.402 111.781 136.258 111.75 136.082 111.75 c 135.852 111.75 135.629 + 111.809 135.41 111.922 c 135.191 112.027 134.988 112.184 134.801 112.391 + c 134.613 112.59 134.434 112.836 134.27 113.125 c 134.113 113.418 133.965 + 113.75 133.832 114.125 c 133.441 117.547 l h +132.27 117.547 m S Q +145.422 110.859 m 144.609 117.547 l 143.422 117.547 l 144.234 110.859 l + h +145.859 108.766 m 145.859 108.883 145.832 108.992 145.781 109.094 c 145.738 + 109.188 145.676 109.277 145.594 109.359 c 145.52 109.434 145.43 109.496 + 145.328 109.547 c 145.234 109.59 145.133 109.609 145.031 109.609 c 144.926 + 109.609 144.82 109.59 144.719 109.547 c 144.625 109.496 144.539 109.434 + 144.469 109.359 c 144.395 109.277 144.332 109.188 144.281 109.094 c 144.238 + 108.992 144.219 108.883 144.219 108.766 c 144.219 108.652 144.238 108.543 + 144.281 108.438 c 144.332 108.336 144.395 108.246 144.469 108.172 c 144.539 + 108.09 144.625 108.027 144.719 107.984 c 144.82 107.945 144.926 107.922 + 145.031 107.922 c 145.133 107.922 145.238 107.945 145.344 107.984 c 145.445 + 108.027 145.535 108.09 145.609 108.172 c 145.68 108.246 145.738 108.336 + 145.781 108.438 c 145.832 108.543 145.859 108.652 145.859 108.766 c h +145.859 108.766 m f +q 1 0 0 1 0 0 cm +145.422 110.859 m 144.609 117.547 l 143.422 117.547 l 144.234 110.859 l + h +145.859 108.766 m 145.859 108.883 145.832 108.992 145.781 109.094 c 145.738 + 109.188 145.676 109.277 145.594 109.359 c 145.52 109.434 145.43 109.496 + 145.328 109.547 c 145.234 109.59 145.133 109.609 145.031 109.609 c 144.926 + 109.609 144.82 109.59 144.719 109.547 c 144.625 109.496 144.539 109.434 + 144.469 109.359 c 144.395 109.277 144.332 109.188 144.281 109.094 c 144.238 + 108.992 144.219 108.883 144.219 108.766 c 144.219 108.652 144.238 108.543 + 144.281 108.438 c 144.332 108.336 144.395 108.246 144.469 108.172 c 144.539 + 108.09 144.625 108.027 144.719 107.984 c 144.82 107.945 144.926 107.922 + 145.031 107.922 c 145.133 107.922 145.238 107.945 145.344 107.984 c 145.445 + 108.027 145.535 108.09 145.609 108.172 c 145.68 108.246 145.738 108.336 + 145.781 108.438 c 145.832 108.543 145.859 108.652 145.859 108.766 c h +145.859 108.766 m S Q +146.539 117.547 m 147.336 110.859 l 147.93 110.859 l 148.055 110.859 148.152 + 110.891 148.227 110.953 c 148.297 111.016 148.328 111.117 148.32 111.25 + c 148.227 112.562 l 148.59 111.969 149.008 111.523 149.477 111.219 c 149.945 + 110.918 150.422 110.766 150.914 110.766 c 151.203 110.766 151.461 110.824 + 151.68 110.938 c 151.906 111.043 152.09 111.203 152.227 111.422 c 152.371 + 111.641 152.469 111.906 152.523 112.219 c 152.574 112.531 152.574 112.887 + 152.523 113.281 c 152.023 117.547 l 150.836 117.547 l 151.336 113.281 l + 151.398 112.762 151.359 112.375 151.227 112.125 c 151.09 111.875 150.84 + 111.75 150.477 111.75 c 150.258 111.75 150.031 111.809 149.805 111.922 +c 149.586 112.039 149.367 112.203 149.148 112.422 c 148.938 112.633 148.742 + 112.887 148.555 113.188 c 148.375 113.48 148.227 113.809 148.102 114.172 + c 147.727 117.547 l h +146.539 117.547 m f +q 1 0 0 1 0 0 cm +146.539 117.547 m 147.336 110.859 l 147.93 110.859 l 148.055 110.859 148.152 + 110.891 148.227 110.953 c 148.297 111.016 148.328 111.117 148.32 111.25 + c 148.227 112.562 l 148.59 111.969 149.008 111.523 149.477 111.219 c 149.945 + 110.918 150.422 110.766 150.914 110.766 c 151.203 110.766 151.461 110.824 + 151.68 110.938 c 151.906 111.043 152.09 111.203 152.227 111.422 c 152.371 + 111.641 152.469 111.906 152.523 112.219 c 152.574 112.531 152.574 112.887 + 152.523 113.281 c 152.023 117.547 l 150.836 117.547 l 151.336 113.281 l + 151.398 112.762 151.359 112.375 151.227 112.125 c 151.09 111.875 150.84 + 111.75 150.477 111.75 c 150.258 111.75 150.031 111.809 149.805 111.922 +c 149.586 112.039 149.367 112.203 149.148 112.422 c 148.938 112.633 148.742 + 112.887 148.555 113.188 c 148.375 113.48 148.227 113.809 148.102 114.172 + c 147.727 117.547 l h +146.539 117.547 m S Q +155.84 110.859 m 155.027 117.547 l 153.84 117.547 l 154.652 110.859 l h +156.277 108.766 m 156.277 108.883 156.25 108.992 156.199 109.094 c 156.156 + 109.188 156.094 109.277 156.012 109.359 c 155.938 109.434 155.848 109.496 + 155.746 109.547 c 155.652 109.59 155.551 109.609 155.449 109.609 c 155.344 + 109.609 155.238 109.59 155.137 109.547 c 155.043 109.496 154.957 109.434 + 154.887 109.359 c 154.812 109.277 154.75 109.188 154.699 109.094 c 154.656 + 108.992 154.637 108.883 154.637 108.766 c 154.637 108.652 154.656 108.543 + 154.699 108.438 c 154.75 108.336 154.812 108.246 154.887 108.172 c 154.957 + 108.09 155.043 108.027 155.137 107.984 c 155.238 107.945 155.344 107.922 + 155.449 107.922 c 155.551 107.922 155.656 107.945 155.762 107.984 c 155.863 + 108.027 155.953 108.09 156.027 108.172 c 156.098 108.246 156.156 108.336 + 156.199 108.438 c 156.25 108.543 156.277 108.652 156.277 108.766 c h +156.277 108.766 m f +q 1 0 0 1 0 0 cm +155.84 110.859 m 155.027 117.547 l 153.84 117.547 l 154.652 110.859 l h +156.277 108.766 m 156.277 108.883 156.25 108.992 156.199 109.094 c 156.156 + 109.188 156.094 109.277 156.012 109.359 c 155.938 109.434 155.848 109.496 + 155.746 109.547 c 155.652 109.59 155.551 109.609 155.449 109.609 c 155.344 + 109.609 155.238 109.59 155.137 109.547 c 155.043 109.496 154.957 109.434 + 154.887 109.359 c 154.812 109.277 154.75 109.188 154.699 109.094 c 154.656 + 108.992 154.637 108.883 154.637 108.766 c 154.637 108.652 154.656 108.543 + 154.699 108.438 c 154.75 108.336 154.812 108.246 154.887 108.172 c 154.957 + 108.09 155.043 108.027 155.137 107.984 c 155.238 107.945 155.344 107.922 + 155.449 107.922 c 155.551 107.922 155.656 107.945 155.762 107.984 c 155.863 + 108.027 155.953 108.09 156.027 108.172 c 156.098 108.246 156.156 108.336 + 156.199 108.438 c 156.25 108.543 156.277 108.652 156.277 108.766 c h +156.277 108.766 m S Q +157.238 113.109 m 160.27 113.109 l 160.145 114.094 l 157.113 114.094 l +h +157.238 113.109 m f +q 1 0 0 1 0 0 cm +157.238 113.109 m 160.27 113.109 l 160.145 114.094 l 157.113 114.094 l +h +157.238 113.109 m S Q +161.297 117.547 m 162.484 107.828 l 163.656 107.828 l 163.078 112.562 l + 163.242 112.293 163.426 112.047 163.625 111.828 c 163.832 111.602 164.047 + 111.406 164.266 111.25 c 164.484 111.094 164.711 110.977 164.953 110.891 + c 165.191 110.809 165.43 110.766 165.672 110.766 c 166.266 110.766 166.727 + 110.977 167.062 111.391 c 167.395 111.809 167.562 112.418 167.562 113.219 + c 167.562 113.586 167.52 113.949 167.438 114.312 c 167.363 114.668 167.254 + 115.012 167.109 115.344 c 166.973 115.668 166.801 115.969 166.594 116.25 + c 166.395 116.531 166.172 116.777 165.922 116.984 c 165.68 117.184 165.414 + 117.344 165.125 117.469 c 164.832 117.582 164.523 117.641 164.203 117.641 + c 163.836 117.641 163.504 117.566 163.203 117.422 c 162.898 117.266 162.648 + 117.047 162.453 116.766 c 162.344 117.203 l 162.32 117.32 162.281 117.406 + 162.219 117.469 c 162.164 117.523 162.07 117.547 161.938 117.547 c h +165.234 111.719 m 165.004 111.719 164.773 111.789 164.547 111.922 c 164.328 + 112.059 164.113 112.246 163.906 112.484 c 163.695 112.715 163.5 112.992 + 163.312 113.312 c 163.133 113.637 162.984 113.992 162.859 114.375 c 162.641 + 116.062 l 162.816 116.305 163.023 116.477 163.266 116.578 c 163.516 116.672 + 163.77 116.719 164.031 116.719 c 164.383 116.719 164.707 116.617 165 116.406 + c 165.289 116.199 165.535 115.934 165.734 115.609 c 165.941 115.289 166.098 + 114.93 166.203 114.531 c 166.316 114.125 166.375 113.727 166.375 113.328 + c 166.375 112.809 166.273 112.414 166.078 112.141 c 165.879 111.859 165.598 + 111.719 165.234 111.719 c h +165.234 111.719 m f +q 1 0 0 1 0 0 cm +161.297 117.547 m 162.484 107.828 l 163.656 107.828 l 163.078 112.562 l + 163.242 112.293 163.426 112.047 163.625 111.828 c 163.832 111.602 164.047 + 111.406 164.266 111.25 c 164.484 111.094 164.711 110.977 164.953 110.891 + c 165.191 110.809 165.43 110.766 165.672 110.766 c 166.266 110.766 166.727 + 110.977 167.062 111.391 c 167.395 111.809 167.562 112.418 167.562 113.219 + c 167.562 113.586 167.52 113.949 167.438 114.312 c 167.363 114.668 167.254 + 115.012 167.109 115.344 c 166.973 115.668 166.801 115.969 166.594 116.25 + c 166.395 116.531 166.172 116.777 165.922 116.984 c 165.68 117.184 165.414 + 117.344 165.125 117.469 c 164.832 117.582 164.523 117.641 164.203 117.641 + c 163.836 117.641 163.504 117.566 163.203 117.422 c 162.898 117.266 162.648 + 117.047 162.453 116.766 c 162.344 117.203 l 162.32 117.32 162.281 117.406 + 162.219 117.469 c 162.164 117.523 162.07 117.547 161.938 117.547 c h +165.234 111.719 m 165.004 111.719 164.773 111.789 164.547 111.922 c 164.328 + 112.059 164.113 112.246 163.906 112.484 c 163.695 112.715 163.5 112.992 + 163.312 113.312 c 163.133 113.637 162.984 113.992 162.859 114.375 c 162.641 + 116.062 l 162.816 116.305 163.023 116.477 163.266 116.578 c 163.516 116.672 + 163.77 116.719 164.031 116.719 c 164.383 116.719 164.707 116.617 165 116.406 + c 165.289 116.199 165.535 115.934 165.734 115.609 c 165.941 115.289 166.098 + 114.93 166.203 114.531 c 166.316 114.125 166.375 113.727 166.375 113.328 + c 166.375 112.809 166.273 112.414 166.078 112.141 c 165.879 111.859 165.598 + 111.719 165.234 111.719 c h +165.234 111.719 m S Q +174.238 112.328 m 174.238 112.621 174.168 112.887 174.035 113.125 c 173.91 + 113.367 173.668 113.586 173.316 113.781 c 172.973 113.969 172.492 114.137 + 171.879 114.281 c 171.262 114.418 170.477 114.531 169.52 114.625 c 169.52 + 114.656 169.52 114.695 169.52 114.734 c 169.52 114.777 169.52 114.812 169.52 + 114.844 c 169.52 115.449 169.664 115.914 169.957 116.234 c 170.246 116.547 + 170.691 116.703 171.285 116.703 c 171.648 116.703 171.949 116.656 172.191 + 116.562 c 172.43 116.469 172.633 116.371 172.801 116.266 c 172.977 116.164 + 173.121 116.062 173.238 115.969 c 173.363 115.875 173.48 115.828 173.598 + 115.828 c 173.68 115.828 173.758 115.871 173.832 115.953 c 174.129 116.312 + l 173.879 116.543 173.633 116.742 173.395 116.906 c 173.164 117.074 172.93 + 117.215 172.691 117.328 c 172.449 117.434 172.195 117.512 171.926 117.562 + c 171.664 117.613 171.379 117.641 171.066 117.641 c 170.648 117.641 170.27 + 117.578 169.926 117.453 c 169.59 117.32 169.305 117.133 169.066 116.891 + c 168.836 116.652 168.66 116.367 168.535 116.031 c 168.41 115.688 168.348 + 115.305 168.348 114.875 c 168.348 114.531 168.383 114.195 168.457 113.859 + c 168.539 113.527 168.652 113.211 168.801 112.906 c 168.957 112.594 169.137 + 112.312 169.348 112.062 c 169.566 111.805 169.816 111.578 170.098 111.391 + c 170.379 111.195 170.684 111.043 171.02 110.938 c 171.352 110.824 171.711 + 110.766 172.098 110.766 c 172.473 110.766 172.793 110.82 173.066 110.922 + c 173.336 111.016 173.559 111.141 173.738 111.297 c 173.914 111.445 174.039 + 111.609 174.113 111.797 c 174.195 111.984 174.238 112.164 174.238 112.328 + c h +172.051 111.625 m 171.727 111.625 171.434 111.684 171.176 111.797 c 170.914 + 111.914 170.68 112.07 170.473 112.266 c 170.273 112.465 170.102 112.699 + 169.957 112.969 c 169.809 113.242 169.699 113.527 169.629 113.828 c 170.137 + 113.777 170.582 113.719 170.957 113.656 c 171.332 113.586 171.648 113.512 + 171.91 113.438 c 172.18 113.367 172.395 113.289 172.551 113.203 c 172.715 + 113.121 172.848 113.031 172.941 112.938 c 173.035 112.844 173.098 112.75 + 173.129 112.656 c 173.16 112.562 173.176 112.465 173.176 112.359 c 173.176 + 112.277 173.152 112.195 173.113 112.109 c 173.07 112.027 173.004 111.949 + 172.91 111.875 c 172.824 111.805 172.711 111.746 172.566 111.703 c 172.418 + 111.652 172.246 111.625 172.051 111.625 c h +172.051 111.625 m f +q 1 0 0 1 0 0 cm +174.238 112.328 m 174.238 112.621 174.168 112.887 174.035 113.125 c 173.91 + 113.367 173.668 113.586 173.316 113.781 c 172.973 113.969 172.492 114.137 + 171.879 114.281 c 171.262 114.418 170.477 114.531 169.52 114.625 c 169.52 + 114.656 169.52 114.695 169.52 114.734 c 169.52 114.777 169.52 114.812 169.52 + 114.844 c 169.52 115.449 169.664 115.914 169.957 116.234 c 170.246 116.547 + 170.691 116.703 171.285 116.703 c 171.648 116.703 171.949 116.656 172.191 + 116.562 c 172.43 116.469 172.633 116.371 172.801 116.266 c 172.977 116.164 + 173.121 116.062 173.238 115.969 c 173.363 115.875 173.48 115.828 173.598 + 115.828 c 173.68 115.828 173.758 115.871 173.832 115.953 c 174.129 116.312 + l 173.879 116.543 173.633 116.742 173.395 116.906 c 173.164 117.074 172.93 + 117.215 172.691 117.328 c 172.449 117.434 172.195 117.512 171.926 117.562 + c 171.664 117.613 171.379 117.641 171.066 117.641 c 170.648 117.641 170.27 + 117.578 169.926 117.453 c 169.59 117.32 169.305 117.133 169.066 116.891 + c 168.836 116.652 168.66 116.367 168.535 116.031 c 168.41 115.688 168.348 + 115.305 168.348 114.875 c 168.348 114.531 168.383 114.195 168.457 113.859 + c 168.539 113.527 168.652 113.211 168.801 112.906 c 168.957 112.594 169.137 + 112.312 169.348 112.062 c 169.566 111.805 169.816 111.578 170.098 111.391 + c 170.379 111.195 170.684 111.043 171.02 110.938 c 171.352 110.824 171.711 + 110.766 172.098 110.766 c 172.473 110.766 172.793 110.82 173.066 110.922 + c 173.336 111.016 173.559 111.141 173.738 111.297 c 173.914 111.445 174.039 + 111.609 174.113 111.797 c 174.195 111.984 174.238 112.164 174.238 112.328 + c h +172.051 111.625 m 171.727 111.625 171.434 111.684 171.176 111.797 c 170.914 + 111.914 170.68 112.07 170.473 112.266 c 170.273 112.465 170.102 112.699 + 169.957 112.969 c 169.809 113.242 169.699 113.527 169.629 113.828 c 170.137 + 113.777 170.582 113.719 170.957 113.656 c 171.332 113.586 171.648 113.512 + 171.91 113.438 c 172.18 113.367 172.395 113.289 172.551 113.203 c 172.715 + 113.121 172.848 113.031 172.941 112.938 c 173.035 112.844 173.098 112.75 + 173.129 112.656 c 173.16 112.562 173.176 112.465 173.176 112.359 c 173.176 + 112.277 173.152 112.195 173.113 112.109 c 173.07 112.027 173.004 111.949 + 172.91 111.875 c 172.824 111.805 172.711 111.746 172.566 111.703 c 172.418 + 111.652 172.246 111.625 172.051 111.625 c h +172.051 111.625 m S Q +177.25 110.859 m 176.438 117.547 l 175.25 117.547 l 176.062 110.859 l h +177.688 108.766 m 177.688 108.883 177.66 108.992 177.609 109.094 c 177.566 + 109.188 177.504 109.277 177.422 109.359 c 177.348 109.434 177.258 109.496 + 177.156 109.547 c 177.062 109.59 176.961 109.609 176.859 109.609 c 176.754 + 109.609 176.648 109.59 176.547 109.547 c 176.453 109.496 176.367 109.434 + 176.297 109.359 c 176.223 109.277 176.16 109.188 176.109 109.094 c 176.066 + 108.992 176.047 108.883 176.047 108.766 c 176.047 108.652 176.066 108.543 + 176.109 108.438 c 176.16 108.336 176.223 108.246 176.297 108.172 c 176.367 + 108.09 176.453 108.027 176.547 107.984 c 176.648 107.945 176.754 107.922 + 176.859 107.922 c 176.961 107.922 177.066 107.945 177.172 107.984 c 177.273 + 108.027 177.363 108.09 177.438 108.172 c 177.508 108.246 177.566 108.336 + 177.609 108.438 c 177.66 108.543 177.688 108.652 177.688 108.766 c h +177.688 108.766 m f +q 1 0 0 1 0 0 cm +177.25 110.859 m 176.438 117.547 l 175.25 117.547 l 176.062 110.859 l h +177.688 108.766 m 177.688 108.883 177.66 108.992 177.609 109.094 c 177.566 + 109.188 177.504 109.277 177.422 109.359 c 177.348 109.434 177.258 109.496 + 177.156 109.547 c 177.062 109.59 176.961 109.609 176.859 109.609 c 176.754 + 109.609 176.648 109.59 176.547 109.547 c 176.453 109.496 176.367 109.434 + 176.297 109.359 c 176.223 109.277 176.16 109.188 176.109 109.094 c 176.066 + 108.992 176.047 108.883 176.047 108.766 c 176.047 108.652 176.066 108.543 + 176.109 108.438 c 176.16 108.336 176.223 108.246 176.297 108.172 c 176.367 + 108.09 176.453 108.027 176.547 107.984 c 176.648 107.945 176.754 107.922 + 176.859 107.922 c 176.961 107.922 177.066 107.945 177.172 107.984 c 177.273 + 108.027 177.363 108.09 177.438 108.172 c 177.508 108.246 177.566 108.336 + 177.609 108.438 c 177.66 108.543 177.688 108.652 177.688 108.766 c h +177.688 108.766 m S Q +184.164 112.328 m 184.164 112.621 184.094 112.887 183.961 113.125 c 183.836 + 113.367 183.594 113.586 183.242 113.781 c 182.898 113.969 182.418 114.137 + 181.805 114.281 c 181.188 114.418 180.402 114.531 179.445 114.625 c 179.445 + 114.656 179.445 114.695 179.445 114.734 c 179.445 114.777 179.445 114.812 + 179.445 114.844 c 179.445 115.449 179.59 115.914 179.883 116.234 c 180.172 + 116.547 180.617 116.703 181.211 116.703 c 181.574 116.703 181.875 116.656 + 182.117 116.562 c 182.355 116.469 182.559 116.371 182.727 116.266 c 182.902 + 116.164 183.047 116.062 183.164 115.969 c 183.289 115.875 183.406 115.828 + 183.523 115.828 c 183.605 115.828 183.684 115.871 183.758 115.953 c 184.055 + 116.312 l 183.805 116.543 183.559 116.742 183.32 116.906 c 183.09 117.074 + 182.855 117.215 182.617 117.328 c 182.375 117.434 182.121 117.512 181.852 + 117.562 c 181.59 117.613 181.305 117.641 180.992 117.641 c 180.574 117.641 + 180.195 117.578 179.852 117.453 c 179.516 117.32 179.23 117.133 178.992 + 116.891 c 178.762 116.652 178.586 116.367 178.461 116.031 c 178.336 115.688 + 178.273 115.305 178.273 114.875 c 178.273 114.531 178.309 114.195 178.383 + 113.859 c 178.465 113.527 178.578 113.211 178.727 112.906 c 178.883 112.594 + 179.062 112.312 179.273 112.062 c 179.492 111.805 179.742 111.578 180.023 + 111.391 c 180.305 111.195 180.609 111.043 180.945 110.938 c 181.277 110.824 + 181.637 110.766 182.023 110.766 c 182.398 110.766 182.719 110.82 182.992 + 110.922 c 183.262 111.016 183.484 111.141 183.664 111.297 c 183.84 111.445 + 183.965 111.609 184.039 111.797 c 184.121 111.984 184.164 112.164 184.164 + 112.328 c h +181.977 111.625 m 181.652 111.625 181.359 111.684 181.102 111.797 c 180.84 + 111.914 180.605 112.07 180.398 112.266 c 180.199 112.465 180.027 112.699 + 179.883 112.969 c 179.734 113.242 179.625 113.527 179.555 113.828 c 180.062 + 113.777 180.508 113.719 180.883 113.656 c 181.258 113.586 181.574 113.512 + 181.836 113.438 c 182.105 113.367 182.32 113.289 182.477 113.203 c 182.641 + 113.121 182.773 113.031 182.867 112.938 c 182.961 112.844 183.023 112.75 + 183.055 112.656 c 183.086 112.562 183.102 112.465 183.102 112.359 c 183.102 + 112.277 183.078 112.195 183.039 112.109 c 182.996 112.027 182.93 111.949 + 182.836 111.875 c 182.75 111.805 182.637 111.746 182.492 111.703 c 182.344 + 111.652 182.172 111.625 181.977 111.625 c h +181.977 111.625 m f +q 1 0 0 1 0 0 cm +184.164 112.328 m 184.164 112.621 184.094 112.887 183.961 113.125 c 183.836 + 113.367 183.594 113.586 183.242 113.781 c 182.898 113.969 182.418 114.137 + 181.805 114.281 c 181.188 114.418 180.402 114.531 179.445 114.625 c 179.445 + 114.656 179.445 114.695 179.445 114.734 c 179.445 114.777 179.445 114.812 + 179.445 114.844 c 179.445 115.449 179.59 115.914 179.883 116.234 c 180.172 + 116.547 180.617 116.703 181.211 116.703 c 181.574 116.703 181.875 116.656 + 182.117 116.562 c 182.355 116.469 182.559 116.371 182.727 116.266 c 182.902 + 116.164 183.047 116.062 183.164 115.969 c 183.289 115.875 183.406 115.828 + 183.523 115.828 c 183.605 115.828 183.684 115.871 183.758 115.953 c 184.055 + 116.312 l 183.805 116.543 183.559 116.742 183.32 116.906 c 183.09 117.074 + 182.855 117.215 182.617 117.328 c 182.375 117.434 182.121 117.512 181.852 + 117.562 c 181.59 117.613 181.305 117.641 180.992 117.641 c 180.574 117.641 + 180.195 117.578 179.852 117.453 c 179.516 117.32 179.23 117.133 178.992 + 116.891 c 178.762 116.652 178.586 116.367 178.461 116.031 c 178.336 115.688 + 178.273 115.305 178.273 114.875 c 178.273 114.531 178.309 114.195 178.383 + 113.859 c 178.465 113.527 178.578 113.211 178.727 112.906 c 178.883 112.594 + 179.062 112.312 179.273 112.062 c 179.492 111.805 179.742 111.578 180.023 + 111.391 c 180.305 111.195 180.609 111.043 180.945 110.938 c 181.277 110.824 + 181.637 110.766 182.023 110.766 c 182.398 110.766 182.719 110.82 182.992 + 110.922 c 183.262 111.016 183.484 111.141 183.664 111.297 c 183.84 111.445 + 183.965 111.609 184.039 111.797 c 184.121 111.984 184.164 112.164 184.164 + 112.328 c h +181.977 111.625 m 181.652 111.625 181.359 111.684 181.102 111.797 c 180.84 + 111.914 180.605 112.07 180.398 112.266 c 180.199 112.465 180.027 112.699 + 179.883 112.969 c 179.734 113.242 179.625 113.527 179.555 113.828 c 180.062 + 113.777 180.508 113.719 180.883 113.656 c 181.258 113.586 181.574 113.512 + 181.836 113.438 c 182.105 113.367 182.32 113.289 182.477 113.203 c 182.641 + 113.121 182.773 113.031 182.867 112.938 c 182.961 112.844 183.023 112.75 + 183.055 112.656 c 183.086 112.562 183.102 112.465 183.102 112.359 c 183.102 + 112.277 183.078 112.195 183.039 112.109 c 182.996 112.027 182.93 111.949 + 182.836 111.875 c 182.75 111.805 182.637 111.746 182.492 111.703 c 182.344 + 111.652 182.172 111.625 181.977 111.625 c h +181.977 111.625 m S Q +185.176 117.547 m 186.332 107.828 l 187.52 107.828 l 186.348 117.547 l +h +185.176 117.547 m f +q 1 0 0 1 0 0 cm +185.176 117.547 m 186.332 107.828 l 187.52 107.828 l 186.348 117.547 l +h +185.176 117.547 m S Q +190.402 110.859 m 189.59 117.547 l 188.402 117.547 l 189.215 110.859 l +h +190.84 108.766 m 190.84 108.883 190.812 108.992 190.762 109.094 c 190.719 + 109.188 190.656 109.277 190.574 109.359 c 190.5 109.434 190.41 109.496 +190.309 109.547 c 190.215 109.59 190.113 109.609 190.012 109.609 c 189.906 + 109.609 189.801 109.59 189.699 109.547 c 189.605 109.496 189.52 109.434 + 189.449 109.359 c 189.375 109.277 189.312 109.188 189.262 109.094 c 189.219 + 108.992 189.199 108.883 189.199 108.766 c 189.199 108.652 189.219 108.543 + 189.262 108.438 c 189.312 108.336 189.375 108.246 189.449 108.172 c 189.52 + 108.09 189.605 108.027 189.699 107.984 c 189.801 107.945 189.906 107.922 + 190.012 107.922 c 190.113 107.922 190.219 107.945 190.324 107.984 c 190.426 + 108.027 190.516 108.09 190.59 108.172 c 190.66 108.246 190.719 108.336 +190.762 108.438 c 190.812 108.543 190.84 108.652 190.84 108.766 c h +190.84 108.766 m f +q 1 0 0 1 0 0 cm +190.402 110.859 m 189.59 117.547 l 188.402 117.547 l 189.215 110.859 l +h +190.84 108.766 m 190.84 108.883 190.812 108.992 190.762 109.094 c 190.719 + 109.188 190.656 109.277 190.574 109.359 c 190.5 109.434 190.41 109.496 +190.309 109.547 c 190.215 109.59 190.113 109.609 190.012 109.609 c 189.906 + 109.609 189.801 109.59 189.699 109.547 c 189.605 109.496 189.52 109.434 + 189.449 109.359 c 189.375 109.277 189.312 109.188 189.262 109.094 c 189.219 + 108.992 189.199 108.883 189.199 108.766 c 189.199 108.652 189.219 108.543 + 189.262 108.438 c 189.312 108.336 189.375 108.246 189.449 108.172 c 189.52 + 108.09 189.605 108.027 189.699 107.984 c 189.801 107.945 189.906 107.922 + 190.012 107.922 c 190.113 107.922 190.219 107.945 190.324 107.984 c 190.426 + 108.027 190.516 108.09 190.59 108.172 c 190.66 108.246 190.719 108.336 +190.762 108.438 c 190.812 108.543 190.84 108.652 190.84 108.766 c h +190.84 108.766 m S Q +191.645 117.547 m h +193.332 116.812 m 193.332 116.93 193.309 117.039 193.27 117.141 c 193.227 + 117.246 193.164 117.336 193.082 117.406 c 192.996 117.48 192.902 117.539 + 192.801 117.578 c 192.707 117.629 192.602 117.656 192.488 117.656 c 192.371 + 117.656 192.262 117.629 192.16 117.578 c 192.066 117.539 191.977 117.48 + 191.895 117.406 c 191.82 117.336 191.758 117.246 191.707 117.141 c 191.664 + 117.039 191.645 116.93 191.645 116.812 c 191.645 116.699 191.664 116.59 + 191.707 116.484 c 191.758 116.383 191.82 116.293 191.895 116.219 c 191.977 + 116.137 192.066 116.074 192.16 116.031 c 192.262 115.98 192.371 115.953 + 192.488 115.953 c 192.602 115.953 192.711 115.98 192.816 116.031 c 192.918 + 116.074 193.008 116.137 193.082 116.219 c 193.164 116.293 193.227 116.383 + 193.27 116.484 c 193.309 116.59 193.332 116.699 193.332 116.812 c h +193.332 116.812 m f +q 1 0 0 1 0 0 cm +191.645 117.547 m 193.332 116.812 m 193.332 116.93 193.309 117.039 193.27 + 117.141 c 193.227 117.246 193.164 117.336 193.082 117.406 c 192.996 117.48 + 192.902 117.539 192.801 117.578 c 192.707 117.629 192.602 117.656 192.488 + 117.656 c 192.371 117.656 192.262 117.629 192.16 117.578 c 192.066 117.539 + 191.977 117.48 191.895 117.406 c 191.82 117.336 191.758 117.246 191.707 + 117.141 c 191.664 117.039 191.645 116.93 191.645 116.812 c 191.645 116.699 + 191.664 116.59 191.707 116.484 c 191.758 116.383 191.82 116.293 191.895 + 116.219 c 191.977 116.137 192.066 116.074 192.16 116.031 c 192.262 115.98 + 192.371 115.953 192.488 115.953 c 192.602 115.953 192.711 115.98 192.816 + 116.031 c 192.918 116.074 193.008 116.137 193.082 116.219 c 193.164 116.293 + 193.227 116.383 193.27 116.484 c 193.309 116.59 193.332 116.699 193.332 + 116.812 c h +193.332 116.812 m S Q +200.062 116.344 m 199.82 116.594 199.598 116.805 199.391 116.969 c 199.18 + 117.137 198.969 117.273 198.75 117.375 c 198.539 117.469 198.32 117.539 + 198.094 117.578 c 197.875 117.617 197.633 117.641 197.375 117.641 c 196.977 + 117.641 196.629 117.57 196.328 117.438 c 196.023 117.305 195.77 117.117 + 195.562 116.875 c 195.363 116.637 195.207 116.352 195.094 116.016 c 194.988 + 115.672 194.938 115.297 194.938 114.891 c 194.938 114.359 195.02 113.844 + 195.188 113.344 c 195.363 112.844 195.602 112.406 195.906 112.031 c 196.219 + 111.648 196.586 111.34 197.016 111.109 c 197.441 110.883 197.91 110.766 + 198.422 110.766 c 198.867 110.766 199.242 110.844 199.547 111 c 199.859 + 111.156 200.129 111.387 200.359 111.688 c 199.984 112.141 l 199.953 112.172 + 199.914 112.203 199.875 112.234 c 199.832 112.258 199.789 112.266 199.75 + 112.266 c 199.688 112.266 199.625 112.242 199.562 112.188 c 199.5 112.125 + 199.422 112.062 199.328 112 c 199.234 111.93 199.109 111.867 198.953 111.812 + c 198.805 111.762 198.617 111.734 198.391 111.734 c 198.086 111.734 197.801 + 111.82 197.531 111.984 c 197.258 112.141 197.02 112.359 196.812 112.641 + c 196.602 112.922 196.438 113.262 196.312 113.656 c 196.195 114.043 196.141 + 114.461 196.141 114.906 c 196.141 115.18 196.172 115.422 196.234 115.641 + c 196.305 115.859 196.398 116.055 196.516 116.219 c 196.641 116.375 196.797 + 116.496 196.984 116.578 c 197.172 116.664 197.383 116.703 197.625 116.703 + c 197.926 116.703 198.176 116.664 198.375 116.578 c 198.582 116.484 198.754 + 116.387 198.891 116.281 c 199.023 116.18 199.141 116.086 199.234 116 c +199.336 115.906 199.438 115.859 199.531 115.859 c 199.613 115.859 199.688 + 115.902 199.75 115.984 c h +200.062 116.344 m f +q 1 0 0 1 0 0 cm +200.062 116.344 m 199.82 116.594 199.598 116.805 199.391 116.969 c 199.18 + 117.137 198.969 117.273 198.75 117.375 c 198.539 117.469 198.32 117.539 + 198.094 117.578 c 197.875 117.617 197.633 117.641 197.375 117.641 c 196.977 + 117.641 196.629 117.57 196.328 117.438 c 196.023 117.305 195.77 117.117 + 195.562 116.875 c 195.363 116.637 195.207 116.352 195.094 116.016 c 194.988 + 115.672 194.938 115.297 194.938 114.891 c 194.938 114.359 195.02 113.844 + 195.188 113.344 c 195.363 112.844 195.602 112.406 195.906 112.031 c 196.219 + 111.648 196.586 111.34 197.016 111.109 c 197.441 110.883 197.91 110.766 + 198.422 110.766 c 198.867 110.766 199.242 110.844 199.547 111 c 199.859 + 111.156 200.129 111.387 200.359 111.688 c 199.984 112.141 l 199.953 112.172 + 199.914 112.203 199.875 112.234 c 199.832 112.258 199.789 112.266 199.75 + 112.266 c 199.688 112.266 199.625 112.242 199.562 112.188 c 199.5 112.125 + 199.422 112.062 199.328 112 c 199.234 111.93 199.109 111.867 198.953 111.812 + c 198.805 111.762 198.617 111.734 198.391 111.734 c 198.086 111.734 197.801 + 111.82 197.531 111.984 c 197.258 112.141 197.02 112.359 196.812 112.641 + c 196.602 112.922 196.438 113.262 196.312 113.656 c 196.195 114.043 196.141 + 114.461 196.141 114.906 c 196.141 115.18 196.172 115.422 196.234 115.641 + c 196.305 115.859 196.398 116.055 196.516 116.219 c 196.641 116.375 196.797 + 116.496 196.984 116.578 c 197.172 116.664 197.383 116.703 197.625 116.703 + c 197.926 116.703 198.176 116.664 198.375 116.578 c 198.582 116.484 198.754 + 116.387 198.891 116.281 c 199.023 116.18 199.141 116.086 199.234 116 c +199.336 115.906 199.438 115.859 199.531 115.859 c 199.613 115.859 199.688 + 115.902 199.75 115.984 c h +200.062 116.344 m S Q +200.918 117.547 m 202.09 107.828 l 203.277 107.828 l 202.73 112.359 l 203.094 + 111.84 203.496 111.445 203.934 111.172 c 204.371 110.902 204.812 110.766 + 205.262 110.766 c 205.551 110.766 205.809 110.824 206.027 110.938 c 206.254 + 111.043 206.438 111.203 206.574 111.422 c 206.719 111.641 206.812 111.906 + 206.855 112.219 c 206.906 112.531 206.91 112.887 206.871 113.281 c 206.371 + 117.547 l 205.168 117.547 l 205.668 113.281 l 205.73 112.762 205.691 112.375 + 205.559 112.125 c 205.434 111.875 205.188 111.75 204.824 111.75 c 204.625 + 111.75 204.418 111.809 204.199 111.922 c 203.98 112.027 203.77 112.18 203.574 + 112.375 c 203.375 112.562 203.184 112.797 202.996 113.078 c 202.816 113.359 + 202.668 113.672 202.543 114.016 c 202.121 117.547 l h +200.918 117.547 m f +q 1 0 0 1 0 0 cm +200.918 117.547 m 202.09 107.828 l 203.277 107.828 l 202.73 112.359 l 203.094 + 111.84 203.496 111.445 203.934 111.172 c 204.371 110.902 204.812 110.766 + 205.262 110.766 c 205.551 110.766 205.809 110.824 206.027 110.938 c 206.254 + 111.043 206.438 111.203 206.574 111.422 c 206.719 111.641 206.812 111.906 + 206.855 112.219 c 206.906 112.531 206.91 112.887 206.871 113.281 c 206.371 + 117.547 l 205.168 117.547 l 205.668 113.281 l 205.73 112.762 205.691 112.375 + 205.559 112.125 c 205.434 111.875 205.188 111.75 204.824 111.75 c 204.625 + 111.75 204.418 111.809 204.199 111.922 c 203.98 112.027 203.77 112.18 203.574 + 112.375 c 203.375 112.562 203.184 112.797 202.996 113.078 c 202.816 113.359 + 202.668 113.672 202.543 114.016 c 202.121 117.547 l h +200.918 117.547 m S Q +102.41 98.219 m 104.301 82.656 l 109.551 82.656 l 110.457 82.656 111.23 + 82.742 111.879 82.906 c 112.523 83.074 113.051 83.312 113.457 83.625 c +113.871 83.938 114.176 84.32 114.363 84.766 c 114.559 85.203 114.66 85.695 + 114.66 86.234 c 114.66 86.672 114.598 87.094 114.473 87.5 c 114.348 87.898 + 114.16 88.266 113.91 88.609 c 113.66 88.945 113.336 89.25 112.941 89.531 + c 112.555 89.805 112.098 90.027 111.566 90.203 c 113.324 90.652 114.207 + 91.648 114.207 93.188 c 114.207 93.898 114.066 94.559 113.785 95.172 c +113.512 95.789 113.117 96.32 112.598 96.766 c 112.074 97.215 111.434 97.57 + 110.676 97.828 c 109.926 98.09 109.082 98.219 108.145 98.219 c h +106.207 91.422 m 105.66 95.922 l 108.363 95.922 l 108.871 95.922 109.309 + 95.855 109.676 95.719 c 110.051 95.586 110.352 95.402 110.582 95.172 c +110.82 94.934 110.996 94.652 111.113 94.328 c 111.227 93.996 111.285 93.625 + 111.285 93.219 c 111.285 92.668 111.098 92.23 110.723 91.906 c 110.348 +91.586 109.742 91.422 108.91 91.422 c h +106.457 89.375 m 108.566 89.375 l 109.555 89.375 110.316 89.168 110.848 + 88.75 c 111.379 88.336 111.645 87.668 111.645 86.75 c 111.645 86.105 111.449 + 85.641 111.066 85.359 c 110.68 85.07 110.09 84.922 109.301 84.922 c 107.004 + 84.922 l h +106.457 89.375 m f +126.395 89.375 m 126.395 89.898 126.277 90.371 126.051 90.797 c 125.82 +91.227 125.41 91.609 124.816 91.953 c 124.23 92.289 123.434 92.574 122.426 + 92.812 c 121.414 93.055 120.133 93.246 118.582 93.391 c 118.582 94.309 +118.793 94.992 119.223 95.438 c 119.66 95.875 120.324 96.094 121.223 96.094 + c 121.785 96.094 122.242 96.031 122.598 95.906 c 122.961 95.781 123.273 + 95.648 123.535 95.5 c 123.793 95.344 124.023 95.203 124.223 95.078 c 124.418 + 94.953 124.637 94.891 124.879 94.891 c 125.098 94.891 125.277 94.977 125.426 + 95.141 c 126.191 96.062 l 125.754 96.469 125.324 96.82 124.91 97.109 c +124.504 97.402 124.074 97.648 123.629 97.844 c 123.191 98.031 122.727 98.168 + 122.238 98.25 c 121.746 98.344 121.223 98.391 120.66 98.391 c 119.91 98.391 + 119.227 98.273 118.613 98.047 c 117.996 97.809 117.473 97.477 117.035 97.047 + c 116.605 96.609 116.273 96.09 116.035 95.484 c 115.793 94.883 115.676 +94.203 115.676 93.453 c 115.676 92.539 115.832 91.656 116.145 90.812 c 116.465 + 89.961 116.914 89.211 117.488 88.562 c 118.07 87.918 118.773 87.402 119.598 + 87.016 c 120.43 86.621 121.355 86.422 122.379 86.422 c 123.043 86.422 123.629 + 86.512 124.129 86.688 c 124.637 86.855 125.059 87.086 125.395 87.375 c +125.727 87.656 125.977 87.977 126.145 88.328 c 126.309 88.684 126.395 89.031 + 126.395 89.375 c h +122.238 88.484 m 121.809 88.484 121.414 88.559 121.051 88.703 c 120.695 + 88.84 120.371 89.043 120.082 89.312 c 119.789 89.574 119.539 89.891 119.332 + 90.266 c 119.121 90.633 118.957 91.039 118.832 91.484 c 119.871 91.359 +120.711 91.227 121.348 91.078 c 121.992 90.934 122.492 90.777 122.848 90.609 + c 123.199 90.445 123.434 90.266 123.551 90.078 c 123.664 89.883 123.723 + 89.672 123.723 89.453 c 123.723 89.34 123.695 89.227 123.645 89.109 c 123.59 + 88.996 123.504 88.891 123.379 88.797 c 123.254 88.703 123.098 88.633 122.91 + 88.578 c 122.723 88.516 122.496 88.484 122.238 88.484 c h +122.238 88.484 m f +132.02 86.578 m 130.629 98.219 l 127.691 98.219 l 129.098 86.578 l h +132.723 83.234 m 132.723 83.484 132.668 83.727 132.566 83.953 c 132.461 + 84.172 132.32 84.367 132.145 84.531 c 131.965 84.699 131.762 84.836 131.535 + 84.938 c 131.316 85.031 131.09 85.078 130.863 85.078 c 130.633 85.078 130.41 + 85.031 130.191 84.938 c 129.98 84.836 129.793 84.699 129.629 84.531 c 129.461 + 84.367 129.324 84.172 129.223 83.953 c 129.129 83.727 129.082 83.484 129.082 + 83.234 c 129.082 82.984 129.129 82.75 129.223 82.531 c 129.324 82.305 129.461 + 82.105 129.629 81.938 c 129.805 81.773 129.996 81.637 130.207 81.531 c +130.426 81.43 130.648 81.375 130.879 81.375 c 131.117 81.375 131.348 81.43 + 131.566 81.531 c 131.793 81.625 131.992 81.762 132.16 81.938 c 132.336 +82.105 132.473 82.305 132.566 82.531 c 132.668 82.75 132.723 82.984 132.723 + 83.234 c h +132.723 83.234 m f +144.113 89.375 m 144.113 89.898 143.996 90.371 143.77 90.797 c 143.539 +91.227 143.129 91.609 142.535 91.953 c 141.949 92.289 141.152 92.574 140.145 + 92.812 c 139.133 93.055 137.852 93.246 136.301 93.391 c 136.301 94.309 +136.512 94.992 136.941 95.438 c 137.379 95.875 138.043 96.094 138.941 96.094 + c 139.504 96.094 139.961 96.031 140.316 95.906 c 140.68 95.781 140.992 +95.648 141.254 95.5 c 141.512 95.344 141.742 95.203 141.941 95.078 c 142.137 + 94.953 142.355 94.891 142.598 94.891 c 142.816 94.891 142.996 94.977 143.145 + 95.141 c 143.91 96.062 l 143.473 96.469 143.043 96.82 142.629 97.109 c +142.223 97.402 141.793 97.648 141.348 97.844 c 140.91 98.031 140.445 98.168 + 139.957 98.25 c 139.465 98.344 138.941 98.391 138.379 98.391 c 137.629 +98.391 136.945 98.273 136.332 98.047 c 135.715 97.809 135.191 97.477 134.754 + 97.047 c 134.324 96.609 133.992 96.09 133.754 95.484 c 133.512 94.883 133.395 + 94.203 133.395 93.453 c 133.395 92.539 133.551 91.656 133.863 90.812 c +134.184 89.961 134.633 89.211 135.207 88.562 c 135.789 87.918 136.492 87.402 + 137.316 87.016 c 138.148 86.621 139.074 86.422 140.098 86.422 c 140.762 + 86.422 141.348 86.512 141.848 86.688 c 142.355 86.855 142.777 87.086 143.113 + 87.375 c 143.445 87.656 143.695 87.977 143.863 88.328 c 144.027 88.684 +144.113 89.031 144.113 89.375 c h +139.957 88.484 m 139.527 88.484 139.133 88.559 138.77 88.703 c 138.414 +88.84 138.09 89.043 137.801 89.312 c 137.508 89.574 137.258 89.891 137.051 + 90.266 c 136.84 90.633 136.676 91.039 136.551 91.484 c 137.59 91.359 138.43 + 91.227 139.066 91.078 c 139.711 90.934 140.211 90.777 140.566 90.609 c +140.918 90.445 141.152 90.266 141.27 90.078 c 141.383 89.883 141.441 89.672 + 141.441 89.453 c 141.441 89.34 141.414 89.227 141.363 89.109 c 141.309 +88.996 141.223 88.891 141.098 88.797 c 140.973 88.703 140.816 88.633 140.629 + 88.578 c 140.441 88.516 140.215 88.484 139.957 88.484 c h +139.957 88.484 m f +145.363 98.219 m 147.395 81.375 l 150.332 81.375 l 148.301 98.219 l h +145.363 98.219 m f +155.645 86.578 m 154.254 98.219 l 151.316 98.219 l 152.723 86.578 l h +156.348 83.234 m 156.348 83.484 156.293 83.727 156.191 83.953 c 156.086 + 84.172 155.945 84.367 155.77 84.531 c 155.59 84.699 155.387 84.836 155.16 + 84.938 c 154.941 85.031 154.715 85.078 154.488 85.078 c 154.258 85.078 +154.035 85.031 153.816 84.938 c 153.605 84.836 153.418 84.699 153.254 84.531 + c 153.086 84.367 152.949 84.172 152.848 83.953 c 152.754 83.727 152.707 + 83.484 152.707 83.234 c 152.707 82.984 152.754 82.75 152.848 82.531 c 152.949 + 82.305 153.086 82.105 153.254 81.938 c 153.43 81.773 153.621 81.637 153.832 + 81.531 c 154.051 81.43 154.273 81.375 154.504 81.375 c 154.742 81.375 154.973 + 81.43 155.191 81.531 c 155.418 81.625 155.617 81.762 155.785 81.938 c 155.961 + 82.105 156.098 82.305 156.191 82.531 c 156.293 82.75 156.348 82.984 156.348 + 83.234 c h +156.348 83.234 m f +166.957 85.531 m 166.84 85.68 166.73 85.789 166.629 85.859 c 166.535 85.922 + 166.41 85.953 166.254 85.953 c 166.105 85.953 165.957 85.898 165.801 85.781 + c 165.645 85.668 165.449 85.543 165.223 85.406 c 165.004 85.273 164.742 + 85.152 164.441 85.047 c 164.137 84.934 163.777 84.875 163.363 84.875 c +162.957 84.875 162.598 84.934 162.285 85.047 c 161.973 85.152 161.707 85.297 + 161.488 85.484 c 161.27 85.672 161.102 85.902 160.988 86.172 c 160.883 +86.434 160.832 86.715 160.832 87.016 c 160.832 87.34 160.918 87.617 161.098 + 87.844 c 161.273 88.062 161.508 88.262 161.801 88.438 c 162.09 88.617 162.418 + 88.777 162.785 88.922 c 163.16 89.059 163.539 89.203 163.926 89.359 c 164.309 + 89.516 164.684 89.695 165.051 89.891 c 165.426 90.078 165.758 90.309 166.051 + 90.578 c 166.352 90.84 166.586 91.152 166.754 91.516 c 166.93 91.883 167.02 + 92.32 167.02 92.828 c 167.02 93.59 166.883 94.309 166.613 94.984 c 166.34 + 95.652 165.945 96.234 165.426 96.734 c 164.914 97.234 164.293 97.637 163.566 + 97.938 c 162.836 98.238 162.012 98.391 161.098 98.391 c 160.605 98.391 +160.133 98.336 159.676 98.234 c 159.215 98.141 158.777 98.008 158.363 97.828 + c 157.945 97.652 157.559 97.438 157.207 97.188 c 156.863 96.938 156.559 + 96.664 156.301 96.359 c 157.363 94.969 l 157.434 94.867 157.539 94.777 +157.676 94.703 c 157.809 94.633 157.941 94.594 158.066 94.594 c 158.254 +94.594 158.434 94.672 158.613 94.828 c 158.801 94.977 159.02 95.137 159.27 + 95.312 c 159.52 95.492 159.816 95.656 160.16 95.812 c 160.512 95.961 160.941 + 96.031 161.441 96.031 c 162.305 96.031 162.973 95.812 163.441 95.375 c +163.918 94.938 164.16 94.328 164.16 93.547 c 164.16 93.195 164.07 92.898 + 163.895 92.656 c 163.727 92.418 163.496 92.215 163.207 92.047 c 162.914 + 91.883 162.586 91.734 162.223 91.609 c 161.855 91.477 161.48 91.336 161.098 + 91.188 c 160.711 91.043 160.336 90.883 159.973 90.703 c 159.605 90.527 +159.277 90.305 158.988 90.031 c 158.695 89.762 158.461 89.434 158.285 89.047 + c 158.105 88.664 158.02 88.195 158.02 87.641 c 158.02 86.965 158.148 86.32 + 158.41 85.703 c 158.668 85.09 159.039 84.543 159.52 84.062 c 159.996 83.586 + 160.586 83.203 161.285 82.922 c 161.98 82.641 162.762 82.5 163.629 82.5 + c 164.074 82.5 164.504 82.547 164.91 82.641 c 165.324 82.727 165.711 82.844 + 166.066 83 c 166.43 83.156 166.758 83.344 167.051 83.562 c 167.352 83.781 + 167.613 84.023 167.832 84.281 c h +166.957 85.531 m f +177.145 96.156 m 176.758 96.574 176.387 96.93 176.035 97.219 c 175.691 +97.5 175.336 97.73 174.973 97.906 c 174.605 98.086 174.223 98.211 173.816 + 98.281 c 173.41 98.352 172.961 98.391 172.473 98.391 c 171.793 98.391 171.184 + 98.273 170.645 98.047 c 170.113 97.82 169.66 97.492 169.285 97.062 c 168.918 + 96.637 168.637 96.125 168.441 95.531 c 168.242 94.93 168.145 94.258 168.145 + 93.516 c 168.145 92.883 168.211 92.266 168.348 91.672 c 168.48 91.078 168.676 + 90.523 168.926 90 c 169.184 89.469 169.496 88.984 169.863 88.547 c 170.238 + 88.102 170.652 87.719 171.113 87.406 c 171.57 87.094 172.066 86.855 172.598 + 86.688 c 173.129 86.512 173.691 86.422 174.285 86.422 c 175.066 86.422 +175.727 86.559 176.27 86.828 c 176.82 87.09 177.309 87.5 177.738 88.062 +c 176.77 89.172 l 176.707 89.258 176.629 89.328 176.535 89.391 c 176.441 + 89.445 176.34 89.469 176.238 89.469 c 176.102 89.469 175.98 89.434 175.879 + 89.359 c 175.773 89.277 175.652 89.195 175.52 89.109 c 175.383 89.027 175.215 + 88.949 175.02 88.875 c 174.82 88.805 174.559 88.766 174.238 88.766 c 173.832 + 88.766 173.441 88.883 173.066 89.109 c 172.691 89.328 172.355 89.648 172.066 + 90.062 c 171.773 90.48 171.539 90.98 171.363 91.562 c 171.195 92.148 171.113 + 92.805 171.113 93.531 c 171.113 94.355 171.293 94.992 171.66 95.438 c 172.023 + 95.875 172.508 96.094 173.113 96.094 c 173.539 96.094 173.883 96.039 174.145 + 95.922 c 174.414 95.809 174.645 95.684 174.832 95.547 c 175.02 95.402 175.184 + 95.273 175.332 95.156 c 175.488 95.031 175.66 94.969 175.848 94.969 c 175.949 + 94.969 176.043 94.996 176.129 95.047 c 176.223 95.09 176.305 95.152 176.379 + 95.234 c h +177.145 96.156 m f +187.801 98.219 m 186.223 98.219 l 185.836 98.219 185.57 98.125 185.426 +97.938 c 185.289 97.742 185.23 97.508 185.254 97.234 c 185.348 95.75 l 184.793 + 96.562 184.168 97.211 183.473 97.688 c 182.773 98.156 182.004 98.391 181.16 + 98.391 c 180.68 98.391 180.23 98.305 179.816 98.141 c 179.398 97.965 179.035 + 97.703 178.723 97.359 c 178.418 97.008 178.18 96.562 178.004 96.031 c 177.824 + 95.5 177.738 94.883 177.738 94.172 c 177.738 93.477 177.824 92.805 178.004 + 92.156 c 178.191 91.5 178.457 90.887 178.801 90.312 c 179.145 89.73 179.555 + 89.203 180.035 88.734 c 180.523 88.258 181.07 87.844 181.676 87.5 c 182.277 + 87.156 182.93 86.891 183.629 86.703 c 184.324 86.508 185.059 86.406 185.832 + 86.406 c 186.395 86.406 186.957 86.449 187.52 86.531 c 188.082 86.605 188.637 + 86.742 189.191 86.938 c h +182.301 95.969 m 182.652 95.969 182.996 95.855 183.332 95.625 c 183.664 + 95.398 183.977 95.09 184.27 94.703 c 184.559 94.309 184.824 93.844 185.066 + 93.312 c 185.316 92.781 185.527 92.219 185.707 91.625 c 186.082 88.531 +l 185.965 88.523 185.852 88.516 185.738 88.516 c 185.633 88.508 185.535 +88.5 185.441 88.5 c 184.762 88.5 184.133 88.641 183.551 88.922 c 182.977 + 89.195 182.477 89.562 182.051 90.031 c 181.633 90.5 181.305 91.047 181.066 + 91.672 c 180.824 92.297 180.707 92.953 180.707 93.641 c 180.707 94.465 +180.848 95.059 181.129 95.422 c 181.41 95.789 181.801 95.969 182.301 95.969 + c h +182.301 95.969 m f +190.691 98.219 m 192.723 81.375 l 195.66 81.375 l 193.629 98.219 l h +190.691 98.219 m f +207.16 89.375 m 207.16 89.898 207.043 90.371 206.816 90.797 c 206.586 91.227 + 206.176 91.609 205.582 91.953 c 204.996 92.289 204.199 92.574 203.191 92.812 + c 202.18 93.055 200.898 93.246 199.348 93.391 c 199.348 94.309 199.559 +94.992 199.988 95.438 c 200.426 95.875 201.09 96.094 201.988 96.094 c 202.551 + 96.094 203.008 96.031 203.363 95.906 c 203.727 95.781 204.039 95.648 204.301 + 95.5 c 204.559 95.344 204.789 95.203 204.988 95.078 c 205.184 94.953 205.402 + 94.891 205.645 94.891 c 205.863 94.891 206.043 94.977 206.191 95.141 c +206.957 96.062 l 206.52 96.469 206.09 96.82 205.676 97.109 c 205.27 97.402 + 204.84 97.648 204.395 97.844 c 203.957 98.031 203.492 98.168 203.004 98.25 + c 202.512 98.344 201.988 98.391 201.426 98.391 c 200.676 98.391 199.992 + 98.273 199.379 98.047 c 198.762 97.809 198.238 97.477 197.801 97.047 c +197.371 96.609 197.039 96.09 196.801 95.484 c 196.559 94.883 196.441 94.203 + 196.441 93.453 c 196.441 92.539 196.598 91.656 196.91 90.812 c 197.23 89.961 + 197.68 89.211 198.254 88.562 c 198.836 87.918 199.539 87.402 200.363 87.016 + c 201.195 86.621 202.121 86.422 203.145 86.422 c 203.809 86.422 204.395 + 86.512 204.895 86.688 c 205.402 86.855 205.824 87.086 206.16 87.375 c 206.492 + 87.656 206.742 87.977 206.91 88.328 c 207.074 88.684 207.16 89.031 207.16 + 89.375 c h +203.004 88.484 m 202.574 88.484 202.18 88.559 201.816 88.703 c 201.461 +88.84 201.137 89.043 200.848 89.312 c 200.555 89.574 200.305 89.891 200.098 + 90.266 c 199.887 90.633 199.723 91.039 199.598 91.484 c 200.637 91.359 +201.477 91.227 202.113 91.078 c 202.758 90.934 203.258 90.777 203.613 90.609 + c 203.965 90.445 204.199 90.266 204.316 90.078 c 204.43 89.883 204.488 +89.672 204.488 89.453 c 204.488 89.34 204.461 89.227 204.41 89.109 c 204.355 + 88.996 204.27 88.891 204.145 88.797 c 204.02 88.703 203.863 88.633 203.676 + 88.578 c 203.488 88.516 203.262 88.484 203.004 88.484 c h +203.004 88.484 m f +Q Q +showpage +%%Trailer +end +%%EOF diff --git a/Artwork/bee-logo-gehaeusedeckel.png b/Artwork/bee-logo-gehaeusedeckel.png new file mode 100644 index 0000000..30ae699 Binary files /dev/null and b/Artwork/bee-logo-gehaeusedeckel.png differ diff --git a/Artwork/bee-logo-gehaeusedeckel.svg b/Artwork/bee-logo-gehaeusedeckel.svg new file mode 100644 index 0000000..677103a --- /dev/null +++ b/Artwork/bee-logo-gehaeusedeckel.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + https://www.mini-beieli.ch + BeieliScale + + diff --git a/Artwork/bee-nav-logo.png b/Artwork/bee-nav-logo.png new file mode 100644 index 0000000..92c2c19 Binary files /dev/null and b/Artwork/bee-nav-logo.png differ diff --git a/Artwork/bee-nav-logo.svg b/Artwork/bee-nav-logo.svg new file mode 100644 index 0000000..f037a13 --- /dev/null +++ b/Artwork/bee-nav-logo.svg @@ -0,0 +1,132 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + mini-beieli.ch + diff --git a/Artwork/favicon.ico b/Artwork/favicon.ico new file mode 100644 index 0000000..32b2375 Binary files /dev/null and b/Artwork/favicon.ico differ diff --git a/Bilder/beieliscale-prototype.jpg b/Bilder/beieliscale-prototype.jpg new file mode 100644 index 0000000..54037e8 Binary files /dev/null and b/Bilder/beieliscale-prototype.jpg differ diff --git a/Bilder/beieliscale-waegeteil.jpg b/Bilder/beieliscale-waegeteil.jpg new file mode 100644 index 0000000..4e079bd Binary files /dev/null and b/Bilder/beieliscale-waegeteil.jpg differ diff --git a/Case/A9441107.pdf b/Case/A9441107.pdf new file mode 100644 index 0000000..6881c18 Binary files /dev/null and b/Case/A9441107.pdf differ diff --git a/Case/A9441A01-OT.PDF b/Case/A9441A01-OT.PDF new file mode 100644 index 0000000..86b31d1 Binary files /dev/null and b/Case/A9441A01-OT.PDF differ diff --git a/Case/A9441A01.pdf b/Case/A9441A01.pdf new file mode 100644 index 0000000..0decd90 Binary files /dev/null and b/Case/A9441A01.pdf differ diff --git a/Case/Beieliscale-Case-Freigabe.pdf b/Case/Beieliscale-Case-Freigabe.pdf new file mode 100644 index 0000000..c188010 Binary files /dev/null and b/Case/Beieliscale-Case-Freigabe.pdf differ diff --git a/Case/OKW-datasheet-A0305031.pdf b/Case/OKW-datasheet-A0305031.pdf new file mode 100644 index 0000000..61baa46 Binary files /dev/null and b/Case/OKW-datasheet-A0305031.pdf differ diff --git a/Case/bee-logo-gehaeusedeckel.eps b/Case/bee-logo-gehaeusedeckel.eps new file mode 100644 index 0000000..5cd589e --- /dev/null +++ b/Case/bee-logo-gehaeusedeckel.eps @@ -0,0 +1,1171 @@ +%!PS-Adobe-3.0 EPSF-3.0 +%%Creator: cairo 1.15.12 (http://cairographics.org) +%%CreationDate: Wed Aug 1 10:25:10 2018 +%%Pages: 1 +%%DocumentData: Clean7Bit +%%LanguageLevel: 2 +%%BoundingBox: 28 28 208 121 +%%EndComments +%%BeginProlog +50 dict begin +/q { gsave } bind def +/Q { grestore } bind def +/cm { 6 array astore concat } bind def +/w { setlinewidth } bind def +/J { setlinecap } bind def +/j { setlinejoin } bind def +/M { setmiterlimit } bind def +/d { setdash } bind def +/m { moveto } bind def +/l { lineto } bind def +/c { curveto } bind def +/h { closepath } bind def +/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto + 0 exch rlineto 0 rlineto closepath } bind def +/S { stroke } bind def +/f { fill } bind def +/f* { eofill } bind def +/n { newpath } bind def +/W { clip } bind def +/W* { eoclip } bind def +/BT { } bind def +/ET { } bind def +/BDC { mark 3 1 roll /BDC pdfmark } bind def +/EMC { mark /EMC pdfmark } bind def +/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def +/Tj { show currentpoint cairo_store_point } bind def +/TJ { + { + dup + type /stringtype eq + { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse + } forall + currentpoint cairo_store_point +} bind def +/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore + cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def +/Tf { pop /cairo_font exch def /cairo_font_matrix where + { pop cairo_selectfont } if } bind def +/Td { matrix translate cairo_font_matrix matrix concatmatrix dup + /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point + /cairo_font where { pop cairo_selectfont } if } bind def +/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def + cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def +/g { setgray } bind def +/rg { setrgbcolor } bind def +/d1 { setcachedevice } bind def +/cairo_data_source { + CairoDataIndex CairoData length lt + { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } + { () } ifelse +} def +/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def +/cairo_image { image cairo_flush_ascii85_file } def +/cairo_imagemask { imagemask cairo_flush_ascii85_file } def +%%EndProlog +%%BeginSetup +%%BeginResource: font Carlito-Italic +11 dict begin +/FontType 42 def +/FontName /Carlito-Italic def +/PaintType 0 def +/FontMatrix [ 1 0 0 1 0 0 ] def +/FontBBox [ 0 0 0 0 ] def +/Encoding 256 array def +0 1 255 { Encoding exch /.notdef put } for +Encoding 45 /hyphen put +Encoding 46 /period put +Encoding 47 /slash put +Encoding 58 /colon put +Encoding 98 /b put +Encoding 99 /c put +Encoding 101 /e put +Encoding 104 /h put +Encoding 105 /i put +Encoding 108 /l put +Encoding 109 /m put +Encoding 110 /n put +Encoding 112 /p put +Encoding 115 /s put +Encoding 119 /w put +/CharStrings 16 dict dup begin +/.notdef 0 def +/h 1 def +/p 2 def +/s 3 def +/colon 4 def +/slash 5 def +/w 6 def +/period 7 def +/m 8 def +/i 9 def +/n 10 def +/hyphen 11 def +/b 12 def +/e 13 def +/l 14 def +/c 15 def +end readonly def +/sfnts [ +<0001000000090080000300106376742025f401d600000b08000000386670676ddbb62e8c0000 +0b4000000a75676c7966362d50ec0000009c00000a6c686561647d2f70dd000015b800000036 +686865610e3e08f0000015f000000024686d74783f4004a200001614000000486c6f63610000 +6e9c0000165c0000004c6d61787001d20c45000016a800000020707265709a783a38000016c8 +000000810004002b000003d9056700250039003d00410088b50001020001424bb02050584034 +000302010203016800010402010466000000020300025b000400050804055b00090906510006 +060e4300080807510007070d07441b4032000302010203016800010402010466000600090006 +0959000000020300025b000400050804055b00080807510007070d074459400d414011111428 +26232a1c240a182b133e0333321e0215140e040f012327263e0435342623220e022322271334 +3e0233321e0215140e0223222e020121112137211121f11838424c2c3d6448271d2b342f2104 +11770d041a2b352d1f45362736271a0b190c5f101d271716271c11111c271617271d10fead03 +aefc52320345fcbb044d15251d10223e5936354c392a2524166471213129262d372730391115 +1115fd1916281d11111d281617271c11111c270463fa993504fd000000010050000003c1058e +0017002c40290301020301420000000e430003030153000101174305040202020d0244000000 +1700172315231106132b331333033e0133321e0207032313362623220e02070350abaf504ec0 +6440623d160b49af490e3a4f2d5f584f1c3e058efd697376315e8a58fd91026f736e2f567b4b +fdfb000000020021feb603e803e00019002a0070400f0501040020060205041801020503424b +b0265058401d070104040053010100000f4300050502530002021543060103031103441b4021 +0000000f43070104040153000101174300050502530002021543060103031103445940131b1a +000024221a2a1b2a0019001928272108122b1b013332160f013e0333321615140e0423222627 +0301220e020f011e0133323e02353426219f581d1f0210275b656e398692223f5a6e81474c7f +2c3401c332655e511d1e286e3951815a3159feb6051d1d1ed340694a28b7b14f9e8f7c5a343a +35fe55049e3a688f55f5352a5a8faf56747800000001000cfff1030903e0003a003d403a3a01 +01051e0102040242000001030100036800030401030466000101055300050517430004040253 +0002021502443836272522201c1a232206112b010e0123222e0223220e0215141e0615140e02 +23222627373633321e0233323e0235342e0435343e023332161702de08100d0e213145322f4d +371f253e4e524e3e253867945b6397302a111d1021324b3a3352391e406171614034618b565e +8d3303340b0b161916182a381f22332a232327313f2a437b5d384235421b1c211b1c303f2432 +3e2d28354f3e3d7358353a33ffff0063fff001ac038e00220010630000230011ff89fb600103 +0011ffdbfe06001c4019000300020103025b000101005300000015004428282825041e2b0000 +0001ffa8ffa8037e057800090012400f000001006b0001010e0144232202112b170e012b0101 +3e013b015310381c47032d0e33204817202105921d210001004c000005dd03d4002d0020401d +261708030300014202010200000f43040103030d034429212c3c2005142b1333321617131e01 +173e0137013e013b01321617131e01173e0137013e013b01012322270326270e010701062b01 +4c82141a03650506020b1a0e011e061a11491416027b060b0309150e0109061b117ffe528518 +06870603050c07fed10e1a7f03d21411fd9122412020402202750e14130efd8b224120203f22 +02720f15fc2e2102931f20102010fd6e21000000ffff005cfff0015300e8002200105c000103 +0011ff82fb600012400f00010100530000001500442825021c2b0001004d000005fb03e0002d +005a400b050104000c0602030402424bb0265058401606010404005302010200000f43080705 +0303030d03441b401a0000000f430601040401530201010117430807050303030d034459400f +0000002d002d2515251324252109162b33133332160f013e01333216153e0133321607032313 +362e0223220e0207032313362e0223220e0207034d75561e1f020d4cbc686c644dc46d877016 +4aad4a07031932282e5b54491c3dad490701173228335e53471d3b03d31d1eb67d8189828982 +bfb2fd91026f3954381b2a52794ffdf5026f3954381b30598051fe0b00000002005a000001be +0581000300170026402300020203530003030e43040101010f430000000d0044000014120a08 +000300031105102b0103231b01140e0223222e0235343e0233321e02017d76ad76ee15222c17 +172a211414212b17172c221403d2fc2e03d20132192c221414222c19192e221414222d000001 +0048000003c003e0001a004bb50601020301424bb026505840130003030053010100000f4305 +040202020d02441b40170000000f430003030153000101174305040202020d024459400c0000 +001a001a2315252106132b33133332160f013e0133321e0207032313362623220e0207034875 +571d1f020d50cd6c40623d170b49af490f3b5030645d501b3703d31d1ec08385315e8a58fd91 +026f736d325d8251fe130001006001fa022d028a00030017401400000101004d000000015100 +01000145111002112b132107217201bb12fe45028a90000000020051fff203e4058e0019002a +00804bb0245058400b20030205041501020502421b400b2003020504150103050242594bb024 +5058401d0000000e430701040401530001011743000505025306030202021502441b40210000 +000e430701040401530001011743060103030d4300050502530002021502445940131b1a0000 +24221a2a1b2a0019001828251108122b331333033e0333321615140e0423222627070e012301 +220e020f011e0133323e0235342651acac5526586069368192223e586d804651842b0f05191e +01e131635b4f1d1f276b384f7f593056058efd4b3c614525b8af509d907c5a34433d3f1a1903 +543a678e54f9342a5a8fb05673780002003bfff2039a03df00280039003740342e0101041301 +000102420001040004010068050104040353000303174300000002530002021502442a292939 +2a392a26232a06132b01140e010407061415141633323e0233321f010e0323222e0235343e04 +33321e0225220e02073e0535342e02039a3a99fef3d30181834f6a4b3419130f2c36676a7543 +5d94673722425f7a945552764d24febe4574593f1070a57348280e12273f02fb3f69553f1308 +1108858a282f281135334a2f17396a955d4b938671542f2b425141325776430c1d2124282a17 +12252014000000010058000001b0058e0003001840150000000e43020101010d014400000003 +00031103102b3313330358abadac058efa7200000001003afff2035203e0002d003940361201 +0301000104050242000203050302056800050403050466000303015300010117430004040053 +00000015004423282325282406152b250e0323222e0235343e0233321617070e0123222e0223 +220e0215141e0233323e0233321f010326335c5d633a56845a2f4b87bb706385333706120a0e +1b2a413244775a341c36513542593d2b14130e2cb038492b123b6a945976daa864454242080a +181e18477daa623c614525262e26113500000001000000000000000000000007b20501054560 +44310000000100da049001d1058800130012400f00000001530001010e0044282402112b0114 +0e0223222e0235343e0233321e0201d114232d19192c211414212c19192e2214050b192d2114 +14212d191a2d221414222d000000000000000000000000000000000000b0008900b000890522 +0000058703d20000feb6079efdda0531fff2058703e0fff2feb6079efddab0002cb02060662d +b0012c206420b0c050b004265ab004455b582123211b8a5820b050505821b040591b20b03850 +5821b038595920b00b456164b028505821b00b4520b030505821b030591b20b0c05058206620 +8a8a6120b00a5058601b20b020505821b00a601b20b036505821b036601b605959591bb0002b +595923b00050586559592db0022c204520b00425616420b005435058b0052342b00623421b21 +2159b001602db0032c232123212064b105624220b0062342b20b01022a2120b00643208a208a +b0002bb13005258a515860501b6152595823592120b0405358b0002b1b21b0405923b0005058 +65592db0042cb007432bb20002004360422db0052cb00723422320b000234261b0026266b001 +63b00160b0042a2db0062c20204520b0024563b804006220b0005058b040605966b001636044 +b001602db0072c20204520b0002b23b10804256020458a2361206420b020505821b0001bb030 +5058b0201bb040595923b00050586559b0032523614444b001602db0082cb1050545b0016144 +2db0092cb001602020b009434ab000505820b009234259b00a434ab000525820b00a2342592d +b00a2c20b0106266b0016320b80400638a2361b00b4360208a6020b00b2342232db00b2c4b54 +58b10701445924b00d6523782db00c2c4b51584b5358b1070144591b215924b0136523782db0 +0d2cb1000c435558b10c0c43b0016142b00a2b59b00043b0022542b109022542b10a022542b0 +01162320b003255058b101004360b00425428a8a208a2361b0092a2123b00161208a2361b009 +2a211bb101004360b0022542b0022561b0092a2159b0094347b00a434760b0026220b0005058 +b040605966b0016320b0024563b804006220b0005058b040605966b0016360b10000132344b0 +0143b0003eb20101014360422db00e2cb1000545545800b00c23422060b00161b50d0d01000b +0042428a60b10d052bb06d2b1b22592db00f2cb1000e2b2db0102cb1010e2b2db0112cb1020e +2b2db0122cb1030e2b2db0132cb1040e2b2db0142cb1050e2b2db0152cb1060e2b2db0162cb1 +070e2b2db0172cb1080e2b2db0182cb1090e2b2db0192cb0082bb1000545545800b00c234220 +60b00161b50d0d01000b0042428a60b10d052bb06d2b1b22592db01a2cb100192b2db01b2cb1 +01192b2db01c2cb102192b2db01d2cb103192b2db01e2cb104192b2db01f2cb105192b2db020 +2cb106192b2db0212cb107192b2db0222cb108192b2db0232cb109192b2db0242c203cb00160 +2db0252c2060b00d60204323b0016043b0022561b00160b0242a212db0262cb0252bb0252a2d +b0272c2020472020b0024563b804006220b0005058b040605966b001636023613823208a5558 +20472020b0024563b804006220b0005058b040605966b00163602361381b21592db0282cb100 +0545545800b00116b0272ab00115301b22592db0292cb0082bb1000545545800b00116b0272a +b00115301b22592db02a2c2035b001602db02b2c00b0034563b804006220b0005058b0406059 +66b00163b0002bb0024563b804006220b0005058b040605966b00163b0002bb00016b4000000 +0000443e2338b12a01152a2db02c2c203c204720b0024563b804006220b0005058b040605966 +b0016360b0004361382db02d2c2e173c2db02e2c203c204720b0024563b804006220b0005058 +b040605966b0016360b0004361b0014363382db02f2cb102001625202e2047b0002342b00225 +498a8a47234723612058621b2159b0012342b22e010115142a2db0302cb00016b00425b00425 +4723472361b006452b658a2e2320203c8a382db0312cb00016b00425b00425202e4723472361 +20b0042342b006452b20b060505820b0405158b3022003201bb30226031a5942422320b00843 +208a234723472361234660b00443b0026220b0005058b040605966b001636020b0002b208a8a +6120b00243606423b0034361645058b00243611bb003436059b00325b0026220b0005058b040 +605966b0016361232020b00426234661381b23b0084346b00225b0084347234723616020b004 +43b0026220b0005058b040605966b00163602320b0002b23b0044360b0002bb0052561b00525 +b0026220b0005058b040605966b00163b004266120b00425606423b0032560645058211b2321 +59232020b0042623466138592db0322cb00016202020b00526202e4723472361233c382db033 +2cb0001620b0082342202020462347b0002b2361382db0342cb00016b00325b0022547234723 +61b00054582e203c23211bb00225b00225472347236120b00525b004254723472361b00625b0 +052549b0022561b0014563232058621b215963b804006220b0005058b040605966b001636023 +2e2320203c8a382321592db0352cb0001620b00843202e47234723612060b0206066b0026220 +b0005058b040605966b001632320203c8a382db0362c23202e46b00225465258203c592eb126 +01142b2db0372c23202e46b00225465058203c592eb12601142b2db0382c23202e46b0022546 +5258203c5923202e46b00225465058203c592eb12601142b2db0392cb0302b23202e46b00225 +465258203c592eb12601142b2db03a2cb0312b8a20203cb00423428a3823202e46b002254652 +58203c592eb12601142bb004432eb0262b2db03b2cb00016b00425b00426202e4723472361b0 +06452b23203c202e2338b12601142b2db03c2cb108042542b00016b00425b00425202e472347 +236120b0042342b006452b20b060505820b0405158b3022003201bb30226031a594242232047 +b00443b0026220b0005058b040605966b001636020b0002b208a8a6120b00243606423b00343 +61645058b00243611bb003436059b00325b0026220b0005058b040605966b0016361b0022546 +613823203c23381b212020462347b0002b2361382159b12601142b2db03d2cb0302b2eb12601 +142b2db03e2cb0312b212320203cb00423422338b12601142bb004432eb0262b2db03f2cb000 +152047b0002342b20001011514132eb02c2a2db0402cb000152047b0002342b2000101151413 +2eb02c2a2db0412cb100011413b02d2a2db0422cb02f2a2db0432cb000164523202e20468a23 +6138b12601142b2db0442cb0082342b0432b2db0452cb200003c2b2db0462cb200013c2b2db0 +472cb201003c2b2db0482cb201013c2b2db0492cb200003d2b2db04a2cb200013d2b2db04b2c +b201003d2b2db04c2cb201013d2b2db04d2cb20000392b2db04e2cb20001392b2db04f2cb201 +00392b2db0502cb20101392b2db0512cb200003b2b2db0522cb200013b2b2db0532cb201003b +2b2db0542cb201013b2b2db0552cb200003e2b2db0562cb200013e2b2db0572cb201003e2b2d +b0582cb201013e2b2db0592cb200003a2b2db05a2cb200013a2b2db05b2cb201003a2b2db05c +2cb201013a2b2db05d2cb0322b2eb12601142b2db05e2cb0322bb0362b2db05f2cb0322bb037 +2b2db0602cb00016b0322bb0382b2db0612cb0332b2eb12601142b2db0622cb0332bb0362b2d +b0632cb0332bb0372b2db0642cb0332bb0382b2db0652cb0342b2eb12601142b2db0662cb034 +2bb0362b2db0672cb0342bb0372b2db0682cb0342bb0382b2db0692cb0352b2eb12601142b2d +b06a2cb0352bb0362b2db06b2cb0352bb0372b2db06c2cb0352bb0382b2db06d2c2bb00865b0 +03245078b00115302d0000000001000000011a5ebf31c0f95f0f3cf5001b0800000000004a53 +db2600000000ce2e3053fc98fdec08e4081e000200060002000100000000000100000600fe00 +01c40aa0fc98f83e08e4080000fb0000000000000000000000000012040e002b041d0050041d +0021031d000c02240063031affa805b8004c0205005c0654004d01d6005a041d004802730060 +041d005103d2003b01d600580354003a0543000001c900da0000000000000148000001c80000 +02bc00000398000003d800000414000004d0000004fc000005e40000065c0000070000000734 +00000834000009100000094400000a0000000a1800000a6c00010000001200e50010008b0007 +00020050005d006e000000e50a75000500014bb800c85258b101018e59b9080008006320b001 +2344b0032370b017452020b0286066208a5558b0022561b00145632362b0022344b20b01062a +b20c06062ab21406062a59b2042809455244b20c08072ab1060144b12401885158b0408858b1 +060344b12601885158b804008858b106014459595959b801ff85b0048db105004400000000> +] def +/f-0-0 currentdict end definefont pop +%%EndResource +%%BeginResource: font Carlito-Italic +11 dict begin +/FontType 42 def +/FontName /Carlito-Italic def +/PaintType 0 def +/FontMatrix [ 1 0 0 1 0 0 ] def +/FontBBox [ 0 0 0 0 ] def +/Encoding 256 array def +0 1 255 { Encoding exch /.notdef put } for +Encoding 1 /g1 put +/CharStrings 2 dict dup begin +/.notdef 0 def +/g1 1 def +end readonly def +/sfnts [ +<0001000000090080000300106376742025f401d600000300000000386670676ddbb62e8c0000 +033800000a75676c79668b98e26d0000009c00000264686561647d2f70dd00000db000000036 +686865610e3e08e000000de800000024686d74780921009900000e0c000000086c6f63610000 +03ac00000e140000000c6d61787001c20c4500000e2000000020707265709a783a3800000e40 +000000810004002b000003d9056700250039003d00410088b50001020001424bb02050584034 +000302010203016800010402010466000000020300025b000400050804055b00090906510006 +060e4300080807510007070d07441b4032000302010203016800010402010466000600090006 +0959000000020300025b000400050804055b00080807510007070d074459400d414011111428 +26232a1c240a182b133e0333321e0215140e040f012327263e0435342623220e022322271334 +3e0233321e0215140e0223222e020121112137211121f11838424c2c3d6448271d2b342f2104 +11770d041a2b352d1f45362736271a0b190c5f101d271716271c11111c271617271d10fead03 +aefc52320345fcbb044d15251d10223e5936354c392a2524166471213129262d372730391115 +1115fd1916281d11111d281617271c11111c270463fa993504fd00000001006efff1052f050f +003e005940563b260207083c2702000702420c0108010701080768040102020c430a06020101 +0351050103030f430b0107070053090d0200001500440100393734322f2e2b2924221f1d1a19 +1817161411100f0d0604003e013e0e0f2b05222637132322263f02133e013b010321133e013b +010321072103061633323e023332161f010e0123222637132103061633323e023332161f010e +010183706b0f49710e1302089e4e041610552a01c04d041710562a01120efeed4908332d1b28 +1e15080808052a318042716b0f4afe464808332d1b281e15080809052831800f807902591412 +431301300e12feaf01310f11feaf7bfdb23e3c0f110f0808512a2e80790259fdb23f3b0f110f +0808512a2e0000000000000000000000000000000000000000b0008900b00089052200000587 +03d20000feb6079efdda0531fff2058703e0fff2feb6079efddab0002cb02060662db0012c20 +6420b0c050b004265ab004455b582123211b8a5820b050505821b040591b20b038505821b038 +595920b00b456164b028505821b00b4520b030505821b030591b20b0c050582066208a8a6120 +b00a5058601b20b020505821b00a601b20b036505821b036601b605959591bb0002b595923b0 +0050586559592db0022c204520b00425616420b005435058b0052342b00623421b212159b001 +602db0032c232123212064b105624220b0062342b20b01022a2120b00643208a208ab0002bb1 +3005258a515860501b6152595823592120b0405358b0002b1b21b0405923b000505865592db0 +042cb007432bb20002004360422db0052cb00723422320b000234261b0026266b00163b00160 +b0042a2db0062c20204520b0024563b804006220b0005058b040605966b001636044b001602d +b0072c20204520b0002b23b10804256020458a2361206420b020505821b0001bb0305058b020 +1bb040595923b00050586559b0032523614444b001602db0082cb1050545b00161442db0092c +b001602020b009434ab000505820b009234259b00a434ab000525820b00a2342592db00a2c20 +b0106266b0016320b80400638a2361b00b4360208a6020b00b2342232db00b2c4b5458b10701 +445924b00d6523782db00c2c4b51584b5358b1070144591b215924b0136523782db00d2cb100 +0c435558b10c0c43b0016142b00a2b59b00043b0022542b109022542b10a022542b001162320 +b003255058b101004360b00425428a8a208a2361b0092a2123b00161208a2361b0092a211bb1 +01004360b0022542b0022561b0092a2159b0094347b00a434760b0026220b0005058b0406059 +66b0016320b0024563b804006220b0005058b040605966b0016360b10000132344b00143b000 +3eb20101014360422db00e2cb1000545545800b00c23422060b00161b50d0d01000b0042428a +60b10d052bb06d2b1b22592db00f2cb1000e2b2db0102cb1010e2b2db0112cb1020e2b2db012 +2cb1030e2b2db0132cb1040e2b2db0142cb1050e2b2db0152cb1060e2b2db0162cb1070e2b2d +b0172cb1080e2b2db0182cb1090e2b2db0192cb0082bb1000545545800b00c23422060b00161 +b50d0d01000b0042428a60b10d052bb06d2b1b22592db01a2cb100192b2db01b2cb101192b2d +b01c2cb102192b2db01d2cb103192b2db01e2cb104192b2db01f2cb105192b2db0202cb10619 +2b2db0212cb107192b2db0222cb108192b2db0232cb109192b2db0242c203cb001602db0252c +2060b00d60204323b0016043b0022561b00160b0242a212db0262cb0252bb0252a2db0272c20 +20472020b0024563b804006220b0005058b040605966b001636023613823208a555820472020 +b0024563b804006220b0005058b040605966b00163602361381b21592db0282cb10005455458 +00b00116b0272ab00115301b22592db0292cb0082bb1000545545800b00116b0272ab0011530 +1b22592db02a2c2035b001602db02b2c00b0034563b804006220b0005058b040605966b00163 +b0002bb0024563b804006220b0005058b040605966b00163b0002bb00016b40000000000443e +2338b12a01152a2db02c2c203c204720b0024563b804006220b0005058b040605966b0016360 +b0004361382db02d2c2e173c2db02e2c203c204720b0024563b804006220b0005058b0406059 +66b0016360b0004361b0014363382db02f2cb102001625202e2047b0002342b00225498a8a47 +234723612058621b2159b0012342b22e010115142a2db0302cb00016b00425b0042547234723 +61b006452b658a2e2320203c8a382db0312cb00016b00425b00425202e472347236120b00423 +42b006452b20b060505820b0405158b3022003201bb30226031a5942422320b00843208a2347 +23472361234660b00443b0026220b0005058b040605966b001636020b0002b208a8a6120b002 +43606423b0034361645058b00243611bb003436059b00325b0026220b0005058b040605966b0 +016361232020b00426234661381b23b0084346b00225b0084347234723616020b00443b00262 +20b0005058b040605966b00163602320b0002b23b0044360b0002bb0052561b00525b0026220 +b0005058b040605966b00163b004266120b00425606423b0032560645058211b232159232020 +b0042623466138592db0322cb00016202020b00526202e4723472361233c382db0332cb00016 +20b0082342202020462347b0002b2361382db0342cb00016b00325b002254723472361b00054 +582e203c23211bb00225b00225472347236120b00525b004254723472361b00625b0052549b0 +022561b0014563232058621b215963b804006220b0005058b040605966b0016360232e232020 +3c8a382321592db0352cb0001620b00843202e47234723612060b0206066b0026220b0005058 +b040605966b001632320203c8a382db0362c23202e46b00225465258203c592eb12601142b2d +b0372c23202e46b00225465058203c592eb12601142b2db0382c23202e46b00225465258203c +5923202e46b00225465058203c592eb12601142b2db0392cb0302b23202e46b0022546525820 +3c592eb12601142b2db03a2cb0312b8a20203cb00423428a3823202e46b00225465258203c59 +2eb12601142bb004432eb0262b2db03b2cb00016b00425b00426202e4723472361b006452b23 +203c202e2338b12601142b2db03c2cb108042542b00016b00425b00425202e472347236120b0 +042342b006452b20b060505820b0405158b3022003201bb30226031a594242232047b00443b0 +026220b0005058b040605966b001636020b0002b208a8a6120b00243606423b0034361645058 +b00243611bb003436059b00325b0026220b0005058b040605966b0016361b002254661382320 +3c23381b212020462347b0002b2361382159b12601142b2db03d2cb0302b2eb12601142b2db0 +3e2cb0312b212320203cb00423422338b12601142bb004432eb0262b2db03f2cb000152047b0 +002342b20001011514132eb02c2a2db0402cb000152047b0002342b20001011514132eb02c2a +2db0412cb100011413b02d2a2db0422cb02f2a2db0432cb000164523202e20468a236138b126 +01142b2db0442cb0082342b0432b2db0452cb200003c2b2db0462cb200013c2b2db0472cb201 +003c2b2db0482cb201013c2b2db0492cb200003d2b2db04a2cb200013d2b2db04b2cb201003d +2b2db04c2cb201013d2b2db04d2cb20000392b2db04e2cb20001392b2db04f2cb20100392b2d +b0502cb20101392b2db0512cb200003b2b2db0522cb200013b2b2db0532cb201003b2b2db054 +2cb201013b2b2db0552cb200003e2b2db0562cb200013e2b2db0572cb201003e2b2db0582cb2 +01013e2b2db0592cb200003a2b2db05a2cb200013a2b2db05b2cb201003a2b2db05c2cb20101 +3a2b2db05d2cb0322b2eb12601142b2db05e2cb0322bb0362b2db05f2cb0322bb0372b2db060 +2cb00016b0322bb0382b2db0612cb0332b2eb12601142b2db0622cb0332bb0362b2db0632cb0 +332bb0372b2db0642cb0332bb0382b2db0652cb0342b2eb12601142b2db0662cb0342bb0362b +2db0672cb0342bb0372b2db0682cb0342bb0382b2db0692cb0352b2eb12601142b2db06a2cb0 +352bb0362b2db06b2cb0352bb0372b2db06c2cb0352bb0382b2db06d2c2bb00865b003245078 +b00115302d0000000001000000011a5e80b9c6115f0f3cf5001b0800000000004a53db260000 +0000ce2e3053fc98fdec08e4081e000200060002000100000000000100000600fe0001c40aa0 +fc98f83e08e4080000fb0000000000000000000000000002040e002b0513006e000000000000 +01480000026400010000000200e50010008b000700020050005d006e000000e50a7500050001 +4bb800c85258b101018e59b9080008006320b0012344b0032370b017452020b0286066208a55 +58b0022561b00145632362b0022344b20b01062ab20c06062ab21406062a59b2042809455244 +b20c08072ab1060144b12401885158b0408858b1060344b12601885158b804008858b1060144 +59595959b801ff85b0048db105004400000000> +] def +/f-0-1 currentdict end definefont pop +%%EndResource +%%BeginResource: font Carlito-BoldItalic +11 dict begin +/FontType 42 def +/FontName /Carlito-BoldItalic def +/PaintType 0 def +/FontMatrix [ 1 0 0 1 0 0 ] def +/FontBBox [ 0 0 0 0 ] def +/Encoding 256 array def +0 1 255 { Encoding exch /.notdef put } for +Encoding 66 /B put +Encoding 83 /S put +Encoding 97 /a put +Encoding 99 /c put +Encoding 101 /e put +Encoding 105 /i put +Encoding 108 /l put +/CharStrings 8 dict dup begin +/.notdef 0 def +/B 1 def +/e 2 def +/i 3 def +/l 4 def +/S 5 def +/c 6 def +/a 7 def +end readonly def +/sfnts [ +<0001000000090080000300106376742026d70276000006e4000000386670676ddbb62e8c0000 +071c00000a75676c7966d604c9440000009c00000648686561647d5a70d00000119400000036 +686865610e680895000011cc00000024686d74781ba40163000011f0000000206c6f63610000 +1d6c00001210000000246d61787001bc0bc90000123400000020707265709a783a3800001254 +0000008100040028000003ff057600230037003b003f0088b50001020001424bb01a50584034 +000302010203016800010402010466000000020300025b000400050804055b00090906510006 +060e4300080807510007070d07441b4032000302010203016800010402010466000600090006 +0959000000020300025b000400050804055b00080807510007070d074459400d3f3e11111428 +26232a1c220a182b133e0133321e0215140e040f012327263e0435342623220e022322271334 +3e0233321e0215140e0223222e020121112137211121e5388e61466e4c281a29302b220516ae +12061527312b1d292a222d211a0f2410481525321e1c3225151525321c1e322515feb803d7fc +2941034cfcb404552d3c25445f3a354c39292322155b6d24342a23272f211f260c0f0c1efd4a +1c3225151525321c1d3125151525310460fa8a4504ec0000000300410000045605300013001e +0027003d403a0c010304014200040701030204035b00050500530000000c4300020201530601 +01010d0144141400002725211f141e141d1715001300122108102b331321321e0215140e0207 +1615140e02230b0133323e0235342623273332363534262b0141a201bf74a669311f406445e2 +4785c179a52fe7425e3c1d5f6bd2b47f886266c405302b50714638665746173ac55b9d734202 +44fe80223d54334753ae6b76524900020031fff103c403ef002300320063400b290502010410 +01000102424bb00f5058401e0001040004010068050104040353000303174300000002530002 +021502441b401e00010400040100680501040403530003031743000000025300020218024459 +400c2524243225322826232706132b01140e010407141633323e0233321f010e0323222e0235 +343e0233321e0225220e02073e0335342e0203c43b96fefdc76f72485d42331f1b1342386a71 +7d49609d6f3d5195d3835681552bfe9d365d4a361086a45a1e0e1f3002f2426d573e12767120 +2720164e344b31173c6f9b6075d9a6642d495a2025435f3910252b311c0f1d180f0000020043 +000001f0059d000300170026402300020203530003030e43040101010f430000000d00440000 +14120a08000300031105102b0103231301140e0223222e0235343e0233321e0201b477fa7701 +361b2d391e1d372a1a1a2b371e1e392c1a03e1fc1f03e1011d20392b19192b3920203a2b1a19 +2c390001003f000001e7059d0003001840150000000e43020101010d01440000000300031103 +102b331333033fadfbae059dfa6300000001fff4fff203cc053d003f003d403a3f0101052001 +0204024200000103010003680003040103046600010105530005051443000404025300020215 +02443b392a2825231c1a232206112b010e0123222e0223220e0215141e0615140e0223222e02 +27373e0133321e0233323635342e0635343e0233321e021703810e1a141229394d363450371c +2d4b5e635f4b2d4684bb753e766a5a215a0a2211172f4059416e7a2d4a5e625e4a2d427bb26f +396a5c4c1c043b13111d221d1c314327293a2d242831455d4161ac814c192d4027760d13262e +2670642d3d2b21252e456347569e7a48162938210001002efff1036103ef002e0069400a1401 +03010001000402424bb00f505840240002030503020568000504030504660003030153000101 +174300040400530000001500441b402400020305030205680005040305046600030301530001 +011743000404005300000018004459b7232623252a2406152b250e0323222e0235343e043332 +1617070e0123222e0223220e0215141633323e023332161f01032e31595e673f57895f332342 +5e75894c648c365208180e111b22332935604a2c5d4e36443027180d170941b036492d133b6d +995f51998671502d45475f0b0e1316133a6a965d6a711e241e0c0b4e00020025fff103f603f0 +00170028005a400b170104021e0502030402424bb02250584017000404025300020217430501 +03030053010100000d00441b401b000404025300020217430000000d43050103030153000101 +18014459400d1918221f182819282a252006122b212322263f010e0123222e0235343e043332 +161701323e0237132e0123220e021514160380873124020846b36c3d6b4f2d2f587c9ab36348 +9046fdb52d554b3f16200e1c0d56946c3e4931237e68792c5a875b59a7947a58311419fcfd3a +65874d0108010147789f59695d0000000000000000000000000000000000000000fe00c200fe +00c205300000059d03e10000febe079efdda053ffff2059d03effff1febe079efddab0002cb0 +2060662db0012c206420b0c050b004265ab004455b582123211b8a5820b050505821b040591b +20b038505821b038595920b00b456164b028505821b00b4520b030505821b030591b20b0c050 +582066208a8a6120b00a5058601b20b020505821b00a601b20b036505821b036601b60595959 +1bb0002b595923b00050586559592db0022c204520b00425616420b005435058b0052342b006 +23421b212159b001602db0032c232123212064b105624220b0062342b20b01022a2120b00643 +208a208ab0002bb13005258a515860501b6152595823592120b0405358b0002b1b21b0405923 +b000505865592db0042cb007432bb20002004360422db0052cb00723422320b000234261b002 +6266b00163b00160b0042a2db0062c20204520b0024563b804006220b0005058b040605966b0 +01636044b001602db0072c20204520b0002b23b10804256020458a2361206420b020505821b0 +001bb0305058b0201bb040595923b00050586559b0032523614444b001602db0082cb1050545 +b00161442db0092cb001602020b009434ab000505820b009234259b00a434ab000525820b00a +2342592db00a2c20b0106266b0016320b80400638a2361b00b4360208a6020b00b2342232db0 +0b2c4b5458b10701445924b00d6523782db00c2c4b51584b5358b1070144591b215924b01365 +23782db00d2cb1000c435558b10c0c43b0016142b00a2b59b00043b0022542b109022542b10a +022542b001162320b003255058b101004360b00425428a8a208a2361b0092a2123b00161208a +2361b0092a211bb101004360b0022542b0022561b0092a2159b0094347b00a434760b0026220 +b0005058b040605966b0016320b0024563b804006220b0005058b040605966b0016360b10000 +132344b00143b0003eb20101014360422db00e2cb1000545545800b00c23422060b00161b50d +0d01000b0042428a60b10d052bb06d2b1b22592db00f2cb1000e2b2db0102cb1010e2b2db011 +2cb1020e2b2db0122cb1030e2b2db0132cb1040e2b2db0142cb1050e2b2db0152cb1060e2b2d +b0162cb1070e2b2db0172cb1080e2b2db0182cb1090e2b2db0192cb0082bb1000545545800b0 +0c23422060b00161b50d0d01000b0042428a60b10d052bb06d2b1b22592db01a2cb100192b2d +b01b2cb101192b2db01c2cb102192b2db01d2cb103192b2db01e2cb104192b2db01f2cb10519 +2b2db0202cb106192b2db0212cb107192b2db0222cb108192b2db0232cb109192b2db0242c20 +3cb001602db0252c2060b00d60204323b0016043b0022561b00160b0242a212db0262cb0252b +b0252a2db0272c2020472020b0024563b804006220b0005058b040605966b001636023613823 +208a555820472020b0024563b804006220b0005058b040605966b00163602361381b21592db0 +282cb1000545545800b00116b0272ab00115301b22592db0292cb0082bb1000545545800b001 +16b0272ab00115301b22592db02a2c2035b001602db02b2c00b0034563b804006220b0005058 +b040605966b00163b0002bb0024563b804006220b0005058b040605966b00163b0002bb00016 +b40000000000443e2338b12a01152a2db02c2c203c204720b0024563b804006220b0005058b0 +40605966b0016360b0004361382db02d2c2e173c2db02e2c203c204720b0024563b804006220 +b0005058b040605966b0016360b0004361b0014363382db02f2cb102001625202e2047b00023 +42b00225498a8a47234723612058621b2159b0012342b22e010115142a2db0302cb00016b004 +25b004254723472361b006452b658a2e2320203c8a382db0312cb00016b00425b00425202e47 +2347236120b0042342b006452b20b060505820b0405158b3022003201bb30226031a59424223 +20b00843208a234723472361234660b00443b0026220b0005058b040605966b001636020b000 +2b208a8a6120b00243606423b0034361645058b00243611bb003436059b00325b0026220b000 +5058b040605966b0016361232020b00426234661381b23b0084346b00225b008434723472361 +6020b00443b0026220b0005058b040605966b00163602320b0002b23b0044360b0002bb00525 +61b00525b0026220b0005058b040605966b00163b004266120b00425606423b0032560645058 +211b232159232020b0042623466138592db0322cb00016202020b00526202e4723472361233c +382db0332cb0001620b0082342202020462347b0002b2361382db0342cb00016b00325b00225 +4723472361b00054582e203c23211bb00225b00225472347236120b00525b004254723472361 +b00625b0052549b0022561b0014563232058621b215963b804006220b0005058b040605966b0 +016360232e2320203c8a382321592db0352cb0001620b00843202e47234723612060b0206066 +b0026220b0005058b040605966b001632320203c8a382db0362c23202e46b00225465258203c +592eb12601142b2db0372c23202e46b00225465058203c592eb12601142b2db0382c23202e46 +b00225465258203c5923202e46b00225465058203c592eb12601142b2db0392cb0302b23202e +46b00225465258203c592eb12601142b2db03a2cb0312b8a20203cb00423428a3823202e46b0 +0225465258203c592eb12601142bb004432eb0262b2db03b2cb00016b00425b00426202e4723 +472361b006452b23203c202e2338b12601142b2db03c2cb108042542b00016b00425b0042520 +2e472347236120b0042342b006452b20b060505820b0405158b3022003201bb30226031a5942 +42232047b00443b0026220b0005058b040605966b001636020b0002b208a8a6120b002436064 +23b0034361645058b00243611bb003436059b00325b0026220b0005058b040605966b0016361 +b0022546613823203c23381b212020462347b0002b2361382159b12601142b2db03d2cb0302b +2eb12601142b2db03e2cb0312b212320203cb00423422338b12601142bb004432eb0262b2db0 +3f2cb000152047b0002342b20001011514132eb02c2a2db0402cb000152047b0002342b20001 +011514132eb02c2a2db0412cb100011413b02d2a2db0422cb02f2a2db0432cb000164523202e +20468a236138b12601142b2db0442cb0082342b0432b2db0452cb200003c2b2db0462cb20001 +3c2b2db0472cb201003c2b2db0482cb201013c2b2db0492cb200003d2b2db04a2cb200013d2b +2db04b2cb201003d2b2db04c2cb201013d2b2db04d2cb20000392b2db04e2cb20001392b2db0 +4f2cb20100392b2db0502cb20101392b2db0512cb200003b2b2db0522cb200013b2b2db0532c +b201003b2b2db0542cb201013b2b2db0552cb200003e2b2db0562cb200013e2b2db0572cb201 +003e2b2db0582cb201013e2b2db0592cb200003a2b2db05a2cb200013a2b2db05b2cb201003a +2b2db05c2cb201013a2b2db05d2cb0322b2eb12601142b2db05e2cb0322bb0362b2db05f2cb0 +322bb0372b2db0602cb00016b0322bb0382b2db0612cb0332b2eb12601142b2db0622cb0332b +b0362b2db0632cb0332bb0372b2db0642cb0332bb0382b2db0652cb0342b2eb12601142b2db0 +662cb0342bb0362b2db0672cb0342bb0372b2db0682cb0342bb0382b2db0692cb0352b2eb126 +01142b2db06a2cb0352bb0362b2db06b2cb0352bb0372b2db06c2cb0352bb0382b2db06d2c2b +b00865b003245078b00115302d0000000001000000011a5ec477a00b5f0f3cf5001b08000000 +00004a53db2600000000ce2e3052fc8dfdd60919082800030006000200010000000000010000 +0600fe0001c40aa0fc8df7ed0919080000fb0000000000000000000000000008040e0028047c +004103ee003101f7004301f7003f03b9fff4034b002e043900250000000000000144000001f8 +000002f0000003680000039c00000484000005700000064800010000000800f5001000000000 +00020050005d006e000000e50a75000000004bb800c85258b101018e59b9080008006320b001 +2344b0032370b017452020b0286066208a5558b0022561b00145632362b0022344b20b01062a +b20c06062ab21406062a59b2042809455244b20c08072ab1060144b12401885158b0408858b1 +060344b12601885158b804008858b106014459595959b801ff85b0048db105004400000000> +] def +/f-1-0 currentdict end definefont pop +%%EndResource +%%EndSetup +%%Page: 1 1 +%%BeginPageSetup +%%PageBoundingBox: 28 28 208 121 +%%EndPageSetup +q 28 28 180 93 rectclip +1 0 0 -1 0 149 cm q +0.282353 0.215686 0.215686 rg +82.383 84.922 m 67.375 93.586 l 60.023 97.832 59.227 97.551 52.367 93.586 + c 44.863 89.254 l 37.355 84.922 l 30.008 80.68 29.852 79.848 29.852 71.926 + c 29.852 54.594 l 29.852 46.105 30.496 45.559 37.355 41.598 c 44.863 37.262 + l 52.367 32.93 l 59.715 28.688 60.512 28.969 67.375 32.93 c 74.879 37.262 + l 82.383 41.598 l 89.734 45.84 89.887 46.668 89.887 54.594 c 89.887 71.926 + l 89.887 80.41 89.246 80.961 82.383 84.922 c h +82.383 84.922 m f +3.012722 w +1 J +1 j +[] 0.0 d +4 M q 1 0 0 1 0 0 cm +82.383 84.922 m 67.375 93.586 l 60.023 97.832 59.227 97.551 52.367 93.586 + c 44.863 89.254 l 37.355 84.922 l 30.008 80.68 29.852 79.848 29.852 71.926 + c 29.852 54.594 l 29.852 46.105 30.496 45.559 37.355 41.598 c 44.863 37.262 + l 52.367 32.93 l 59.715 28.688 60.512 28.969 67.375 32.93 c 74.879 37.262 + l 82.383 41.598 l 89.734 45.84 89.887 46.668 89.887 54.594 c 89.887 71.926 + l 89.887 80.41 89.246 80.961 82.383 84.922 c h +82.383 84.922 m S Q +1 0.8 0 rg +8.278126 w +q 1 0 0 1 0 0 cm +43.355 55.277 m 48.305 53.184 53.746 52.027 59.461 52.027 c 65.172 52.027 + 70.613 53.184 75.566 55.277 c S Q +q 1 0 0 1 0 0 cm +47.824 65.859 m 51.398 64.344 55.332 63.508 59.461 63.508 c 63.586 63.508 + 67.52 64.344 71.094 65.859 c S Q +q 1 0 0 1 0 0 cm +52.301 76.453 m 54.5 75.52 56.918 75.008 59.461 75.008 c 62 75.008 64.418 + 75.52 66.621 76.453 c S Q +0.282353 0.215686 0.215686 rg +6.025443 w +0 j +q 1 0 0 1 0 0 cm +46.73 39.398 m 42.965 31.867 l S Q +q 1 0 0 1 0 0 cm +73.09 39.398 m 76.855 31.867 l S Q +BT +14 0 0 -14 55.6118 117.547745 Tm +/f-0-0 1 Tf +(h)Tj +ET +0.31561 w +0 J +q 1 0 0 1 0 0 cm +56.16 117.547 m 57.332 107.828 l 58.52 107.828 l 57.973 112.359 l 58.336 + 111.84 58.738 111.445 59.176 111.172 c 59.613 110.902 60.055 110.766 60.504 + 110.766 c 60.793 110.766 61.051 110.824 61.27 110.938 c 61.496 111.043 +61.68 111.203 61.816 111.422 c 61.961 111.641 62.055 111.906 62.098 112.219 + c 62.148 112.531 62.152 112.887 62.113 113.281 c 61.613 117.547 l 60.41 + 117.547 l 60.91 113.281 l 60.973 112.762 60.934 112.375 60.801 112.125 +c 60.676 111.875 60.43 111.75 60.066 111.75 c 59.867 111.75 59.66 111.809 + 59.441 111.922 c 59.223 112.027 59.012 112.18 58.816 112.375 c 58.617 112.562 + 58.426 112.797 58.238 113.078 c 58.059 113.359 57.91 113.672 57.785 114.016 + c 57.363 117.547 l h +56.16 117.547 m S Q +BT +14 0 0 -14 62.666488 117.547745 Tm +/f-0-1 1 Tf +<01>Tj +ET +q 1 0 0 1 0 0 cm +65.309 117.656 m 64.797 117.656 64.422 117.512 64.184 117.219 c 63.941 +116.93 63.855 116.508 63.918 115.953 c 64.418 111.844 l 63.652 111.844 l + 63.578 111.844 63.52 111.824 63.48 111.781 c 63.438 111.73 63.422 111.664 + 63.434 111.578 c 63.496 111.125 l 64.574 110.984 l 65.105 108.906 l 65.125 + 108.844 65.156 108.793 65.199 108.75 c 65.25 108.711 65.312 108.688 65.387 + 108.688 c 65.965 108.688 l 65.684 111 l 68.746 111 l 69.277 108.906 l 69.285 + 108.844 69.316 108.793 69.371 108.75 c 69.422 108.711 69.484 108.688 69.559 + 108.688 c 70.152 108.688 l 69.871 111 l 71.746 111 l 71.637 111.844 l 69.762 + 111.844 l 69.262 115.875 l 69.23 116.156 69.27 116.367 69.387 116.5 c 69.5 + 116.637 69.66 116.703 69.871 116.703 c 69.996 116.703 70.098 116.688 70.184 + 116.656 c 70.277 116.625 70.355 116.59 70.418 116.547 c 70.488 116.508 +70.547 116.469 70.59 116.438 c 70.641 116.406 70.688 116.391 70.73 116.391 + c 70.762 116.391 70.785 116.402 70.809 116.422 c 70.828 116.434 70.848 +116.461 70.871 116.5 c 71.152 117.047 l 70.934 117.234 70.676 117.387 70.387 + 117.5 c 70.094 117.602 69.797 117.656 69.496 117.656 c 68.984 117.656 68.605 + 117.512 68.355 117.219 c 68.113 116.93 68.027 116.508 68.09 115.953 c 68.605 + 111.844 l 65.574 111.844 l 65.09 115.875 l 65.047 116.156 65.082 116.367 + 65.199 116.5 c 65.324 116.637 65.484 116.703 65.684 116.703 c 65.809 116.703 + 65.918 116.688 66.012 116.656 c 66.105 116.625 66.184 116.59 66.246 116.547 + c 66.316 116.508 66.375 116.469 66.418 116.438 c 66.469 116.406 66.516 +116.391 66.559 116.391 c 66.59 116.391 66.613 116.402 66.637 116.422 c 66.656 + 116.434 66.676 116.461 66.699 116.5 c 66.98 117.047 l 66.75 117.234 66.488 + 117.387 66.199 117.5 c 65.906 117.602 65.609 117.656 65.309 117.656 c h +65.309 117.656 m S Q +BT +14 0 0 -14 71.553207 117.547745 Tm +/f-0-0 1 Tf +(p)Tj +ET +q 1 0 0 1 0 0 cm +71.773 119.797 m 72.867 110.859 l 73.477 110.859 l 73.602 110.859 73.699 + 110.891 73.773 110.953 c 73.844 111.016 73.875 111.117 73.867 111.25 c +73.758 112.703 l 73.934 112.414 74.125 112.148 74.336 111.906 c 74.543 111.668 + 74.762 111.465 74.992 111.297 c 75.219 111.121 75.461 110.992 75.711 110.906 + c 75.961 110.812 76.215 110.766 76.477 110.766 c 77.09 110.766 77.562 110.977 + 77.898 111.391 c 78.23 111.809 78.398 112.418 78.398 113.219 c 78.398 113.586 + 78.355 113.949 78.273 114.312 c 78.199 114.668 78.09 115.012 77.945 115.344 + c 77.797 115.668 77.621 115.969 77.414 116.25 c 77.215 116.531 76.992 116.777 + 76.742 116.984 c 76.492 117.184 76.215 117.344 75.914 117.469 c 75.621 +117.582 75.312 117.641 74.992 117.641 c 74.648 117.641 74.328 117.578 74.039 + 117.453 c 73.746 117.32 73.5 117.133 73.305 116.891 c 72.945 119.797 l +h +76.039 111.719 m 75.809 111.719 75.578 111.789 75.352 111.922 c 75.121 +112.059 74.898 112.246 74.68 112.484 c 74.461 112.715 74.258 112.996 74.07 + 113.328 c 73.891 113.652 73.734 114.008 73.602 114.391 c 73.398 116.062 + l 73.586 116.305 73.805 116.477 74.055 116.578 c 74.305 116.672 74.559 +116.719 74.82 116.719 c 75.184 116.719 75.512 116.617 75.805 116.406 c 76.105 + 116.199 76.355 115.934 76.555 115.609 c 76.762 115.289 76.918 114.93 77.023 + 114.531 c 77.137 114.125 77.195 113.727 77.195 113.328 c 77.195 112.809 + 77.09 112.414 76.883 112.141 c 76.684 111.859 76.402 111.719 76.039 111.719 + c h +76.039 111.719 m S Q +BT +14 0 0 -14 78.689926 117.547745 Tm +/f-0-0 1 Tf +(s)Tj +ET +q 1 0 0 1 0 0 cm +83.707 111.938 m 83.676 111.992 83.637 112.031 83.598 112.062 c 83.566 +112.086 83.52 112.094 83.457 112.094 c 83.395 112.094 83.32 112.07 83.238 + 112.016 c 83.164 111.965 83.07 111.914 82.957 111.859 c 82.852 111.797 +82.723 111.746 82.566 111.703 c 82.41 111.652 82.215 111.625 81.988 111.625 + c 81.77 111.625 81.57 111.652 81.395 111.703 c 81.227 111.758 81.082 111.836 + 80.957 111.938 c 80.832 112.031 80.73 112.141 80.66 112.266 c 80.586 112.391 + 80.551 112.527 80.551 112.672 c 80.551 112.82 80.59 112.953 80.676 113.078 + c 80.758 113.195 80.867 113.297 81.004 113.391 c 81.148 113.484 81.309 +113.574 81.488 113.656 c 81.664 113.73 81.848 113.809 82.035 113.891 c 82.223 + 113.977 82.402 114.062 82.582 114.156 c 82.758 114.242 82.918 114.34 83.066 + 114.453 c 83.211 114.559 83.324 114.684 83.41 114.828 c 83.492 114.977 +83.535 115.141 83.535 115.328 c 83.535 115.641 83.465 115.938 83.332 116.219 + c 83.207 116.492 83.023 116.734 82.785 116.953 c 82.555 117.164 82.27 117.336 + 81.926 117.469 c 81.59 117.594 81.223 117.656 80.816 117.656 c 80.355 117.656 + 79.957 117.582 79.613 117.438 c 79.27 117.281 78.988 117.078 78.77 116.828 + c 79.066 116.391 l 79.137 116.266 79.242 116.203 79.379 116.203 c 79.449 + 116.203 79.523 116.234 79.598 116.297 c 79.668 116.359 79.762 116.43 79.879 + 116.5 c 79.992 116.574 80.133 116.641 80.301 116.703 c 80.477 116.766 80.695 + 116.797 80.957 116.797 c 81.195 116.797 81.402 116.766 81.582 116.703 c + 81.77 116.641 81.926 116.559 82.051 116.453 c 82.184 116.34 82.289 116.211 + 82.363 116.062 c 82.434 115.918 82.473 115.766 82.473 115.609 c 82.473 +115.383 82.398 115.199 82.254 115.062 c 82.105 114.918 81.918 114.793 81.691 + 114.688 c 81.473 114.586 81.23 114.492 80.973 114.406 c 80.723 114.312 +80.48 114.203 80.254 114.078 c 80.035 113.953 79.852 113.805 79.707 113.625 + c 79.559 113.449 79.488 113.219 79.488 112.938 c 79.488 112.656 79.543 +112.387 79.66 112.125 c 79.785 111.867 79.957 111.637 80.176 111.438 c 80.395 + 111.23 80.66 111.07 80.973 110.953 c 81.293 110.828 81.652 110.766 82.051 + 110.766 c 82.477 110.766 82.852 110.836 83.176 110.969 c 83.496 111.094 + 83.773 111.277 84.004 111.516 c h +83.707 111.938 m S Q +BT +14 0 0 -14 84.131332 117.547745 Tm +/f-0-0 1 Tf +(:)Tj +ET +q 1 0 0 1 0 0 cm +84.805 117.547 m 86.492 116.812 m 86.492 116.93 86.469 117.039 86.43 117.141 + c 86.387 117.246 86.324 117.336 86.242 117.406 c 86.156 117.48 86.062 117.539 + 85.961 117.578 c 85.867 117.629 85.762 117.656 85.648 117.656 c 85.531 +117.656 85.422 117.629 85.32 117.578 c 85.227 117.539 85.137 117.48 85.055 + 117.406 c 84.98 117.336 84.918 117.246 84.867 117.141 c 84.824 117.039 +84.805 116.93 84.805 116.812 c 84.805 116.699 84.824 116.59 84.867 116.484 + c 84.918 116.383 84.98 116.293 85.055 116.219 c 85.137 116.137 85.227 116.074 + 85.32 116.031 c 85.422 115.98 85.531 115.953 85.648 115.953 c 85.762 115.953 + 85.871 115.98 85.977 116.031 c 86.078 116.074 86.168 116.137 86.242 116.219 + c 86.324 116.293 86.387 116.383 86.43 116.484 c 86.469 116.59 86.492 116.699 + 86.492 116.812 c h +87.055 112.172 m 87.055 112.289 87.031 112.398 86.992 112.5 c 86.949 112.605 + 86.887 112.695 86.805 112.766 c 86.719 112.84 86.625 112.902 86.523 112.953 + c 86.43 112.996 86.324 113.016 86.211 113.016 c 86.094 113.016 85.984 112.996 + 85.883 112.953 c 85.789 112.902 85.699 112.84 85.617 112.766 c 85.543 112.695 + 85.48 112.605 85.43 112.5 c 85.387 112.398 85.367 112.289 85.367 112.172 + c 85.367 112.059 85.387 111.949 85.43 111.844 c 85.48 111.742 85.543 111.652 + 85.617 111.578 c 85.699 111.496 85.789 111.434 85.883 111.391 c 85.984 +111.34 86.094 111.312 86.211 111.312 c 86.324 111.312 86.434 111.34 86.539 + 111.391 c 86.641 111.434 86.73 111.496 86.805 111.578 c 86.887 111.652 +86.949 111.742 86.992 111.844 c 87.031 111.949 87.055 112.059 87.055 112.172 + c h +87.055 112.172 m S Q +BT +14 0 0 -14 87.877427 117.547745 Tm +/f-0-0 1 Tf +(/)Tj +ET +q 1 0 0 1 0 0 cm +88.441 117.703 m 88.367 117.848 88.27 117.957 88.145 118.031 c 88.02 118.113 + 87.895 118.156 87.77 118.156 c 87.27 118.156 l 92.832 108.406 l 92.895 +108.273 92.98 108.168 93.098 108.094 c 93.223 108.012 93.355 107.969 93.504 + 107.969 c 93.988 107.969 l h +88.441 117.703 m S Q +BT +14 0 0 -14 93.318833 117.547745 Tm +/f-0-0 1 Tf +(/)Tj +ET +q 1 0 0 1 0 0 cm +93.883 117.703 m 93.809 117.848 93.711 117.957 93.586 118.031 c 93.461 +118.113 93.336 118.156 93.211 118.156 c 92.711 118.156 l 98.273 108.406 +l 98.336 108.273 98.422 108.168 98.539 108.094 c 98.664 108.012 98.797 107.969 + 98.945 107.969 c 99.43 107.969 l h +93.883 117.703 m S Q +BT +14 0 0 -14 98.760239 117.547745 Tm +/f-0-0 1 Tf +(w)Tj +ET +q 1 0 0 1 0 0 cm +99.277 110.859 m 100.168 110.859 l 100.262 110.859 100.332 110.887 100.387 + 110.938 c 100.449 110.98 100.488 111.039 100.512 111.109 c 101.199 115.375 + l 101.219 115.531 101.234 115.684 101.246 115.828 c 101.266 115.977 101.277 + 116.121 101.277 116.266 c 101.328 116.121 101.387 115.977 101.449 115.828 + c 101.512 115.684 101.574 115.531 101.637 115.375 c 103.59 111.078 l 103.621 + 111.016 103.66 110.965 103.715 110.922 c 103.777 110.871 103.844 110.844 + 103.918 110.844 c 104.418 110.844 l 104.512 110.844 104.582 110.871 104.637 + 110.922 c 104.688 110.965 104.719 111.016 104.73 111.078 c 105.559 115.375 + l 105.59 115.531 105.613 115.684 105.637 115.828 c 105.668 115.977 105.688 + 116.121 105.699 116.266 c 105.738 116.121 105.781 115.98 105.824 115.844 + c 105.875 115.699 105.934 115.547 105.996 115.391 c 107.809 111.109 l 107.84 + 111.039 107.887 110.98 107.949 110.938 c 108.012 110.887 108.078 110.859 + 108.152 110.859 c 109.027 110.859 l 106.09 117.547 l 105.168 117.547 l +105.062 117.547 104.996 117.477 104.965 117.328 c 104.043 112.812 l 104.02 + 112.68 104 112.539 103.98 112.391 c 103.957 112.465 103.934 112.539 103.902 + 112.609 c 103.879 112.684 103.855 112.758 103.824 112.828 c 101.746 117.328 + l 101.684 117.477 101.594 117.547 101.48 117.547 c 100.605 117.547 l h +99.277 110.859 m S Q +BT +14 0 0 -14 108.795398 117.547745 Tm +/f-0-0 1 Tf +(w)Tj +ET +q 1 0 0 1 0 0 cm +109.312 110.859 m 110.203 110.859 l 110.297 110.859 110.367 110.887 110.422 + 110.938 c 110.484 110.98 110.523 111.039 110.547 111.109 c 111.234 115.375 + l 111.254 115.531 111.27 115.684 111.281 115.828 c 111.301 115.977 111.312 + 116.121 111.312 116.266 c 111.363 116.121 111.422 115.977 111.484 115.828 + c 111.547 115.684 111.609 115.531 111.672 115.375 c 113.625 111.078 l 113.656 + 111.016 113.695 110.965 113.75 110.922 c 113.812 110.871 113.879 110.844 + 113.953 110.844 c 114.453 110.844 l 114.547 110.844 114.617 110.871 114.672 + 110.922 c 114.723 110.965 114.754 111.016 114.766 111.078 c 115.594 115.375 + l 115.625 115.531 115.648 115.684 115.672 115.828 c 115.703 115.977 115.723 + 116.121 115.734 116.266 c 115.773 116.121 115.816 115.98 115.859 115.844 + c 115.91 115.699 115.969 115.547 116.031 115.391 c 117.844 111.109 l 117.875 + 111.039 117.922 110.98 117.984 110.938 c 118.047 110.887 118.113 110.859 + 118.188 110.859 c 119.062 110.859 l 116.125 117.547 l 115.203 117.547 l + 115.098 117.547 115.031 117.477 115 117.328 c 114.078 112.812 l 114.055 + 112.68 114.035 112.539 114.016 112.391 c 113.992 112.465 113.969 112.539 + 113.938 112.609 c 113.914 112.684 113.891 112.758 113.859 112.828 c 111.781 + 117.328 l 111.719 117.477 111.629 117.547 111.516 117.547 c 110.641 117.547 + l h +109.312 110.859 m S Q +BT +14 0 0 -14 118.830555 117.547745 Tm +/f-0-0 1 Tf +(w)Tj +ET +q 1 0 0 1 0 0 cm +119.348 110.859 m 120.238 110.859 l 120.332 110.859 120.402 110.887 120.457 + 110.938 c 120.52 110.98 120.559 111.039 120.582 111.109 c 121.27 115.375 + l 121.289 115.531 121.305 115.684 121.316 115.828 c 121.336 115.977 121.348 + 116.121 121.348 116.266 c 121.398 116.121 121.457 115.977 121.52 115.828 + c 121.582 115.684 121.645 115.531 121.707 115.375 c 123.66 111.078 l 123.691 + 111.016 123.73 110.965 123.785 110.922 c 123.848 110.871 123.914 110.844 + 123.988 110.844 c 124.488 110.844 l 124.582 110.844 124.652 110.871 124.707 + 110.922 c 124.758 110.965 124.789 111.016 124.801 111.078 c 125.629 115.375 + l 125.66 115.531 125.684 115.684 125.707 115.828 c 125.738 115.977 125.758 + 116.121 125.77 116.266 c 125.809 116.121 125.852 115.98 125.895 115.844 + c 125.945 115.699 126.004 115.547 126.066 115.391 c 127.879 111.109 l 127.91 + 111.039 127.957 110.98 128.02 110.938 c 128.082 110.887 128.148 110.859 + 128.223 110.859 c 129.098 110.859 l 126.16 117.547 l 125.238 117.547 l +125.133 117.547 125.066 117.477 125.035 117.328 c 124.113 112.812 l 124.09 + 112.68 124.07 112.539 124.051 112.391 c 124.027 112.465 124.004 112.539 + 123.973 112.609 c 123.949 112.684 123.926 112.758 123.895 112.828 c 121.816 + 117.328 l 121.754 117.477 121.664 117.547 121.551 117.547 c 120.676 117.547 + l h +119.348 110.859 m S Q +BT +14 0 0 -14 128.209456 117.547745 Tm +/f-0-0 1 Tf +(.)Tj +ET +q 1 0 0 1 0 0 cm +128.836 117.547 m 130.523 116.812 m 130.523 116.93 130.5 117.039 130.461 + 117.141 c 130.418 117.246 130.355 117.336 130.273 117.406 c 130.188 117.48 + 130.094 117.539 129.992 117.578 c 129.898 117.629 129.793 117.656 129.68 + 117.656 c 129.562 117.656 129.453 117.629 129.352 117.578 c 129.258 117.539 + 129.168 117.48 129.086 117.406 c 129.012 117.336 128.949 117.246 128.898 + 117.141 c 128.855 117.039 128.836 116.93 128.836 116.812 c 128.836 116.699 + 128.855 116.59 128.898 116.484 c 128.949 116.383 129.012 116.293 129.086 + 116.219 c 129.168 116.137 129.258 116.074 129.352 116.031 c 129.453 115.98 + 129.562 115.953 129.68 115.953 c 129.793 115.953 129.902 115.98 130.008 + 116.031 c 130.109 116.074 130.199 116.137 130.273 116.219 c 130.355 116.293 + 130.418 116.383 130.461 116.484 c 130.5 116.59 130.523 116.699 130.523 +116.812 c h +130.523 116.812 m S Q +BT +14 0 0 -14 131.736803 117.547745 Tm +/f-0-0 1 Tf +(m)Tj +ET +q 1 0 0 1 0 0 cm +132.27 117.547 m 133.066 110.859 l 133.66 110.859 l 133.793 110.859 133.895 + 110.891 133.957 110.953 c 134.027 111.016 134.059 111.117 134.051 111.25 + c 133.973 112.5 l 134.316 111.93 134.699 111.496 135.129 111.203 c 135.555 + 110.914 136.008 110.766 136.488 110.766 c 136.977 110.766 137.336 110.922 + 137.566 111.234 c 137.793 111.547 137.91 112 137.91 112.594 c 138.254 111.969 + 138.648 111.512 139.098 111.219 c 139.543 110.918 140.02 110.766 140.52 + 110.766 c 141.133 110.766 141.566 110.984 141.816 111.422 c 142.074 111.859 + 142.152 112.48 142.051 113.281 c 141.551 117.547 l 140.363 117.547 l 140.879 + 113.281 l 140.91 113.023 140.918 112.797 140.91 112.609 c 140.898 112.422 + 140.863 112.266 140.801 112.141 c 140.746 112.008 140.664 111.906 140.551 + 111.844 c 140.434 111.781 140.289 111.75 140.113 111.75 c 139.902 111.75 + 139.695 111.805 139.488 111.906 c 139.277 112 139.074 112.141 138.879 112.328 + c 138.691 112.516 138.512 112.75 138.348 113.031 c 138.18 113.305 138.035 + 113.617 137.91 113.969 c 137.488 117.547 l 136.316 117.547 l 136.816 113.281 + l 136.848 113.023 136.855 112.797 136.848 112.609 c 136.848 112.422 136.82 + 112.266 136.77 112.141 c 136.715 112.008 136.633 111.906 136.52 111.844 + c 136.402 111.781 136.258 111.75 136.082 111.75 c 135.852 111.75 135.629 + 111.809 135.41 111.922 c 135.191 112.027 134.988 112.184 134.801 112.391 + c 134.613 112.59 134.434 112.836 134.27 113.125 c 134.113 113.418 133.965 + 113.75 133.832 114.125 c 133.441 117.547 l h +132.27 117.547 m S Q +BT +14 0 0 -14 142.811022 117.547745 Tm +/f-0-0 1 Tf +(i)Tj +ET +q 1 0 0 1 0 0 cm +145.422 110.859 m 144.609 117.547 l 143.422 117.547 l 144.234 110.859 l + h +145.859 108.766 m 145.859 108.883 145.832 108.992 145.781 109.094 c 145.738 + 109.188 145.676 109.277 145.594 109.359 c 145.52 109.434 145.43 109.496 + 145.328 109.547 c 145.234 109.59 145.133 109.609 145.031 109.609 c 144.926 + 109.609 144.82 109.59 144.719 109.547 c 144.625 109.496 144.539 109.434 + 144.469 109.359 c 144.395 109.277 144.332 109.188 144.281 109.094 c 144.238 + 108.992 144.219 108.883 144.219 108.766 c 144.219 108.652 144.238 108.543 + 144.281 108.438 c 144.332 108.336 144.395 108.246 144.469 108.172 c 144.539 + 108.09 144.625 108.027 144.719 107.984 c 144.82 107.945 144.926 107.922 + 145.031 107.922 c 145.133 107.922 145.238 107.945 145.344 107.984 c 145.445 + 108.027 145.535 108.09 145.609 108.172 c 145.68 108.246 145.738 108.336 + 145.781 108.438 c 145.832 108.543 145.859 108.652 145.859 108.766 c h +145.859 108.766 m S Q +BT +14 0 0 -14 146.037585 117.547745 Tm +/f-0-0 1 Tf +(n)Tj +ET +q 1 0 0 1 0 0 cm +146.539 117.547 m 147.336 110.859 l 147.93 110.859 l 148.055 110.859 148.152 + 110.891 148.227 110.953 c 148.297 111.016 148.328 111.117 148.32 111.25 + c 148.227 112.562 l 148.59 111.969 149.008 111.523 149.477 111.219 c 149.945 + 110.918 150.422 110.766 150.914 110.766 c 151.203 110.766 151.461 110.824 + 151.68 110.938 c 151.906 111.043 152.09 111.203 152.227 111.422 c 152.371 + 111.641 152.469 111.906 152.523 112.219 c 152.574 112.531 152.574 112.887 + 152.523 113.281 c 152.023 117.547 l 150.836 117.547 l 151.336 113.281 l + 151.398 112.762 151.359 112.375 151.227 112.125 c 151.09 111.875 150.84 + 111.75 150.477 111.75 c 150.258 111.75 150.031 111.809 149.805 111.922 +c 149.586 112.039 149.367 112.203 149.148 112.422 c 148.938 112.633 148.742 + 112.887 148.555 113.188 c 148.375 113.48 148.227 113.809 148.102 114.172 + c 147.727 117.547 l h +146.539 117.547 m S Q +BT +14 0 0 -14 153.228991 117.547745 Tm +/f-0-0 1 Tf +(i)Tj +ET +q 1 0 0 1 0 0 cm +155.84 110.859 m 155.027 117.547 l 153.84 117.547 l 154.652 110.859 l h +156.277 108.766 m 156.277 108.883 156.25 108.992 156.199 109.094 c 156.156 + 109.188 156.094 109.277 156.012 109.359 c 155.938 109.434 155.848 109.496 + 155.746 109.547 c 155.652 109.59 155.551 109.609 155.449 109.609 c 155.344 + 109.609 155.238 109.59 155.137 109.547 c 155.043 109.496 154.957 109.434 + 154.887 109.359 c 154.812 109.277 154.75 109.188 154.699 109.094 c 154.656 + 108.992 154.637 108.883 154.637 108.766 c 154.637 108.652 154.656 108.543 + 154.699 108.438 c 154.75 108.336 154.812 108.246 154.887 108.172 c 154.957 + 108.09 155.043 108.027 155.137 107.984 c 155.238 107.945 155.344 107.922 + 155.449 107.922 c 155.551 107.922 155.656 107.945 155.762 107.984 c 155.863 + 108.027 155.953 108.09 156.027 108.172 c 156.098 108.246 156.156 108.336 + 156.199 108.438 c 156.25 108.543 156.277 108.652 156.277 108.766 c h +156.277 108.766 m S Q +BT +14 0 0 -14 156.455555 117.547745 Tm +/f-0-0 1 Tf +(-)Tj +ET +q 1 0 0 1 0 0 cm +157.238 113.109 m 160.27 113.109 l 160.145 114.094 l 157.113 114.094 l +h +157.238 113.109 m S Q +BT +14 0 0 -14 160.748517 117.547745 Tm +/f-0-0 1 Tf +(b)Tj +ET +q 1 0 0 1 0 0 cm +161.297 117.547 m 162.484 107.828 l 163.656 107.828 l 163.078 112.562 l + 163.242 112.293 163.426 112.047 163.625 111.828 c 163.832 111.602 164.047 + 111.406 164.266 111.25 c 164.484 111.094 164.711 110.977 164.953 110.891 + c 165.191 110.809 165.43 110.766 165.672 110.766 c 166.266 110.766 166.727 + 110.977 167.062 111.391 c 167.395 111.809 167.562 112.418 167.562 113.219 + c 167.562 113.586 167.52 113.949 167.438 114.312 c 167.363 114.668 167.254 + 115.012 167.109 115.344 c 166.973 115.668 166.801 115.969 166.594 116.25 + c 166.395 116.531 166.172 116.777 165.922 116.984 c 165.68 117.184 165.414 + 117.344 165.125 117.469 c 164.832 117.582 164.523 117.641 164.203 117.641 + c 163.836 117.641 163.504 117.566 163.203 117.422 c 162.898 117.266 162.648 + 117.047 162.453 116.766 c 162.344 117.203 l 162.32 117.32 162.281 117.406 + 162.219 117.469 c 162.164 117.523 162.07 117.547 161.938 117.547 c h +165.234 111.719 m 165.004 111.719 164.773 111.789 164.547 111.922 c 164.328 + 112.059 164.113 112.246 163.906 112.484 c 163.695 112.715 163.5 112.992 + 163.312 113.312 c 163.133 113.637 162.984 113.992 162.859 114.375 c 162.641 + 116.062 l 162.816 116.305 163.023 116.477 163.266 116.578 c 163.516 116.672 + 163.77 116.719 164.031 116.719 c 164.383 116.719 164.707 116.617 165 116.406 + c 165.289 116.199 165.535 115.934 165.734 115.609 c 165.941 115.289 166.098 + 114.93 166.203 114.531 c 166.316 114.125 166.375 113.727 166.375 113.328 + c 166.375 112.809 166.273 112.414 166.078 112.141 c 165.879 111.859 165.598 + 111.719 165.234 111.719 c h +165.234 111.719 m S Q +BT +14 0 0 -14 167.939933 117.547745 Tm +/f-0-0 1 Tf +(e)Tj +ET +q 1 0 0 1 0 0 cm +174.238 112.328 m 174.238 112.621 174.168 112.887 174.035 113.125 c 173.91 + 113.367 173.668 113.586 173.316 113.781 c 172.973 113.969 172.492 114.137 + 171.879 114.281 c 171.262 114.418 170.477 114.531 169.52 114.625 c 169.52 + 114.656 169.52 114.695 169.52 114.734 c 169.52 114.777 169.52 114.812 169.52 + 114.844 c 169.52 115.449 169.664 115.914 169.957 116.234 c 170.246 116.547 + 170.691 116.703 171.285 116.703 c 171.648 116.703 171.949 116.656 172.191 + 116.562 c 172.43 116.469 172.633 116.371 172.801 116.266 c 172.977 116.164 + 173.121 116.062 173.238 115.969 c 173.363 115.875 173.48 115.828 173.598 + 115.828 c 173.68 115.828 173.758 115.871 173.832 115.953 c 174.129 116.312 + l 173.879 116.543 173.633 116.742 173.395 116.906 c 173.164 117.074 172.93 + 117.215 172.691 117.328 c 172.449 117.434 172.195 117.512 171.926 117.562 + c 171.664 117.613 171.379 117.641 171.066 117.641 c 170.648 117.641 170.27 + 117.578 169.926 117.453 c 169.59 117.32 169.305 117.133 169.066 116.891 + c 168.836 116.652 168.66 116.367 168.535 116.031 c 168.41 115.688 168.348 + 115.305 168.348 114.875 c 168.348 114.531 168.383 114.195 168.457 113.859 + c 168.539 113.527 168.652 113.211 168.801 112.906 c 168.957 112.594 169.137 + 112.312 169.348 112.062 c 169.566 111.805 169.816 111.578 170.098 111.391 + c 170.379 111.195 170.684 111.043 171.02 110.938 c 171.352 110.824 171.711 + 110.766 172.098 110.766 c 172.473 110.766 172.793 110.82 173.066 110.922 + c 173.336 111.016 173.559 111.141 173.738 111.297 c 173.914 111.445 174.039 + 111.609 174.113 111.797 c 174.195 111.984 174.238 112.164 174.238 112.328 + c h +172.051 111.625 m 171.727 111.625 171.434 111.684 171.176 111.797 c 170.914 + 111.914 170.68 112.07 170.473 112.266 c 170.273 112.465 170.102 112.699 + 169.957 112.969 c 169.809 113.242 169.699 113.527 169.629 113.828 c 170.137 + 113.777 170.582 113.719 170.957 113.656 c 171.332 113.586 171.648 113.512 + 171.91 113.438 c 172.18 113.367 172.395 113.289 172.551 113.203 c 172.715 + 113.121 172.848 113.031 172.941 112.938 c 173.035 112.844 173.098 112.75 + 173.129 112.656 c 173.16 112.562 173.176 112.465 173.176 112.359 c 173.176 + 112.277 173.152 112.195 173.113 112.109 c 173.07 112.027 173.004 111.949 + 172.91 111.875 c 172.824 111.805 172.711 111.746 172.566 111.703 c 172.418 + 111.652 172.246 111.625 172.051 111.625 c h +172.051 111.625 m S Q +BT +14 0 0 -14 174.63915 117.547745 Tm +/f-0-0 1 Tf +(i)Tj +ET +q 1 0 0 1 0 0 cm +177.25 110.859 m 176.438 117.547 l 175.25 117.547 l 176.062 110.859 l h +177.688 108.766 m 177.688 108.883 177.66 108.992 177.609 109.094 c 177.566 + 109.188 177.504 109.277 177.422 109.359 c 177.348 109.434 177.258 109.496 + 177.156 109.547 c 177.062 109.59 176.961 109.609 176.859 109.609 c 176.754 + 109.609 176.648 109.59 176.547 109.547 c 176.453 109.496 176.367 109.434 + 176.297 109.359 c 176.223 109.277 176.16 109.188 176.109 109.094 c 176.066 + 108.992 176.047 108.883 176.047 108.766 c 176.047 108.652 176.066 108.543 + 176.109 108.438 c 176.16 108.336 176.223 108.246 176.297 108.172 c 176.367 + 108.09 176.453 108.027 176.547 107.984 c 176.648 107.945 176.754 107.922 + 176.859 107.922 c 176.961 107.922 177.066 107.945 177.172 107.984 c 177.273 + 108.027 177.363 108.09 177.438 108.172 c 177.508 108.246 177.566 108.336 + 177.609 108.438 c 177.66 108.543 177.688 108.652 177.688 108.766 c h +177.688 108.766 m S Q +BT +14 0 0 -14 177.865713 117.547745 Tm +/f-0-0 1 Tf +(e)Tj +ET +q 1 0 0 1 0 0 cm +184.164 112.328 m 184.164 112.621 184.094 112.887 183.961 113.125 c 183.836 + 113.367 183.594 113.586 183.242 113.781 c 182.898 113.969 182.418 114.137 + 181.805 114.281 c 181.188 114.418 180.402 114.531 179.445 114.625 c 179.445 + 114.656 179.445 114.695 179.445 114.734 c 179.445 114.777 179.445 114.812 + 179.445 114.844 c 179.445 115.449 179.59 115.914 179.883 116.234 c 180.172 + 116.547 180.617 116.703 181.211 116.703 c 181.574 116.703 181.875 116.656 + 182.117 116.562 c 182.355 116.469 182.559 116.371 182.727 116.266 c 182.902 + 116.164 183.047 116.062 183.164 115.969 c 183.289 115.875 183.406 115.828 + 183.523 115.828 c 183.605 115.828 183.684 115.871 183.758 115.953 c 184.055 + 116.312 l 183.805 116.543 183.559 116.742 183.32 116.906 c 183.09 117.074 + 182.855 117.215 182.617 117.328 c 182.375 117.434 182.121 117.512 181.852 + 117.562 c 181.59 117.613 181.305 117.641 180.992 117.641 c 180.574 117.641 + 180.195 117.578 179.852 117.453 c 179.516 117.32 179.23 117.133 178.992 + 116.891 c 178.762 116.652 178.586 116.367 178.461 116.031 c 178.336 115.688 + 178.273 115.305 178.273 114.875 c 178.273 114.531 178.309 114.195 178.383 + 113.859 c 178.465 113.527 178.578 113.211 178.727 112.906 c 178.883 112.594 + 179.062 112.312 179.273 112.062 c 179.492 111.805 179.742 111.578 180.023 + 111.391 c 180.305 111.195 180.609 111.043 180.945 110.938 c 181.277 110.824 + 181.637 110.766 182.023 110.766 c 182.398 110.766 182.719 110.82 182.992 + 110.922 c 183.262 111.016 183.484 111.141 183.664 111.297 c 183.84 111.445 + 183.965 111.609 184.039 111.797 c 184.121 111.984 184.164 112.164 184.164 + 112.328 c h +181.977 111.625 m 181.652 111.625 181.359 111.684 181.102 111.797 c 180.84 + 111.914 180.605 112.07 180.398 112.266 c 180.199 112.465 180.027 112.699 + 179.883 112.969 c 179.734 113.242 179.625 113.527 179.555 113.828 c 180.062 + 113.777 180.508 113.719 180.883 113.656 c 181.258 113.586 181.574 113.512 + 181.836 113.438 c 182.105 113.367 182.32 113.289 182.477 113.203 c 182.641 + 113.121 182.773 113.031 182.867 112.938 c 182.961 112.844 183.023 112.75 + 183.055 112.656 c 183.086 112.562 183.102 112.465 183.102 112.359 c 183.102 + 112.277 183.078 112.195 183.039 112.109 c 182.996 112.027 182.93 111.949 + 182.836 111.875 c 182.75 111.805 182.637 111.746 182.492 111.703 c 182.344 + 111.652 182.172 111.625 181.977 111.625 c h +181.977 111.625 m S Q +BT +14 0 0 -14 184.564929 117.547745 Tm +/f-0-0 1 Tf +(l)Tj +ET +q 1 0 0 1 0 0 cm +185.176 117.547 m 186.332 107.828 l 187.52 107.828 l 186.348 117.547 l +h +185.176 117.547 m S Q +BT +14 0 0 -14 187.791493 117.547745 Tm +/f-0-0 1 Tf +(i)Tj +ET +q 1 0 0 1 0 0 cm +190.402 110.859 m 189.59 117.547 l 188.402 117.547 l 189.215 110.859 l +h +190.84 108.766 m 190.84 108.883 190.812 108.992 190.762 109.094 c 190.719 + 109.188 190.656 109.277 190.574 109.359 c 190.5 109.434 190.41 109.496 +190.309 109.547 c 190.215 109.59 190.113 109.609 190.012 109.609 c 189.906 + 109.609 189.801 109.59 189.699 109.547 c 189.605 109.496 189.52 109.434 + 189.449 109.359 c 189.375 109.277 189.312 109.188 189.262 109.094 c 189.219 + 108.992 189.199 108.883 189.199 108.766 c 189.199 108.652 189.219 108.543 + 189.262 108.438 c 189.312 108.336 189.375 108.246 189.449 108.172 c 189.52 + 108.09 189.605 108.027 189.699 107.984 c 189.801 107.945 189.906 107.922 + 190.012 107.922 c 190.113 107.922 190.219 107.945 190.324 107.984 c 190.426 + 108.027 190.516 108.09 190.59 108.172 c 190.66 108.246 190.719 108.336 +190.762 108.438 c 190.812 108.543 190.84 108.652 190.84 108.766 c h +190.84 108.766 m S Q +BT +14 0 0 -14 191.018056 117.547745 Tm +/f-0-0 1 Tf +(.)Tj +ET +q 1 0 0 1 0 0 cm +191.645 117.547 m 193.332 116.812 m 193.332 116.93 193.309 117.039 193.27 + 117.141 c 193.227 117.246 193.164 117.336 193.082 117.406 c 192.996 117.48 + 192.902 117.539 192.801 117.578 c 192.707 117.629 192.602 117.656 192.488 + 117.656 c 192.371 117.656 192.262 117.629 192.16 117.578 c 192.066 117.539 + 191.977 117.48 191.895 117.406 c 191.82 117.336 191.758 117.246 191.707 + 117.141 c 191.664 117.039 191.645 116.93 191.645 116.812 c 191.645 116.699 + 191.664 116.59 191.707 116.484 c 191.758 116.383 191.82 116.293 191.895 + 116.219 c 191.977 116.137 192.066 116.074 192.16 116.031 c 192.262 115.98 + 192.371 115.953 192.488 115.953 c 192.602 115.953 192.711 115.98 192.816 + 116.031 c 192.918 116.074 193.008 116.137 193.082 116.219 c 193.164 116.293 + 193.227 116.383 193.27 116.484 c 193.309 116.59 193.332 116.699 193.332 + 116.812 c h +193.332 116.812 m S Q +BT +14 0 0 -14 194.545403 117.547745 Tm +/f-0-0 1 Tf +(c)Tj +ET +q 1 0 0 1 0 0 cm +200.062 116.344 m 199.82 116.594 199.598 116.805 199.391 116.969 c 199.18 + 117.137 198.969 117.273 198.75 117.375 c 198.539 117.469 198.32 117.539 + 198.094 117.578 c 197.875 117.617 197.633 117.641 197.375 117.641 c 196.977 + 117.641 196.629 117.57 196.328 117.438 c 196.023 117.305 195.77 117.117 + 195.562 116.875 c 195.363 116.637 195.207 116.352 195.094 116.016 c 194.988 + 115.672 194.938 115.297 194.938 114.891 c 194.938 114.359 195.02 113.844 + 195.188 113.344 c 195.363 112.844 195.602 112.406 195.906 112.031 c 196.219 + 111.648 196.586 111.34 197.016 111.109 c 197.441 110.883 197.91 110.766 + 198.422 110.766 c 198.867 110.766 199.242 110.844 199.547 111 c 199.859 + 111.156 200.129 111.387 200.359 111.688 c 199.984 112.141 l 199.953 112.172 + 199.914 112.203 199.875 112.234 c 199.832 112.258 199.789 112.266 199.75 + 112.266 c 199.688 112.266 199.625 112.242 199.562 112.188 c 199.5 112.125 + 199.422 112.062 199.328 112 c 199.234 111.93 199.109 111.867 198.953 111.812 + c 198.805 111.762 198.617 111.734 198.391 111.734 c 198.086 111.734 197.801 + 111.82 197.531 111.984 c 197.258 112.141 197.02 112.359 196.812 112.641 + c 196.602 112.922 196.438 113.262 196.312 113.656 c 196.195 114.043 196.141 + 114.461 196.141 114.906 c 196.141 115.18 196.172 115.422 196.234 115.641 + c 196.305 115.859 196.398 116.055 196.516 116.219 c 196.641 116.375 196.797 + 116.496 196.984 116.578 c 197.172 116.664 197.383 116.703 197.625 116.703 + c 197.926 116.703 198.176 116.664 198.375 116.578 c 198.582 116.484 198.754 + 116.387 198.891 116.281 c 199.023 116.18 199.141 116.086 199.234 116 c +199.336 115.906 199.438 115.859 199.531 115.859 c 199.613 115.859 199.688 + 115.902 199.75 115.984 c h +200.062 116.344 m S Q +BT +14 0 0 -14 200.369617 117.547745 Tm +/f-0-0 1 Tf +(h)Tj +ET +q 1 0 0 1 0 0 cm +200.918 117.547 m 202.09 107.828 l 203.277 107.828 l 202.73 112.359 l 203.094 + 111.84 203.496 111.445 203.934 111.172 c 204.371 110.902 204.812 110.766 + 205.262 110.766 c 205.551 110.766 205.809 110.824 206.027 110.938 c 206.254 + 111.043 206.438 111.203 206.574 111.422 c 206.719 111.641 206.812 111.906 + 206.855 112.219 c 206.906 112.531 206.91 112.887 206.871 113.281 c 206.371 + 117.547 l 205.168 117.547 l 205.668 113.281 l 205.73 112.762 205.691 112.375 + 205.559 112.125 c 205.434 111.875 205.188 111.75 204.824 111.75 c 204.625 + 111.75 204.418 111.809 204.199 111.922 c 203.98 112.027 203.77 112.18 203.574 + 112.375 c 203.375 112.562 203.184 112.797 202.996 113.078 c 202.816 113.359 + 202.668 113.672 202.543 114.016 c 202.121 117.547 l h +200.918 117.547 m S Q +BT +23.999999 0 0 -23.999999 101.645821 98.218046 Tm +/f-1-0 1 Tf +[(Beiel)-3(iSc)7(ale)]TJ +ET +Q Q +showpage +%%Trailer +end +%%EOF + \ No newline at end of file diff --git a/Case/bee-logo-gehaeusedeckel.png b/Case/bee-logo-gehaeusedeckel.png new file mode 100644 index 0000000..30ae699 Binary files /dev/null and b/Case/bee-logo-gehaeusedeckel.png differ diff --git a/Case/beieliscale-gehaeuse-aussparungen.pdf b/Case/beieliscale-gehaeuse-aussparungen.pdf new file mode 100644 index 0000000..4e22134 Binary files /dev/null and b/Case/beieliscale-gehaeuse-aussparungen.pdf differ diff --git a/Case/beieliscale-gehaeuse-aussparungen.svg b/Case/beieliscale-gehaeuse-aussparungen.svg new file mode 100644 index 0000000..d469167 --- /dev/null +++ b/Case/beieliscale-gehaeuse-aussparungen.svg @@ -0,0 +1,3742 @@ + + + +image/svg+xml93,20 + + + + + + + + + + + +35,50 24,60 + + + + + + + + + + + +R1,40 + + + + + + + + + + + +R24,00 + + + + + + + + + + + +"Z" + + + + + + + + + + + +150,20 + + + + + + + + + + + +87,5° + + + + + + + + + + + +R1,40 + + + + + + + + + + + +R24,00 + + + + + + + + + + + +AA + + + + + + + + + + + +R21,00 + + + + + + + + + + + +BB + + + + + + + + + + + +57,31 + + + + + + + + + + + +44,31 + + + + + + + + + + + +R2,00 + + + + + + + + + + + +Ansicht "Z" + + + + + + + + + + + +View "Z" + + + + + + + + + + + +2,50 + + + + + + + + + + + +R24,00 + + + + + + + + + + + +R1,40 + + + + + + + + + + + +1,0° + + + + + + + + + + + +3,60 + + + + + + + + + + + +2,50 1,30 + + + + + + + + + + + +59,00 46,00 + + + + + + + + + + + +Schnitt A-A + + + + + + + + + + + +Section A-A + + + + + + + + + + + +1,30 5,50 6,80 5,50 + + + + + + + + + + + +103,00 116,00 + + + + + + + + + + + +Schnitt B-B + + + + + + + + + + + +Section B-B + + + + + + + + + + + +2,70 + + + + + + + + + + + ++ + + + + + + + + + + + +0,300,00 + + + + + + + + + + + +108,00 138,00 + + + + + + + + + + + +50,00 80,00 46,00 + + + + + + + + + + + +103,00 + + + + + + + + + + + +Platine Unterteil + + + + + + + + + + + +P C B Bottom Part + + + + + + + + + + + +(4x) + + + + + + + + + + + +2,70 + + + + + + + + + + + ++ + + + + + + + + + + + +0,300,00 + + + + + + + + + + + +50,00 80,00 + + + + + + + + + + + +108,00 138,00 + + + + + + + + + + + +46,00 + + + + + + + + + + + +103,00 + + + + + + + + + + + +Platine Oberteil + + + + + + + + + + + +P C B Top Part + + + + + + + + + + + +(4x) + + + + + + + + + + + +1 + + + + + + + + + + + +2 + + + + + + + + + + + +3 + + + + + + + + + + + +4 + + + + + + + + + + + +AA + + + + + + + + + + + +B + + + + + + + + + + + +C + + + + + + + + + + + +D + + + + + + + + + + + +E + + + + + + + + + + + +F + + + + + + + + + + + +2 + + + + + + + + + + + +3 + + + + + + + + + + + +4 + + + + + + + + + + + +B + + + + + + + + + + + +C + + + + + + + + + + + +D + + + + + + + + + + + +E + + + + + + + + + + + +F + + + + + + + + + + + +1 + + + + + + + + + + + +ENTWICKLUNG + + + + + + + + + + + +1:1 + + + + + + + + + + + +DIN 6 Teil 2 + + + + + + + + + + + +M + + + + + + + + + + + +00022696 + + + + + + + + + + + +Dateiname: + + + + + + + + + + + +Status: + + + + + + + + + + + +KUNDE + + + + + + + + + + + +Zeichnungsart: + + + + + + + + + + + +Toleranzen: + + + + + + + + + + + +Volumen: + + + + + + + + + + + +DIN 16742 TG 6 + + + + + + + + + + + +24.04.2017 + + + + + + + + + + + +01 + + + + + + + + + + + +O. Herrlich + + + + + + + + + + + +Ausführung I F (Gehäuse flach) + + + + + + + + + + + +A9441107 + + + + + + + + + + + +EVOTEC 150 + + + + + + + + + + + +Erst. von: + + + + + + + + + + + +Freig. von: + + + + + + + + + + + +Erst. am: + + + + + + + + + + + +Freig. am: + + + + + + + + + + + +Revision: + + + + + + + + + + + +not be reproduced or copied and not be used or incorporated in any product. + + + + + + + + + + + +and is tendered subject to the conditions that the information be retained in confidence + + + + + + + + + + + +Für dieses Dokument behalten wir uns alle Urheberrechte vor. Es darf auch auszugs- + + + + + + + + + + + +weise weder vervielfältigt noch Dritten in irgendeiner Form zugänglich gemacht werden. + + + + + + + + + + + +This document contains proprietary information of OKW Gehäusesysteme GmbH + + + + + + + + + + + +1 + + + + + + + + + + + +20 + + + + + + + + + + + +6.8 + + + + + + + + + + + +16.5 + + + + + + + + + + +18.5 + + + + + + + + + + +21 + + + + + + + + + + + + + + + + + + +19 + + + + + + + + + +55 + + + + + + + + + +14.5 + + + + + + + + + +Innen: 10 x 5 (durchgehend) + + + + + + + +Aussen: 14 x 9.5 (2mm tiefe Tasche) + + + + + + +12.4 + + + + + + + + + + + + + +Durchmesser: 2.2Ansenkung: 0.9 + + + + + + +Durchmesser: 3.5Ansenkung: 1.0 + + + + + + +Blaue Farbe bedeutet: Ausparungen sind auf gegenüberliegender Seit + + + + + +A + + + + + +A + + + + + +C + + + + + +C + + + + + +B + + + + + +B + + + + + +B + + + + + +B + + + +BeieliScaleSkizze Gehäuseaussparungennbit Informatik GmbHjoerg.lehmann@nbit.ch2.8.2018, Jörg Lehmann02 \ No newline at end of file diff --git a/Kosten/beieliscale-kosten.ods b/Kosten/beieliscale-kosten.ods new file mode 100644 index 0000000..c9a6035 Binary files /dev/null and b/Kosten/beieliscale-kosten.ods differ diff --git a/LoRa/SwisscomLPNPortalV13DeviceDeveloperGuide.pdf b/LoRa/SwisscomLPNPortalV13DeviceDeveloperGuide.pdf new file mode 100644 index 0000000..e384a1a Binary files /dev/null and b/LoRa/SwisscomLPNPortalV13DeviceDeveloperGuide.pdf differ diff --git a/LoRa/Swisscom_LPN_Portal_V1_2_ApplicationDevelopmentGuide.pdf b/LoRa/Swisscom_LPN_Portal_V1_2_ApplicationDevelopmentGuide.pdf new file mode 100644 index 0000000..ae0cd59 Binary files /dev/null and b/LoRa/Swisscom_LPN_Portal_V1_2_ApplicationDevelopmentGuide.pdf differ diff --git a/Mechanische_Teile/distanzhalter-v1.0.pdf b/Mechanische_Teile/distanzhalter-v1.0.pdf new file mode 100644 index 0000000..8febb2b Binary files /dev/null and b/Mechanische_Teile/distanzhalter-v1.0.pdf differ diff --git a/Mechanische_Teile/distanzhalter.fcstd b/Mechanische_Teile/distanzhalter.fcstd new file mode 100644 index 0000000..093603f Binary files /dev/null and b/Mechanische_Teile/distanzhalter.fcstd differ diff --git a/Mechanische_Teile/waegauflage-v1.1.pdf b/Mechanische_Teile/waegauflage-v1.1.pdf new file mode 100644 index 0000000..9677a4f Binary files /dev/null and b/Mechanische_Teile/waegauflage-v1.1.pdf differ diff --git a/Mechanische_Teile/waegauflage.fcstd b/Mechanische_Teile/waegauflage.fcstd new file mode 100644 index 0000000..d4db67f Binary files /dev/null and b/Mechanische_Teile/waegauflage.fcstd differ diff --git a/Other-Components/adafruit-tpl5111-reset-enable-timer-breakout.pdf b/Other-Components/adafruit-tpl5111-reset-enable-timer-breakout.pdf new file mode 100644 index 0000000..5eeb477 Binary files /dev/null and b/Other-Components/adafruit-tpl5111-reset-enable-timer-breakout.pdf differ diff --git a/PCB/BeieliScale-v2.1.fzz b/PCB/BeieliScale-v2.1.fzz new file mode 100644 index 0000000..3384d6c Binary files /dev/null and b/PCB/BeieliScale-v2.1.fzz differ diff --git a/PCB/pcb-2.1.png b/PCB/pcb-2.1.png new file mode 100644 index 0000000..148f7bc Binary files /dev/null and b/PCB/pcb-2.1.png differ diff --git a/Python/minicom_follow.sh b/Python/minicom_follow.sh new file mode 100755 index 0000000..b16a788 --- /dev/null +++ b/Python/minicom_follow.sh @@ -0,0 +1,6 @@ +#!/bin/bash +DEVICE=/dev/ttyACM0 +while [ ! -e ${DEVICE} ]; do + sleep 0.2 +done +minicom -D ${DEVICE} -b 115200 diff --git a/Python/see_serial_output.py b/Python/see_serial_output.py new file mode 100755 index 0000000..5d61db8 --- /dev/null +++ b/Python/see_serial_output.py @@ -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() diff --git a/Python/send2beieliscale.py b/Python/send2beieliscale.py new file mode 100755 index 0000000..517ddb2 --- /dev/null +++ b/Python/send2beieliscale.py @@ -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() diff --git a/Python/serial_monitor.py b/Python/serial_monitor.py new file mode 100755 index 0000000..3abf3ce --- /dev/null +++ b/Python/serial_monitor.py @@ -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