diff --git a/lorawan.cpp b/lorawan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b85fcbc446eff5c228d82785258a217c9635ec09 --- /dev/null +++ b/lorawan.cpp @@ -0,0 +1,204 @@ +#include "lorawan.h" + +namespace config { + // Values should be defined in config.h + uint16_t lora_sending_interval = LORAWAN_SENDING_INTERVAL; // [s] + + static const u1_t PROGMEM APPEUI[8] = LORAWAN_APPLICATION_EUI; + static const u1_t PROGMEM DEVEUI[8] = LORAWAN_DEVICE_EUI; + static const u1_t PROGMEM APPKEY[16] = LORAWAN_APPLICATION_KEY; +} +// Pin mapping for TTGO LoRa32 V1 +// More info : https://www.thethingsnetwork.org/forum/t/big-esp32-sx127x-topic-part-3/18436 +const lmic_pinmap lmic_pins = { .nss = 18, .rxtx = LMIC_UNUSED_PIN, .rst = 14, .dio = { 26, 33, 32 } }; + +// Payloads will be automatically sent via MQTT by TheThingsNetwork, and can be seen with: +// mosquitto_sub -h eu.thethings.network -t '+/devices/+/up' -u 'APPLICATION-NAME' -P 'ttn-account-v2.4xxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxx' -v +// or encrypted: +// mosquitto_sub -h eu.thethings.network -t '+/devices/+/up' -u 'APPLICATION-NAME' -P 'ttn-account-v2.4xxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxx' -v --cafile mqtt-ca.pem -p 8883 +// -> +// co2ampel-test/devices/esp3a7c94/up {"app_id":"co2ampel-test","dev_id":"esp3a7c94","hardware_serial":"00xxxxxxxx","port":1,"counter":5,"payload_raw":"TJd7","payload_fields":{"co2":760,"rh":61.5,"temp":20.2},"metadata":{"time":"2020-12-23T23:00:51.44020438Z","frequency":867.5,"modulation":"LORA","data_rate":"SF7BW125","airtime":51456000,"coding_rate":"4/5","gateways":[{"gtw_id":"eui-xxxxxxxxxxxxxxxxxx","timestamp":1765406908,"time":"2020-12-23T23:00:51.402519Z","channel":5,"rssi":-64,"snr":7.5,"rf_chain":0,"latitude":22.7,"longitude":114.24,"altitude":450}]}} +// More info : https://www.thethingsnetwork.org/docs/applications/mqtt/quick-start.html + +//TODO: Add infos to webserver (last timestamp, sending interval, connected or not?) +//TODO: Merge back to other branch (and other git) +//TODO: compile for both boards, and check ESP32 && LORA + +void os_getArtEui(u1_t *buf) { + memcpy_P(buf, config::APPEUI, 8); +} + +void os_getDevEui(u1_t *buf) { + memcpy_P(buf, config::DEVEUI, 8); +} + +void os_getDevKey(u1_t *buf) { + memcpy_P(buf, config::APPKEY, 16); +} + +namespace lorawan { + bool waiting_for_confirmation = false; + + void printHex2(unsigned v) { + v &= 0xff; + if (v < 16) + Serial.print('0'); + Serial.print(v, HEX); + } + + void onEvent(ev_t ev) { + static bool trying_to_join = false; + Serial.print("LoRa - "); + Serial.print(ntp::getLocalTime()); + Serial.print(" - "); + switch (ev) { + case EV_JOINING: + trying_to_join = true; + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + trying_to_join = false; + waiting_for_confirmation = false; + LedEffects::onBoardLEDOff(); + Serial.println(F("EV_JOINED")); + { + u4_t netid = 0; + devaddr_t devaddr = 0; + u1_t nwkKey[16]; + u1_t artKey[16]; + LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); + Serial.print(F("netid: ")); + Serial.println(netid, DEC); + Serial.print(F("devaddr: ")); + Serial.println(devaddr, HEX); + Serial.print(F("AppSKey: ")); + for (size_t i = 0; i < sizeof(artKey); ++i) { + if (i != 0) + Serial.print("-"); + printHex2(artKey[i]); + } + Serial.println(""); + Serial.print("NwkSKey: "); + for (size_t i = 0; i < sizeof(nwkKey); ++i) { + if (i != 0) + Serial.print("-"); + printHex2(nwkKey[i]); + } + Serial.println(); + } + Serial.println(F("Other services may resume, and will not be frozen anymore.")); + // Disable link check validation (automatically enabled + // during join, but because slow data rates change max TX + // size, we don't use it in this example. + LMIC_setLinkCheckMode(0); + 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.print(F("Received ")); + Serial.print(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + break; + case EV_TXSTART: + waiting_for_confirmation = trying_to_join; + Serial.println(F("EV_TXSTART")); + break; + case EV_TXCANCELED: + waiting_for_confirmation = false; + LedEffects::onBoardLEDOff(); + Serial.println(F("EV_TXCANCELED")); + break; + case EV_JOIN_TXCOMPLETE: + waiting_for_confirmation = false; + LedEffects::onBoardLEDOff(); + Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept.")); + Serial.println(F("Other services may resume.")); + break; + default: + Serial.print(F("LoRa event: ")); + Serial.println((unsigned) ev); + break; + } + if (waiting_for_confirmation) { + LedEffects::onBoardLEDOn(); + Serial.println(F("LoRa - waiting for OTAA confirmation. Freezing every other service!")); + } + } + + void preparePayload() { + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else { + uint8_t buff[3]; + // Mapping CO2 from 0ppm to 5100ppm to [0, 255], with 20ppm increments. + buff[0] = (min(max(sensor::co2, 0), 5100) + 10) / 20; + // Mapping temperatures from [-10°C, 41°C] to [0, 255], with 0.2°C increment + buff[1] = static_cast<uint8_t>((min(max(sensor::temperature, -10), 41) + 10.1f) * 5); + // Mapping humidity from [0%, 100%] to [0, 200], with 0.5°C increment (0.4°C would also be possible) + buff[2] = static_cast<uint8_t>(min(max(sensor::humidity, 0) + 0.25f, 100) * 2); + + Serial.print(F("LoRa - Payload : '0x")); + printHex2(buff[0]); + printHex2(buff[1]); + printHex2(buff[2]); + Serial.print(F("', ")); + Serial.print(buff[0] * 20); + Serial.print(F(" ppm, ")); + Serial.print(buff[1] * 0.2 - 10); + Serial.print(F(" °C, ")); + Serial.print(buff[2] * 0.5); + Serial.println(F(" %.")); + + // Prepare upstream data transmission at the next possible time. + LMIC_setTxData2(1, buff, sizeof(buff), 0); + //NOTE: To decode in TheThingsNetwork: + //function Decoder(bytes, port) { + // return { + // co2: bytes[0] * 20, + // temp: bytes[1] / 5.0 - 10, + // rh: bytes[2] / 2.0 + // }; + //} + } + } + + void initialize() { + Serial.println(F("Starting LoRaWAN.")); + // LMIC init. + os_init(); + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + // Join, but don't send anything yet. + LMIC_startJoining(); + } + + // Checks if OTAA is connected, or if payload should be sent. + // NOTE: while a transaction is in process (i.e. until the TXcomplete event has been received, no blocking code (e.g. delay loops etc.) are allowed, otherwise the LMIC/OS code might miss the event. + // If this rule is not followed, a typical symptom is, that the first send is ok and all following ones end with the TX not complete failure. + void process() { + os_runloop_once(); + } + + void preparePayloadIfTimehasCome() { + static unsigned long last_sent_at = 0; + unsigned long now = seconds(); + if (now - last_sent_at > config::lora_sending_interval) { + last_sent_at = now; + preparePayload(); + } + } +} + +void onEvent(ev_t ev) { + lorawan::onEvent(ev); +} diff --git a/lorawan.h b/lorawan.h new file mode 100644 index 0000000000000000000000000000000000000000..19282930f01f5b8a54ddba39a24e696cb6cacc73 --- /dev/null +++ b/lorawan.h @@ -0,0 +1,24 @@ +#ifndef AMPEL_LORAWAN_H_ +#define AMPEL_LORAWAN_H_ + +#include <Arduino.h> +#include <lmic.h> +#include <hal/hal.h> +#include <SPI.h> +typedef uint8_t u1_t; +//#include <lmic_project_config.h> + +#include "co2_sensor.h" +#include "led_effects.h" +#include "config.h" + +#include "util.h" + +namespace lorawan { + extern bool waiting_for_confirmation; + void initialize(); + void process(); + void preparePayloadIfTimehasCome(); +} + +#endif