211 lines
7.9 KiB
C++
211 lines
7.9 KiB
C++
#include "lorawan.h"
|
|
#if defined(AMPEL_LORAWAN) && 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;
|
|
}
|
|
|
|
// 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;
|
|
char last_transmission[23] = "";
|
|
|
|
void initialize() {
|
|
Serial.println(F("Starting LoRaWAN. Frequency plan : " LMIC_FREQUENCY_PLAN " MHz."));
|
|
|
|
// More info about pin mapping : https://github.com/mcci-catena/arduino-lmic#pin-mapping
|
|
// Has been tested successfully with ESP32 TTGO LoRa32 V1, and might work with other ESP32+LoRa boards.
|
|
const lmic_pinmap *pPinMap = Arduino_LMIC::GetPinmap_ThisBoard();
|
|
// LMIC init.
|
|
os_init_ex(pPinMap);
|
|
// Reset the MAC state. Session and pending data transfers will be discarded.
|
|
LMIC_reset();
|
|
// Join, but don't send anything yet.
|
|
LMIC_startJoining();
|
|
sensor_console::defineIntCommand("lora", setLoRaInterval, F("300 (Sets LoRaWAN sending interval, in s)"));
|
|
}
|
|
|
|
// 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 printHex2(unsigned v) {
|
|
v &= 0xff;
|
|
if (v < 16)
|
|
Serial.print('0');
|
|
Serial.print(v, HEX);
|
|
}
|
|
|
|
void onEvent(ev_t ev) {
|
|
char current_time[23];
|
|
ntp::getLocalTime(current_time);
|
|
Serial.print("LoRa - ");
|
|
Serial.print(current_time);
|
|
Serial.print(" - ");
|
|
switch (ev) {
|
|
case EV_JOINING:
|
|
Serial.println(F("EV_JOINING"));
|
|
break;
|
|
case EV_JOINED:
|
|
waiting_for_confirmation = false;
|
|
connected = true;
|
|
led_effects::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(F(" 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)
|
|
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:
|
|
ntp::getLocalTime(last_transmission);
|
|
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;
|
|
led_effects::onBoardLEDOff();
|
|
Serial.println(F("EV_TXCANCELED"));
|
|
break;
|
|
case EV_JOIN_TXCOMPLETE:
|
|
waiting_for_confirmation = false;
|
|
led_effects::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) {
|
|
led_effects::onBoardLEDOn();
|
|
Serial.println(F("LoRa - waiting for OTAA confirmation. Freezing every other service!"));
|
|
}
|
|
}
|
|
|
|
void preparePayload(int16_t co2, float temperature, float humidity) {
|
|
// 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(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>((util::min(util::max(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>(util::min(util::max(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 preparePayloadIfTimeHasCome(const int16_t &co2, const float &temperature, const float &humidity) {
|
|
static unsigned long last_sent_at = 0;
|
|
unsigned long now = seconds();
|
|
if (connected && (now - last_sent_at > config::lorawan_sending_interval)) {
|
|
last_sent_at = now;
|
|
preparePayload(co2, temperature, humidity);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************
|
|
* Callbacks for sensor commands *
|
|
*****************************************************************/
|
|
void setLoRaInterval(int32_t sending_interval) {
|
|
config::lorawan_sending_interval = sending_interval;
|
|
Serial.print(F("Setting LoRa sending interval to : "));
|
|
Serial.print(config::lorawan_sending_interval);
|
|
Serial.println("s.");
|
|
led_effects::showKITTWheel(color::green, 1);
|
|
}
|
|
}
|
|
|
|
void onEvent(ev_t ev) {
|
|
lorawan::onEvent(ev);
|
|
}
|
|
#endif
|