lorawan.cpp 7.22 KB
Newer Older
1
#include "lorawan.h"
2
#if defined(AMPEL_LORAWAN) && defined(ESP32)
3
4
5

namespace config {
  // Values should be defined in config.h
Eric Duminil's avatar
Eric Duminil committed
6
  uint16_t lorawan_sending_interval = LORAWAN_SENDING_INTERVAL; // [s]
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

  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;
Eric Duminil's avatar
Eric Duminil committed
35
36
  bool connected = false;
  String last_transmission = "";
37

Eric Duminil's avatar
Eric Duminil committed
38
  void initialize() {
Eric Duminil's avatar
Eric Duminil committed
39
    Serial.println(F("Starting LoRaWAN. Frequency plan : " LMIC_FREQUENCY_PLAN " MHz."));
Eric Duminil's avatar
Eric Duminil committed
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

    // 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();
  }

  // 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();
  }

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
  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;
Eric Duminil's avatar
Eric Duminil committed
76
      connected = true;
77
      led_effects::onBoardLEDOff();
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
      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."));
Eric Duminil's avatar
Note    
Eric Duminil committed
105
      // Disable link check validation (automatically enabled during join)
106
107
108
109
110
111
112
113
114
      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:
Eric Duminil's avatar
Eric Duminil committed
115
116
      last_transmission = ntp::getLocalTime();
      Serial.println(F("EV_TXCOMPLETE"));
117
118
      break;
    case EV_TXSTART:
Eric Duminil's avatar
Eric Duminil committed
119
      waiting_for_confirmation = !connected;
120
121
122
123
      Serial.println(F("EV_TXSTART"));
      break;
    case EV_TXCANCELED:
      waiting_for_confirmation = false;
124
      led_effects::onBoardLEDOff();
125
126
127
128
      Serial.println(F("EV_TXCANCELED"));
      break;
    case EV_JOIN_TXCOMPLETE:
      waiting_for_confirmation = false;
129
      led_effects::onBoardLEDOff();
130
131
132
133
134
135
136
137
138
      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) {
139
      led_effects::onBoardLEDOn();
140
141
142
143
      Serial.println(F("LoRa - waiting for OTAA confirmation. Freezing every other service!"));
    }
  }

Eric Duminil's avatar
Eric Duminil committed
144
  void preparePayload(int16_t co2, float temperature, float humidity) {
145
146
147
148
149
150
    // 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.
Eric Duminil's avatar
Eric Duminil committed
151
      buff[0] = (util::min(util::max(co2, 0), 5100) + 10) / 20;
152
      // Mapping temperatures from [-10°C, 41°C] to [0, 255], with 0.2°C increment
Eric Duminil's avatar
Eric Duminil committed
153
      buff[1] = static_cast<uint8_t>((util::min(util::max(temperature, -10), 41) + 10.1f) * 5);
154
      // Mapping humidity from [0%, 100%] to [0, 200], with 0.5°C increment (0.4°C would also be possible)
Eric Duminil's avatar
Eric Duminil committed
155
      buff[2] = static_cast<uint8_t>(util::min(util::max(humidity, 0) + 0.25f, 100) * 2);
156

157
      Serial.print(F("LoRa - Payload : '"));
158
      printHex2(buff[0]);
159
      Serial.print(" ");
160
      printHex2(buff[1]);
161
      Serial.print(" ");
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
      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
      //  };
      //}
    }
  }

Eric Duminil's avatar
Eric Duminil committed
184
  void preparePayloadIfTimeHasCome(const int16_t &co2, const float &temperature, const float &humidity) {
185
186
    static unsigned long last_sent_at = 0;
    unsigned long now = seconds();
Eric Duminil's avatar
Eric Duminil committed
187
    if (connected && (now - last_sent_at > config::lorawan_sending_interval)) {
188
      last_sent_at = now;
Eric Duminil's avatar
Eric Duminil committed
189
      preparePayload(co2, temperature, humidity);
190
191
192
193
194
195
196
    }
  }
}

void onEvent(ev_t ev) {
  lorawan::onEvent(ev);
}
197
#endif