#include "lorawan.h" #if defined(ESP32) namespace config { // Values should be defined in config.h uint16_t lorawan_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 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; bool connected = false; String last_transmission = ""; void printHex2(unsigned v) { v &= 0xff; if (v < 16) Serial.print('0'); Serial.print(v, HEX); } void onEvent(ev_t ev) { Serial.print("LoRa - "); Serial.print(ntp::getLocalTime()); Serial.print(" - "); switch (ev) { case EV_JOINING: Serial.println(F("EV_JOINING")); break; case EV_JOINED: waiting_for_confirmation = false; connected = true; 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: last_transmission = ntp::getLocalTime(); Serial.println(F("EV_TXCOMPLETE")); break; case EV_TXSTART: waiting_for_confirmation = !connected; 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] = (util::min(util::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((util::min(util::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(util::min(util::max(sensor::humidity, 0) + 0.25f, 100) * 2); Serial.print(F("LoRa - Payload : '")); printHex2(buff[0]); Serial.print(" "); printHex2(buff[1]); Serial.print(" "); 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::lorawan_sending_interval) { last_sent_at = now; preparePayload(); } } } void onEvent(ev_t ev) { lorawan::onEvent(ev); } #endif