Tuesday, 25 September 2018

LoRa for feather

Overview

 
In this project, we're going to build a small weather-logging node using a Feather and a temperature sensor. The captured data will then be sent to The Things Network.

What is The Things Network?

The Things Network is a project dedicated to building a network for the Internet of Things. While WiFi is used in most Internet of Things devices, The Things Network uses a protocol called LoRaWAN which allows devices to talk to the internet without cellular or WiFi connectivity. This means you don't need to worry about protected wireless hotspots, cellular data plans, or spotty WiFi connectivity.
It's ideal for most internet of things projects, and unlike cellular plans or WiFi - it's free to use.
Also, there are plenty of gateways available to connect your feather to - if you'd like to find a gateway in your area, check the Gateway Map.

Parts

We're going to use one of our RadioFruits, the Feather M0 LoRa. In The The Things Network terms, this will be used as our device.
We'll also want to send data from our device to a gatewayThis small sensor can read both the temperature and relative humidity. It's perfect for building small IoT data-loggers.

Arduino Wiring

Wiring the Antenna


featherm0.png
Your Feather M0 does not come with an antenna, but there are two ways of wiring one up.

For this guide, and to keep the build cost-effective, we soldered a small, 82mm wire to the ANT pad.
  • Antenna Options and installation instructions are detailed on this product's learn guide's antenna options page. 
  • Note: Antenna length differs between regions, make sure you cut the antenna to the correct length for your region.

feather_wiring_bb_wire_only.png
Add a required jumper wire - Pin 3 is internally connected to pin io0, but we'll need to add a jumper wire between the Feather Digital Pin 6 and Feather Pin io1.

Wiring

Make the following connections between the Feather M0 and the DHT22:
  • Feather 3V to DHT22 Pin 1
  • Feather Pin 10 to DHT22 Pin 2
  • Feather Ground to DHT22 Pin 3
Make sure there is a wire connected between Pin 6 and the pin labeled `io1`. Your sketch will not work properly if these are not connected.

Registering a Feather with The Things Network

Before your Feather can communicate with The Things Network, you'll need to create an application.
First, we're going to register an account with TTN. Navigate to their account registration page to set up an account.
Once logged in, navigate to the The Things Network Console. This page is where you can register applications and add new devices or gateways. Click Applications
Click add application.
Fill out an Application ID to identify the application by, and a description of what the application is. We set our Handler Registration to match our region, us-west. If you're not located in the U.S., TTN provides multiple regions for handler registration.
Once created, you'll be directed to the Application Overview. From here, you can add devices, view data coming into (and out of) the application, add integrations for external services, and more. We'll come back to this section later in the guide.
Click Register Device
On the Register Device Page, The Device ID should be a unique string to identify the device.
The Device EUI is the unique identifier which came in the same bag as your Radiofruit Feather. We'll pad the middle of the string with four zeroes. The App Key will be randomly generated for you by TTN. Select the App EUI (used to identify the application) from the list.
Now that the application is set up, and the device is registered to the application, let's move on to wiring the Feather for use with TTN.

Arduino Setup

Before proceeding with this guide, make sure your board is correctly set-up by following the Using the Arduino IDE  page of the setup guide for the Feather M0 LoRa.
We're going to use MCCI's arduino-lmic library with this guide to communicate with The Things Network. Open the Arduino Library Manager (Sketch->Include Library->Manage Libraries). In the search box, type MCCIthe MCCI LoRaWAN LMIC library should be the first result. Click Install.
We've included the code for this guide inside the arduino-lmic library. To open it, from the Arduino IDE click File->Examples->MCCI LoRaWAN LMIC library->ttn-otaa-feather-us915-dht22
The code for the Feather M0 Weather Node should pop up.
Now that our code is set up, let's learn how to send data from our Feather to TTN and decode it!

Region Configuration

If you're within the United States, you can skip this page. The code and setup is already targeted towards the US frequency ranges.
Dont fret if you're not in the US - you'll just have to make some small modifications to your code to ensure it runs on the correct region. The Arduino-Lmic library supports the following regions:
  • eu_868: EU
  • us_915: US
  • au_921: Australia 
  • as_923: Asia
  • in_866: India
First, within the directory of your sketch, create a new folder named project_config
Open the Arduino IDE and copy and paste the contents of the lmic_project_config.h file into the IDE:
Simply uncomment the region your device is located in. We use a SX1276 transceiver on the Feather M0 LoRA, so this definition should not be changed.

Arduino Code

Code Setup

To register your Feather with The Things Network, you need to set three unique identifiers in the code: APPEUI, DEVEUI, and APPKEY.
Navigate to the Device Overview page for your Feather device. Make sure the Activation Method is set to OTAA
Before adding the unique identifiers to our sketch, we'll need to first expand them by clicking the <> icon. Then, we'll need to switch the order of the Device EUI and Application EUI to little-endian format. You can swap the order by clicking the button with two arrows.
We're going to copy each unique identifier from the Device Overview to the variables within the sketch.
First, copy the Application EUI  from the TTN console to APPEUI variable in the sketch.
Next, copy the Device EUI from the TTN console to the DEVEUI variable in the sketch.
Finally, copy the App Key from the TTN console to APPKEY variable in the sketch.
That's all for now - we're ready to upload the code onto our Arduino.

Code Overview

Most of what happens in the code occurs in the setup(), on_event() and do_send() functions.
Within setup(), we initialize our DHT sensor and set up the LMIC (LoraWAN-in-C, formerly LoraMAC-in-C) framework for use with our radio (the RFM95) and region-specific radio settings.
Our main loop() calls os_runloop_once(), which calls the LMIC runloop processor. This loop causes radio events to occur based on events and time - such as callbacks for when the transmission is complete. While you can place additional code within this loop() routine, we don't advise it - the LoRaWAN timing is tight.
If there isn't a transmission job currently running, we're going to prepare the packet to send to The Things Network. This occurs in the do_send() function.
To read the temperature, we're going to first create a floating point variable, temperature, and assign it to the temperature value from the sensor.
float temperature = dht.readTemperature();
While we can't directly send floating point numbers (like 50.62 degrees) to The Things Network dashboard, the MCCI-LMIC library includes some data encoding utility functions to encode floating point data into integer data using a special bit layout -sflt16.
Since the floating point is within the range -1 to -1, we'll need to divide the range (by 100 for temperature) to increase the exponent range. We're going to get the value back in the range by later multiplying it by the range (100, in this example) using the decoder on the TTN Console.
// adjust for the f2sflt16 range (-1 to 1)
temperature = temperature / 100; rHumidity = rHumidity / 100;

Next, we're going to convert the float to an integer. To do this, we'll use the library's float-to-signed-float-16 function (LMIC_f2sflt16):
// float -> int
uint16_t payloadTemp = LMIC_f2sflt16(temperature);

Almost there! Before loading our values into the payload, we'll need to convert them from an integer (payloadTemp) to bytes. To do this, we'll use the Arduino lowByte and highByte functions:
// int -> bytes
byte tempLow = lowByte(payloadTemp);
byte tempHigh = highByte(payloadTemp);
We defined a payload further up in the code (static uint8_t payload[5]). Now, we place the bits (low byte first)  into the payload:
payload[0] = tempLow;
payload[1] = tempHigh;
and prepare the payload for sending to TTN at the next possible time.
LMIC_setTxData2(1, payload, sizeof(payload)-1, 0);

Using the Sketch

Compile (cmd/ctrl + R) and Upload (ctrl/cmd+U) the sketch onto the Feather. Then, pop open the serial monitor (Tools->Serial Monitor). You should see the Feather joining the Things Network. Once it joins, it'll dump its connection info. Then, it'll show EV_TXStart (an event which begins the transmission) and EV_TXComplete (the transmission has been received and acknowledged by the gateway). The output should be similar to the following:
  1. Starting
  2. Temperature: 26.00 *C
  3. %RH 48.10
  4. 105016549: EV_JOINING
  5. 105016641: EV_TXSTART
  6. 105357886: EV_JOINED
  7. netid: 19
  8. devaddr: 26022F78
  9. artKey: 78-F6-78-6C-87-26-86-AE-E1-AC-6D-79-83-57-7E-11
  10. nwkKey: FE-14-C4-A7-BF-D3-B6-E6-95-D4-2F-93-DC-F9-D7-25
  11. 105358155: EV_TXSTART
  12. 105579674: EV_TXCOMPLETE (includes waiting for RX windows)
If you're stuck on EV_JOINING or fail to join the network,  make sure your device is within range of a The Things Network gateway.
If the code is looping EV_TXSTART, make sure a jumper wire is connecting the Feather's Pin 6 and Feather Pin 'io1'.
Navigate to the The Things Network Console and select your application. From the menu on the right hand side of the page, Click Data.
If everything worked correctly, you'll see the payload from your device streaming into the page, in real time.
Neat, right? But while we received a payload, we still don't understand what it means...

Decoding the Payload

If you're sending packets in strange formats or encodings (like we are!), The Things Network Console has a programmable data decoder to decode the packets, and assign useful labels to the data.


Copy and paste the decoder script below into the decoder's integrated text editor and click save. Then, click the data tab. Next to the raw payload data, you should see the decoded data for humidity and temperature.

Decoder Code

  1. function Decoder(bytes, port) {
  2. // Decode an uplink message from a buffer
  3. // (array) of bytes to an object of fields.
  4. var decoded = {};
  5. // temperature
  6.  
  7. rawTemp = bytes[0] + bytes[1] * 256;
  8. decoded.degreesC = sflt162f(rawTemp) * 100;
  9. // humidity
  10. rawHumid = bytes[2] + bytes[3] * 256;
  11. decoded.humidity = sflt162f(rawHumid) * 100;
  12. return decoded;
  13. }
  14.  
  15. function sflt162f(rawSflt16)
  16. {
  17. // rawSflt16 is the 2-byte number decoded from wherever;
  18. // it's in range 0..0xFFFF
  19. // bit 15 is the sign bit
  20. // bits 14..11 are the exponent
  21. // bits 10..0 are the the mantissa. Unlike IEEE format,
  22. // the msb is transmitted; this means that numbers
  23. // might not be normalized, but makes coding for
  24. // underflow easier.
  25. // As with IEEE format, negative zero is possible, so
  26. // we special-case that in hopes that JavaScript will
  27. // also cooperate.
  28. //
  29. // The result is a number in the open interval (-1.0, 1.0);
  30. //
  31. // throw away high bits for repeatability.
  32. rawSflt16 &= 0xFFFF;
  33.  
  34. // special case minus zero:
  35. if (rawSflt16 == 0x8000)
  36. return -0.0;
  37.  
  38. // extract the sign.
  39. var sSign = ((rawSflt16 & 0x8000) != 0) ? -1 : 1;
  40. // extract the exponent
  41. var exp1 = (rawSflt16 >> 11) & 0xF;
  42.  
  43. // extract the "mantissa" (the fractional part)
  44. var mant1 = (rawSflt16 & 0x7FF) / 2048.0;
  45.  
  46. // convert back to a floating point number. We hope
  47. // that Math.pow(2, k) is handled efficiently by
  48. // the JS interpreter! If this is time critical code,
  49. // you can replace by a suitable shift and divide.
  50. var f_unscaled = sSign * mant1 * Math.pow(2, exp1 - 15);
  51.  
  52. return f_unscaled;
  53. }

Code

  1. /*******************************************************************************
  2. * The Things Network - Sensor Data Example
  3. *
  4. * Example of sending a valid LoRaWAN packet with DHT22 temperature and
  5. * humidity data to The Things Networ using a Feather M0 LoRa.
  6. *
  7. * Learn Guide: https://learn.adafruit.com/the-things-network-for-feather
  8. *
  9. * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
  10. * Copyright (c) 2018 Terry Moore, MCCI
  11. * Copyright (c) 2018 Brent Rubell, Adafruit Industries
  12. *
  13. * Permission is hereby granted, free of charge, to anyone
  14. * obtaining a copy of this document and accompanying files,
  15. * to do whatever they want with them without any restriction,
  16. * including, but not limited to, copying, modification and redistribution.
  17. * NO WARRANTY OF ANY KIND IS PROVIDED.
  18. *******************************************************************************/
  19. #include <lmic.h>
  20. #include <hal/hal.h>
  21. #include <SPI.h>
  22.  
  23. // include the DHT22 Sensor Library
  24. #include "DHT.h"
  25.  
  26. // DHT digital pin and sensor type
  27. #define DHTPIN 10
  28. #define DHTTYPE DHT22
  29.  
  30. //
  31. // For normal use, we require that you edit the sketch to replace FILLMEIN
  32. // with values assigned by the TTN console. However, for regression tests,
  33. // we want to be able to compile these scripts. The regression tests define
  34. // COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
  35. // working but innocuous value.
  36. //
  37. #ifdef COMPILE_REGRESSION_TEST
  38. #define FILLMEIN 0
  39. #else
  40. #warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
  41. #define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
  42. #endif
  43.  
  44. // This EUI must be in little-endian format, so least-significant-byte
  45. // first. When copying an EUI from ttnctl output, this means to reverse
  46. // the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
  47. // 0x70.
  48. static const u1_t PROGMEM APPEUI[8] = { FILLMEIN };
  49. void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
  50.  
  51. // This should also be in little endian format, see above.
  52. static const u1_t PROGMEM DEVEUI[8] = { FILLMEIN };
  53. void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
  54.  
  55. // This key should be in big endian format (or, since it is not really a
  56. // number but a block of memory, endianness does not really apply). In
  57. // practice, a key taken from the TTN console can be copied as-is.
  58. static const u1_t PROGMEM APPKEY[16] = { FILLMEIN };
  59. void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
  60.  
  61. // payload to send to TTN gateway
  62. static uint8_t payload[5];
  63. static osjob_t sendjob;
  64.  
  65. // Schedule TX every this many seconds (might become longer due to duty
  66. // cycle limitations).
  67. const unsigned TX_INTERVAL = 30;
  68.  
  69. // Pin mapping for Adafruit Feather M0 LoRa
  70. const lmic_pinmap lmic_pins = {
  71. .nss = 8,
  72. .rxtx = LMIC_UNUSED_PIN,
  73. .rst = 4,
  74. .dio = {3, 6, LMIC_UNUSED_PIN},
  75. .rxtx_rx_active = 0,
  76. .rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
  77. .spi_freq = 8000000,
  78. };
  79.  
  80. // init. DHT
  81. DHT dht(DHTPIN, DHTTYPE);
  82.  
  83. void onEvent (ev_t ev) {
  84. Serial.print(os_getTime());
  85. Serial.print(": ");
  86. switch(ev) {
  87. case EV_SCAN_TIMEOUT:
  88. Serial.println(F("EV_SCAN_TIMEOUT"));
  89. break;
  90. case EV_BEACON_FOUND:
  91. Serial.println(F("EV_BEACON_FOUND"));
  92. break;
  93. case EV_BEACON_MISSED:
  94. Serial.println(F("EV_BEACON_MISSED"));
  95. break;
  96. case EV_BEACON_TRACKED:
  97. Serial.println(F("EV_BEACON_TRACKED"));
  98. break;
  99. case EV_JOINING:
  100. Serial.println(F("EV_JOINING"));
  101. break;
  102. case EV_JOINED:
  103. Serial.println(F("EV_JOINED"));
  104. {
  105. u4_t netid = 0;
  106. devaddr_t devaddr = 0;
  107. u1_t nwkKey[16];
  108. u1_t artKey[16];
  109. LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
  110. Serial.print("netid: ");
  111. Serial.println(netid, DEC);
  112. Serial.print("devaddr: ");
  113. Serial.println(devaddr, HEX);
  114. Serial.print("artKey: ");
  115. for (int i=0; i<sizeof(artKey); ++i) {
  116. if (i != 0)
  117. Serial.print("-");
  118. Serial.print(artKey[i], HEX);
  119. }
  120. Serial.println("");
  121. Serial.print("nwkKey: ");
  122. for (int i=0; i<sizeof(nwkKey); ++i) {
  123. if (i != 0)
  124. Serial.print("-");
  125. Serial.print(nwkKey[i], HEX);
  126. }
  127. Serial.println("");
  128. }
  129. // Disable link check validation (automatically enabled
  130. // during join, but because slow data rates change max TX
  131. // size, we don't use it in this example.
  132. LMIC_setLinkCheckMode(0);
  133. break;
  134. /*
  135. || This event is defined but not used in the code. No
  136. || point in wasting codespace on it.
  137. ||
  138. || case EV_RFU1:
  139. || Serial.println(F("EV_RFU1"));
  140. || break;
  141. */
  142. case EV_JOIN_FAILED:
  143. Serial.println(F("EV_JOIN_FAILED"));
  144. break;
  145. case EV_REJOIN_FAILED:
  146. Serial.println(F("EV_REJOIN_FAILED"));
  147. break;
  148. break;
  149. case EV_TXCOMPLETE:
  150. Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
  151. if (LMIC.txrxFlags & TXRX_ACK)
  152. Serial.println(F("Received ack"));
  153. if (LMIC.dataLen) {
  154. Serial.println(F("Received "));
  155. Serial.println(LMIC.dataLen);
  156. Serial.println(F(" bytes of payload"));
  157. }
  158. // Schedule next transmission
  159. os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
  160. break;
  161. case EV_LOST_TSYNC:
  162. Serial.println(F("EV_LOST_TSYNC"));
  163. break;
  164. case EV_RESET:
  165. Serial.println(F("EV_RESET"));
  166. break;
  167. case EV_RXCOMPLETE:
  168. // data received in ping slot
  169. Serial.println(F("EV_RXCOMPLETE"));
  170. break;
  171. case EV_LINK_DEAD:
  172. Serial.println(F("EV_LINK_DEAD"));
  173. break;
  174. case EV_LINK_ALIVE:
  175. Serial.println(F("EV_LINK_ALIVE"));
  176. break;
  177. /*
  178. || This event is defined but not used in the code. No
  179. || point in wasting codespace on it.
  180. ||
  181. || case EV_SCAN_FOUND:
  182. || Serial.println(F("EV_SCAN_FOUND"));
  183. || break;
  184. */
  185. case EV_TXSTART:
  186. Serial.println(F("EV_TXSTART"));
  187. break;
  188. default:
  189. Serial.print(F("Unknown event: "));
  190. Serial.println((unsigned) ev);
  191. break;
  192. }
  193. }
  194.  
  195. void do_send(osjob_t* j){
  196. // Check if there is not a current TX/RX job running
  197. if (LMIC.opmode & OP_TXRXPEND) {
  198. Serial.println(F("OP_TXRXPEND, not sending"));
  199. } else {
  200. // read the temperature from the DHT22
  201. float temperature = dht.readTemperature();
  202. Serial.print("Temperature: "); Serial.print(temperature);
  203. Serial.println(" *C");
  204. // adjust for the f2sflt16 range (-1 to 1)
  205. temperature = temperature / 100;
  206.  
  207. // read the humidity from the DHT22
  208. float rHumidity = dht.readHumidity();
  209. Serial.print("%RH ");
  210. Serial.println(rHumidity);
  211. // adjust for the f2sflt16 range (-1 to 1)
  212. rHumidity = rHumidity / 100;
  213. // float -> int
  214. // note: this uses the sflt16 datum (https://github.com/mcci-catena/arduino-lmic#sflt16)
  215. uint16_t payloadTemp = LMIC_f2sflt16(temperature);
  216. // int -> bytes
  217. byte tempLow = lowByte(payloadTemp);
  218. byte tempHigh = highByte(payloadTemp);
  219. // place the bytes into the payload
  220. payload[0] = tempLow;
  221. payload[1] = tempHigh;
  222.  
  223. // float -> int
  224. uint16_t payloadHumid = LMIC_f2sflt16(rHumidity);
  225. // int -> bytes
  226. byte humidLow = lowByte(payloadHumid);
  227. byte humidHigh = highByte(payloadHumid);
  228. payload[2] = humidLow;
  229. payload[3] = humidHigh;
  230.  
  231. // prepare upstream data transmission at the next possible time.
  232. // transmit on port 1 (the first parameter); you can use any value from 1 to 223 (others are reserved).
  233. // don't request an ack (the last parameter, if not zero, requests an ack from the network).
  234. // Remember, acks consume a lot of network resources; don't ask for an ack unless you really need it.
  235. LMIC_setTxData2(1, payload, sizeof(payload)-1, 0);
  236. }
  237. // Next TX is scheduled after TX_COMPLETE event.
  238. }
  239.  
  240. void setup() {
  241. delay(5000);
  242. while (! Serial);
  243. Serial.begin(9600);
  244. Serial.println(F("Starting"));
  245.  
  246. dht.begin();
  247.  
  248. // LMIC init
  249. os_init();
  250. // Reset the MAC state. Session and pending data transfers will be discarded.
  251. LMIC_reset();
  252. // Disable link-check mode and ADR, because ADR tends to complicate testing.
  253. LMIC_setLinkCheckMode(0);
  254. // Set the data rate to Spreading Factor 7. This is the fastest supported rate for 125 kHz channels, and it
  255. // minimizes air time and battery power. Set the transmission power to 14 dBi (25 mW).
  256. LMIC_setDrTxpow(DR_SF7,14);
  257. // in the US, with TTN, it saves join time if we start on subband 1 (channels 8-15). This will
  258. // get overridden after the join by parameters from the network. If working with other
  259. // networks or in other regions, this will need to be changed.
  260. LMIC_selectSubBand(1);
  261.  
  262. // Start job (sending automatically starts OTAA too)
  263. do_send(&sendjob);
  264. }
  265.  
  266. void loop() {
  267. // we call the LMIC's runloop processor. This will cause things to happen based on events and time. One
  268. // of the things that will happen is callbacks for transmission complete or received messages. We also
  269. // use this loop to queue periodic data transmissions. You can put other things here in the `loop()` routine,
  270. // but beware that LoRaWAN timing is pretty tight, so if you do more than a few milliseconds of work, you
  271. // will want to call `os_runloop_once()` every so often, to keep the radio running.
  272. os_runloop_once();
  273. }

Add an OLED

Now that we've got our Feather M0 connecting to The Things Network, we'll add a FeatherWing OLED to view the active connection with The Things Network and the sensor measurements.
Snap the FeatherWing onto your Feather, navigate to the FeatherWing OLED Arduino Setup guide, and follow the instructions to set up and test the OLED. Once you verify that the FeatherWing OLED works, copy and paste the code below into a new sketch.
Make sure to re-enter your unique device identifiers before compiling and uploading the sketch.

Code

  1. /*******************************************************************************
  2. * The Things Network - Sensor Data Example with OLED
  3. *
  4. * Example of sending a valid LoRaWAN packet with DHT22 temperature and
  5. * humidity data to The Things Networ using a Feather M0 LoRa.
  6. *
  7. * Learn Guide: https://learn.adafruit.com/the-things-network-for-feather
  8. *
  9. * Copyright (c) 2015 Thomas Telkamp and Matthijs Kooijman
  10. * Copyright (c) 2018 Terry Moore, MCCI
  11. * Copyright (c) 2018 Brent Rubell, Adafruit Industries
  12. *
  13. * Permission is hereby granted, free of charge, to anyone
  14. * obtaining a copy of this document and accompanying files,
  15. * to do whatever they want with them without any restriction,
  16. * including, but not limited to, copying, modification and redistribution.
  17. * NO WARRANTY OF ANY KIND IS PROVIDED.
  18. *******************************************************************************/
  19. #include <lmic.h>
  20. #include <hal/hal.h>
  21. #include <SPI.h>
  22.  
  23. // include the DHT22 Sensor Library
  24. #include "DHT.h"
  25.  
  26. // include the FeatherWing OLED library
  27. #include <Wire.h>
  28. #include <Adafruit_GFX.h>
  29. #include <Adafruit_SSD1306.h>
  30.  
  31. // DHT digital pin and sensor type
  32. #define DHTPIN 10
  33. #define DHTTYPE DHT22
  34.  
  35. Adafruit_SSD1306 display = Adafruit_SSD1306();
  36.  
  37. #if (SSD1306_LCDHEIGHT != 32)
  38. #error("Height incorrect, please fix Adafruit_SSD1306.h!");
  39. #endif
  40.  
  41. //
  42. // For normal use, we require that you edit the sketch to replace FILLMEIN
  43. // with values assigned by the TTN console. However, for regression tests,
  44. // we want to be able to compile these scripts. The regression tests define
  45. // COMPILE_REGRESSION_TEST, and in that case we define FILLMEIN to a non-
  46. // working but innocuous value.
  47. //
  48. #ifdef COMPILE_REGRESSION_TEST
  49. #define FILLMEIN 0
  50. #else
  51. #warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
  52. #define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
  53. #endif
  54.  
  55. // This EUI must be in little-endian format, so least-significant-byte
  56. // first. When copying an EUI from ttnctl output, this means to reverse
  57. // the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3,
  58. // 0x70.
  59. static const u1_t PROGMEM APPEUI[8] = { FILLMEIN };
  60. void os_getArtEui (u1_t* buf) { memcpy_P(buf, APPEUI, 8);}
  61.  
  62. // This should also be in little endian format, see above.
  63. static const u1_t PROGMEM DEVEUI[8] = { FILLMEIN };
  64. void os_getDevEui (u1_t* buf) { memcpy_P(buf, DEVEUI, 8);}
  65.  
  66. // This key should be in big endian format (or, since it is not really a
  67. // number but a block of memory, endianness does not really apply). In
  68. // practice, a key taken from the TTN console can be copied as-is.
  69. static const u1_t PROGMEM APPKEY[16] = { FILLMEIN };
  70. void os_getDevKey (u1_t* buf) { memcpy_P(buf, APPKEY, 16);}
  71.  
  72. // payload to send to TTN gateway
  73. static uint8_t payload[5];
  74. static osjob_t sendjob;
  75.  
  76. // Schedule TX every this many seconds (might become longer due to duty
  77. // cycle limitations).
  78. const unsigned TX_INTERVAL = 10;
  79.  
  80. // dht
  81. float temperature;
  82. float rHumidity;
  83.  
  84. // Pin mapping for Adafruit Feather M0 LoRa
  85. const lmic_pinmap lmic_pins = {
  86. .nss = 8,
  87. .rxtx = LMIC_UNUSED_PIN,
  88. .rst = 4,
  89. .dio = {3, 6, LMIC_UNUSED_PIN},
  90. .rxtx_rx_active = 0,
  91. .rssi_cal = 8, // LBT cal for the Adafruit Feather M0 LoRa, in dB
  92. .spi_freq = 8000000,
  93. };
  94.  
  95. // init. DHT
  96. DHT dht(DHTPIN, DHTTYPE);
  97.  
  98. void onEvent (ev_t ev) {
  99. Serial.print(os_getTime());
  100. Serial.print(": ");
  101. switch(ev) {
  102. case EV_SCAN_TIMEOUT:
  103. Serial.println(F("EV_SCAN_TIMEOUT"));
  104. break;
  105. case EV_BEACON_FOUND:
  106. Serial.println(F("EV_BEACON_FOUND"));
  107. break;
  108. case EV_BEACON_MISSED:
  109. Serial.println(F("EV_BEACON_MISSED"));
  110. break;
  111. case EV_BEACON_TRACKED:
  112. Serial.println(F("EV_BEACON_TRACKED"));
  113. break;
  114. case EV_JOINING:
  115. display.print("TTN: Joining...");
  116. display.display();
  117. Serial.println(F("EV_JOINING"));
  118. break;
  119. case EV_JOINED:
  120. display.clearDisplay();
  121. display.display();
  122. display.setCursor(0, 0);
  123. display.println("TTN: Connected");
  124. display.display();
  125. Serial.println(F("EV_JOINED"));
  126. {
  127. u4_t netid = 0;
  128. devaddr_t devaddr = 0;
  129. u1_t nwkKey[16];
  130. u1_t artKey[16];
  131. LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
  132. Serial.print("netid: ");
  133. Serial.println(netid, DEC);
  134. Serial.print("devaddr: ");
  135. Serial.println(devaddr, HEX);
  136. Serial.print("artKey: ");
  137. for (int i=0; i<sizeof(artKey); ++i) {
  138. if (i != 0)
  139. Serial.print("-");
  140. Serial.print(artKey[i], HEX);
  141. }
  142. Serial.println("");
  143. Serial.print("nwkKey: ");
  144. for (int i=0; i<sizeof(nwkKey); ++i) {
  145. if (i != 0)
  146. Serial.print("-");
  147. Serial.print(nwkKey[i], HEX);
  148. }
  149. Serial.println("");
  150. }
  151. // Disable link check validation (automatically enabled
  152. // during join, but because slow data rates change max TX
  153. // size, we don't use it in this example.
  154. LMIC_setLinkCheckMode(0);
  155. break;
  156. /*
  157. || This event is defined but not used in the code. No
  158. || point in wasting codespace on it.
  159. ||
  160. || case EV_RFU1:
  161. || Serial.println(F("EV_RFU1"));
  162. || break;
  163. */
  164. case EV_JOIN_FAILED:
  165. Serial.println(F("EV_JOIN_FAILED"));
  166. break;
  167. case EV_REJOIN_FAILED:
  168. Serial.println(F("EV_REJOIN_FAILED"));
  169. break;
  170. break;
  171. case EV_TXCOMPLETE:
  172. display.clearDisplay();
  173. display.display();
  174. display.setCursor(0, 0);
  175. display.println("TTN: Connected");
  176. display.display();
  177. display.setCursor(0, 20);
  178. display.println("* Sent!");
  179. display.display();
  180. Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
  181. if (LMIC.txrxFlags & TXRX_ACK)
  182. Serial.println(F("Received ack"));
  183. if (LMIC.dataLen) {
  184. Serial.println(F("Received "));
  185. Serial.println(LMIC.dataLen);
  186. Serial.println(F(" bytes of payload"));
  187. }
  188. // Schedule next transmission
  189. os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
  190. break;
  191. case EV_LOST_TSYNC:
  192. Serial.println(F("EV_LOST_TSYNC"));
  193. break;
  194. case EV_RESET:
  195. Serial.println(F("EV_RESET"));
  196. break;
  197. case EV_RXCOMPLETE:
  198. // data received in ping slot
  199. Serial.println(F("EV_RXCOMPLETE"));
  200. break;
  201. case EV_LINK_DEAD:
  202. Serial.println(F("EV_LINK_DEAD"));
  203. break;
  204. case EV_LINK_ALIVE:
  205. Serial.println(F("EV_LINK_ALIVE"));
  206. break;
  207. /*
  208. || This event is defined but not used in the code. No
  209. || point in wasting codespace on it.
  210. ||
  211. || case EV_SCAN_FOUND:
  212. || Serial.println(F("EV_SCAN_FOUND"));
  213. || break;
  214. */
  215. case EV_TXSTART:
  216. display.clearDisplay();
  217. display.display();
  218. display.setCursor(0, 0);
  219. display.println("TTN: Connected");
  220. display.setCursor(0, 10);
  221. display.println("* Sending");
  222. display.setCursor(0, 25);
  223. display.print("Temp: ");display.print(temperature*100);display.print(" C, ");
  224. display.print("RH%: ");display.print(rHumidity*100);
  225. display.display();
  226. Serial.println(F("EV_TXSTART"));
  227. break;
  228. default:
  229. Serial.print(F("Unknown event: "));
  230. Serial.println((unsigned) ev);
  231. break;
  232. }
  233. }
  234.  
  235. void do_send(osjob_t* j){
  236. // Check if there is not a current TX/RX job running
  237. if (LMIC.opmode & OP_TXRXPEND) {
  238. Serial.println(F("OP_TXRXPEND, not sending"));
  239. } else {
  240. // read the temperature from the DHT22
  241. temperature = dht.readTemperature();
  242. Serial.print("Temperature: "); Serial.print(temperature);
  243. Serial.println(" *C");
  244. // adjust for the f2sflt16 range (-1 to 1)
  245. temperature = temperature / 100;
  246.  
  247. // read the humidity from the DHT22
  248. rHumidity = dht.readHumidity();
  249. Serial.print("%RH ");
  250. Serial.println(rHumidity);
  251. // adjust for the f2sflt16 range (-1 to 1)
  252. rHumidity = rHumidity / 100;
  253. // float -> int
  254. // note: this uses the sflt16 datum (https://github.com/mcci-catena/arduino-lmic#sflt16)
  255. uint16_t payloadTemp = LMIC_f2sflt16(temperature);
  256. // int -> bytes
  257. byte tempLow = lowByte(payloadTemp);
  258. byte tempHigh = highByte(payloadTemp);
  259. // place the bytes into the payload
  260. payload[0] = tempLow;
  261. payload[1] = tempHigh;
  262.  
  263. // float -> int
  264. uint16_t payloadHumid = LMIC_f2sflt16(rHumidity);
  265. // int -> bytes
  266. byte humidLow = lowByte(payloadHumid);
  267. byte humidHigh = highByte(payloadHumid);
  268. payload[2] = humidLow;
  269. payload[3] = humidHigh;
  270.  
  271. // prepare upstream data transmission at the next possible time.
  272. // transmit on port 1 (the first parameter); you can use any value from 1 to 223 (others are reserved).
  273. // don't request an ack (the last parameter, if not zero, requests an ack from the network).
  274. // Remember, acks consume a lot of network resources; don't ask for an ack unless you really need it.
  275. LMIC_setTxData2(1, payload, sizeof(payload)-1, 0);
  276. Serial.println(F("EV_TXSTART"));
  277. }
  278. // Next TX is scheduled after TX_COMPLETE event.
  279. }
  280.  
  281. void setup() {
  282. delay(5000);
  283. while (! Serial);
  284. Serial.begin(9600);
  285. Serial.println(F("Starting"));
  286.  
  287. dht.begin();
  288. // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  289. display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 128x32)
  290. Serial.println("OLED and DHT init'd");
  291.  
  292. // Show image buffer on the display hardware.
  293. // Since the buffer is intialized with an Adafruit splashscreen
  294. // internally, this will display the splashscreen.
  295. display.display();
  296. delay(1000);
  297. // Clear the buffer.
  298. display.clearDisplay();
  299. display.display();
  300.  
  301. // set text display size/location
  302. display.setTextSize(1);
  303. display.setTextColor(WHITE);
  304. display.setCursor(0,0);
  305.  
  306. // LMIC init
  307. os_init();
  308. // Reset the MAC state. Session and pending data transfers will be discarded.
  309. LMIC_reset();
  310. // Disable link-check mode and ADR, because ADR tends to complicate testing.
  311. LMIC_setLinkCheckMode(0);
  312. // Set the data rate to Spreading Factor 7. This is the fastest supported rate for 125 kHz channels, and it
  313. // minimizes air time and battery power. Set the transmission power to 14 dBi (25 mW).
  314. LMIC_setDrTxpow(DR_SF7,14);
  315. // in the US, with TTN, it saves join time if we start on subband 1 (channels 8-15). This will
  316. // get overridden after the join by parameters from the network. If working with other
  317. // networks or in other regions, this will need to be changed.
  318. LMIC_selectSubBand(1);
  319.  
  320. // Start job (sending automatically starts OTAA too)
  321. do_send(&sendjob);
  322. }
  323.  
  324. void loop() {
  325. // we call the LMIC's runloop processor. This will cause things to happen based on events and time. One
  326. // of the things that will happen is callbacks for transmission complete or received messages. We also
  327. // use this loop to queue periodic data transmissions. You can put other things here in the `loop()` routine,
  328. // but beware that LoRaWAN timing is pretty tight, so if you do more than a few milliseconds of work, you
  329. // will want to call `os_runloop_once()` every so often, to keep the radio running.
  330. os_runloop_once();
  331. }

No comments:

Post a Comment