LoraWanDustSensor: Difference between revisions
m (→Cayenne) |
|||
Line 92: | Line 92: | ||
===== Cayenne ===== | ===== Cayenne ===== | ||
It's reasonably compact, it's a standard format, you can get a preview of the data in the TTN console. Interacts nicely with other platforms. | It's reasonably compact, it's a standard format, you can get a preview of the data in the TTN console. Interacts nicely with other platforms. | ||
[https://community.mydevices.com/t/cayenne-lpp-2-0/7510 Specification for Cayenne LPP 2.0] | |||
HOWEVER: | HOWEVER: |
Revision as of 16:17, 28 April 2020
Project LoRaWAN dust Sensor | |
---|---|
LoRaWAN airborne particulate matter sensor | |
Status | In progress |
Contact | bertrik |
Last Update | 2020-04-28 |
The idea
The idea is to create a system consisting of:
- a sensor that measures airborne particulate matter and sends the measurement data using LoRa to TheThingsNetwork.
- a forwarder application that collects the data from TTN and forwards it to luftdaten.info
This has been done before by other people, but it appears there is no universal solution. I am publishing all source code on github and will put up documentation on this wiki. Everyone invents their own payload format, something more universal like Cayenne LPP would be nice. However I could not find a way to encode particulate matter data using Cayenne, so I'll just invent my own payload format too.
A similar thing has been done by:
- https://github.com/VekotinVerstas/AQLoRaBurk
- https://github.com/alexcorvis84/LoRa_MakersAsturias
- TTN Ulm, see https://github.com/verschwoerhaus/ttn-ulm-feinstaub (the sensor code) and https://github.com/verschwoerhaus/ttn-ulm-muecke (the forwarder, in python)
- https://alexander-schnapper.de/2019/04/02/mobile-feinstaubmessung/
- Apeldoorn-in-data
- (others...)
One thing in particular I'd like to do better than existing solutions is to use proper OTAA for the LoRa connection to TTN. OTAA means over-the-air-activation and is a mechanism to dynamically negotiate communication/encryption keys instead of programmed specifically in each sensor node. Once the OTAA is done successfully, the node remembers the network id, device address, session keys, etc for future communication.
This makes it possible to have a single firmware image for all sensor nodes and it simplifies the setup:
- you flash the node with a unified firmware
- the node shows its unique id on the OLED
- at the TTN console, you register a new device with the unique id
- the sensor node receives encryption keys over the air automatically
- done!
(idea: an ESP32 has a wifi connection too, perhaps registering the node can be done fully automatically, over wifi/internet)
Next steps
- Are we bound by a 5 minute interval towards Luftdaten (dropping off the maps if sending less frequently)?
- Implement a kind of schedule: turn the sds011 on, wait some time, take a measurement, turn it off, wait for some time
- Luftdaten uses default send schedule of 145 seconds
- make this match the TTN send schedule? it's useless to do a measurement if we don't have TTN airtime to transmit it
- use the built-in on-off schedule of the sds011
- implement calculating the median of accumulated measurements (half of the measurements is higher, other half is lower)
- encode using Cayenne encoding, advantage is that it's more or less standard, and you can more easily interpret the data in the TTN console
- display SDS011 serial number and date code on the screen
Links
Useful links for the TTGO LoRa board:
- https://primalcortex.wordpress.com/2017/11/24/the-esp32-oled-lora-ttgo-lora32-board-and-connecting-it-to-ttn
- https://github.com/fcgdam/TTGO_LoRa32
- https://ictoblog.nl/2018/01/10/mijn-eerste-chinese-esp32-verbonden-met-the-things-network
- Example code that joins TTN by OTAA and saves the OTAA parameters
- https://github.com/CivicLabsBelgium/lora_particle_sensor
- https://github.com/Cinezaster/ttn2luftdaten_forwarder
Hardware
The node is based on Arduino, in particular a TTGO ESP32 board with onboard SX1276 LoRa chip. The sensor is an SDS-011, just like in the luftdaten project. For humidity/temperature, I'd like to use a BME280.
Page with correct pinout of the ESP32 LoRa board.
Luftdaten uses a cycle time of 145 seconds for the SDS011.
Proposed hardware connections:
- SDS011 5V to ESP32 5V
- SDS011 GND to ESP32 GND
- SDS011 TXD to ESP32 GPIO35 (maybe I can find two suitable pins close together)
- SDS011 RXD to ESP32 GPIO25 (maybe I can find two suitable pins close together)
- BME280 3V todo
- BME280 SDA todo
- BME280 SCL todo
- BME280 GND todo
Software
Source code
Source code is hosted on github:
- Arduino node, written in C/Arduino, built using platformio
- TTN-to-luftdaten forwarder, written in Java, built using gradle
Common
Packet format
New idea: encode everything in Cayenne.
Cayenne
It's reasonably compact, it's a standard format, you can get a preview of the data in the TTN console. Interacts nicely with other platforms.
Specification for Cayenne LPP 2.0
HOWEVER:
- A standard analog value has a resolution of 0.01 units, leaving a maximum of 655.35 ug/m3. This value is possibly exceeded in extreme cases (night of new year's day). Alternative: just encode it as a 'digital input' with units of 0.1 ug/m3.
- Still not as compact as direct binary encoding
So proposed format:
- PM10: digital input (type 1), channel id 100, with value in units of 0.01 ug/m3, saturated to 655.35 ug/m3
- PM2.5: digital input (type 1), channel id 25, with value in units of 0.01 ug/m3, saturated to 655.35 ug/m3
- Temperature: temperature (type 103), channel 0, with value in units of 0.1 degrees celcius
- Humidity: humidity (type 104), channel 1, with value in units of 0.5 %
Example:
0x64 0x01 0xXX 0xXX 0x19 0x01 0xYY 0xYY 0x00 0x67 0xTT 0xTT 0x01 0x68 0xHH <== PM10 value ===> <= PM2.5 value ==> <== temperature ==> <= humidity =>
Total payload size is 15 bytes. Over the air, LoRaWAN header adds some more data (13 bytes at least).
Values encoded are averaged using the median over the accumulated measurements over the measurement interval.
Binary
Proposed structure of packets transferred over LoRa:
- PM10 value, encoded in units of 0.1 ug/m3: 2 bytes, big endian
- PM2.5 value, encoded in units of 0.1 ug/m3: 2 bytes, big endian
- temperature, encoded in units of 0.1 deg C: 2 bytes, signed big endian
- relative humidity, encoded in units of 0.1%, 2 bytes, big endian
Total: 8 bytes
Not present value is 0xFFFF. Encoding is big endian.
Would be nice to use Cayenne for this, but I don't know if Cayenne has an id for particulate matter.
How other projects encode the data:
- TTN Apeldoorn (?): https://github.com/tijnonlijn/RFM-node/blob/master/dustduino_PPD42NS_example.ino#L327 sends 5 bytes
- 1 byte : 0x04
- 2 bytes: PM25(?) big endian
- 2 bytes: PM10(?) big endian
- TTN Ulm: https://github.com/verschwoerhaus/ttn-ulm-feinstaub/blob/master/ttnulmdust/ttnulmdust.ino#L225 sends 8 bytes:
- 2 bytes: PM10, big endian (unit 0.01 ug/m3)
- 2 bytes: PM2.5, big endian (unit 0.01 ug/m3)
- 2 bytes: humidity (unit of 0.01%)
- 2 bytes: temperature (unit of 0.01 degree Celcius)
- RIVM node, sends 20 bytes
- 1 byte temperature (unit deg Celcius ?)
- 1 byte relative humidity (unit % ?)
- 2 bytes pressure (unit?)
- 2 bytes pm10 (unit?)
- 2 bytes pm25 (unit?)
- 2 bytes op1 (unit?)
- 2 bytes op2 (unit?)
- 4 bytes latitude (unit?)
- 4 bytes longitude (unit?)
- Apeldoorn in data: https://github.com/nijmeijer/TTN_Apeldoorn_in_Data_2018/blob/master/AiD_Dust_2018/AiD_Dust_2018.ino#L184
- 4 bytes: pm2_5 float big endian (unit?)
- 4 bytes: pm10 float big endian (unit?)
- 4 bytes: humidity float big endian (unit?)
- 4 bytes: temperature float big endian (unit?)
A smaller payload means less time in the air, smaller chance of collision with other LoRa packets and more packets per hour.
LoraWan time budget
Consider the following:
- radio regulations generally have a 0.1% duty cycle requirement for each of the 8 frequencies, so according to the legal limits, there is about 86400x0.001x8 = 691s send time at the best case.
- TheThingsNetwork has a FUP of 30s of data upload per day
- The Luftdaten backend appears to run on a 5 minute interval, or 288 measurements per day.
- The default Luftdaten firmware sends new data every 145s by default
With the TTN FUP of 30s upload per day, we can spend 30s / 288 = 104 ms on each transmission.
Using the LoRaWAN airtime calculator we can determine which modes can be used. So with 288 measurements, we can spend 30s / 288 = 104 ms per packet. At 19 bytes payload, this is only possible at SF7, the highest LoRa speed. Stretching the TTN guideline a bit, say by a factor 2, we can achieve 288 transfers of 19 bytes each at SF7 and SF8. So you need to be relatively close to a gateway.
With a smaller payload, can we use higher spreading factors? :
- skip T/RH/P completely (8 bytes left): SF7 is possible, at SF8 we spend 102 ms per transmission
- skip P only (15 bytes left): SF7 is possible, at SF8 we spend 123 ms per transmission
So, in conclusion: With the FUP of TTN and use of Cayenne encoding, you can just barely send enough data to transport Luftdaten PM data!
Node
Source code for the particulate matter measurement node can be found on the github page.
Platformio
To compile and upload the code to the node, platformio is used.
To install platformio (example for Debian):
sudo apt install python3-pip sudo pip3 install platformio pio update
To compile and upload:
pio run -t upload
Design
The function of the node software is to collect data from the SDS011 (particulate matter) and BME280 (temperature/humidity) at regular intervals, encode this as a data packet and send it over LoRaWAN towards TheThingsNetwork.
For the LoRaWAN data connection, over-the-air activation (OTAA) is used. I use the following scheme, to keep administration to a minimum:
- The Device EUI is derived from the ESP32 MAC address, the node shows this on its OLED
- The App EUI is generated in the TTN console, it is the same for all nodes
- The App Key is generated in the TTN console, it is the same for all nodes
- The device is registered in the TTN console by the Device EUI (this doesn't happen automatically). Frame counter checks are disabled.
- OTAA is done only once for each node. After that, the OTAA parameters are stored in (simulated) EEPROM.
- A long press on the PRG button restarts the OTAA procedure
- OTAA progress is shown on the OLED
- If OTAA has been done successfully, the node restores the session parameters negotiated during OTAA on next bootup, so it can quickly resume sending data.
- I'm NOT saving the upload frame counter (this would be preferable), just disable the feature in the TTN console.
TODO to figure out:
- What about the channel setup? The node connects using 3 frequencies, but receives a bigger list of frequencies during OTAA JOIN.
I've seen the following from the node, receiving an ADR:
40829907: engineUpdate, opmode=0x8 40829935: EV_TXSTART 40829939: engineUpdate, opmode=0x888 40830013: TXMODE, freq=868300000, len=25, SF=11, BW=125, CR=4/5, IH=0 40944876: setupRx1 txrxFlags 0x22 --> 01 start single rx: now-rxtime: 5 40945013: RXMODE_SINGLE, freq=868300000, SF=11, BW=125, CR=4/5, IH=0 rxtimeout: entry: 40951170 rxtime: 40945001 entry-rxtime: 6169 now-entry: 5 rxtime-txend: 63524 41005584: setupRx2 txrxFlags 0x1 --> 02 start single rx: now-rxtime: 4 41005720: RXMODE_SINGLE, freq=869525000, SF=9, BW=125, CR=4/5, IH=0 41017003: process options (olen=0x5) 41017012: LinkAdrReq: p1:11 chmap:00ff chpage:00 uprt:01 ans:86 41017019: ??ack error ack=1 txCnt=0 41017073: decodeFrame txrxFlags 0x2 --> 22 41017312: Received downlink, window=RX2, port=-1, ack=1, txrxFlags=0x22 41017708: EV_TXCOMPLETE (includes waiting for RX windows) 41018027: engineUpdate, opmode=0x800
Backend
A Java program subscribes to the MQTT stream, decodes the telemetry packets and forwards them to the luftdaten API. There is no storage of measurement data in the Java application.
To receive data using mosquitto:
mosquitto_sub -h eu.thethings.network -p 1883 -t +/devices/+/up -u particulatematter -P ttn-account-v2.cNaB2zO-nRiXaCUYmSAugzm-BaG_ZSHbEc5KgHNQFsk
Example upstream data:
particulatematter/devices/ttgo_mac/up {"app_id":"particulatematter","dev_id":"ttgo_mac","hardware_serial":"000084B14CA4AE30","port":1,"counter":16,"payload_raw":"AAEALAAd/////w==","metadata":{"time":"2019-04-13T08:37:45.338427686Z","frequency":868.3,"modulation":"LORA","data_rate":"SF11BW125","airtime":823296000,"coding_rate":"4/5","gateways":[{"gtw_id":"eui-008000000000b8b6","timestamp":2000599916,"time":"2019-04-13T08:37:45.320735Z","channel":1,"rssi":-115,"snr":-3,"rf_chain":1,"latitude":52.0182,"longitude":4.70844,"altitude":27}]}}
Example downstream data:
particulatematter/devices/ttgo_mac/events/down/sent {"payload":"YPUvASalGgEDEf8AAcqtmOw=","message":{"app_id":"particulatematter","dev_id":"ttgo_mac","port":0},"gateway_id":"eui-008000000000b8b6","config":{"modulation":"LORA","data_rate":"SF9BW125","airtime":164864000,"counter":282,"frequency":869525000,"power":27}}
Gateway API:
https://account.thethingsnetwork.org/api/v2/gateways/eui-xxxxxxxxxxx