# LoRaWAN and MQTT hands-on examples In the following: :::success Green boxes indicate questions/exercises ::: :::info Blue boxes indicate useful information/indications ::: # Block 0: A LoRaWAN Network server from inside In this block we take a view from inside a LoRaWAN Network server, reviewing the process required to send sensor data using LoRaWAN, that is moving data from the sensor to the Network Server (NS). As network server we use [**The Things Networks**](https://www.thethingsnetwork.org). Another important Network Server is [**Loriot**](https://www.loriot.io/index.html). Some useful information is available [here](https://www.thethingsnetwork.org/docs/). Remember the overall LoRaWAN architecture: ![](https://i.imgur.com/kY5Ik6o.png) To start using TTN you need to create an account here: https://account.thethingsnetwork.org/register. Then, you will manage your applications and devices via [The Things Network Console](https://eu1.cloud.thethings.network/console/). ![](https://i.imgur.com/YQSX2lr.png) ## The "GATEWAYS" section ![](https://i.imgur.com/i0JtR9V.png) and this is the main gateway ![](https://i.imgur.com/WUKnK0I.jpg) :::info Let's add a gateway! ::: ## The "APPLICATIONS" section ![](https://i.imgur.com/EehYLn6.png) For example: ![](https://i.imgur.com/9X449K5.png) The devices you see here are in a lab in Valencia ![](https://i.imgur.com/JZFxSur.jpg =400x) ## Registering a Device The Things Network supports the two LoRaWAN mechanisms to register devices: Over The Air Activation (OTAA) and Activation By Personalization (ABP). Basically, select "End devices" and then click on: ![](https://i.imgur.com/V6zcWOt.png =100x50) Detailed instructions can be found here: https://www.thethingsindustries.com/docs/devices/adding-devices/ ## Send the data In the following demo we will use a device (a LoPy4 plus a PySense board) registered as indicated in the step before to periodically send the sensed temperature, humidity and luminosity (lux). The device "joins" the Application by: ```python= ... THE_APP_EUI = '70B3......02AE7C' THE_APP_KEY = '1F438669F5......D2FC6501C90F0586' def join_lora(force_join = False): '''Joining The Things Network ''' print('Joining TTN') # restore previous state if not force_join: lora.nvram_restore() if not lora.has_joined() or force_join == True: # create an OTA authentication params app_eui = binascii.unhexlify(THE_APP_EUI.replace(' ','')) # these settings can be found from TTN app_key = binascii.unhexlify(THE_APP_KEY.replace(' ','')) # these settings can be found from TTN # join a network using OTAA if not previously done lora.join(activation=LoRa.OTAA, auth=(app_eui, app_key), timeout=0) # wait until the module has joined the network while not lora.has_joined(): time.sleep(2.5) # saving the state lora.nvram_save() # returning whether the join was successful if lora.has_joined(): flash_led_to(GREEN) print('LoRa Joined') return True else: flash_led_to(RED) print('LoRa Not Joined') return False else: return True ... ``` The main code is something like this: ```python= lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868) print("Device LoRa MAC:", binascii.hexlify(lora.mac())) join_lora(True) while True: # create a LoRa socket s = socket.socket(socket.AF_LORA, socket.SOCK_RAW) s.setsockopt(socket.SOL_LORA, socket.SO_DR, 0) s.setblocking(True) temperature = tempHum.temperature() humidity = tempHum.humidity() luxval = raw2Lux(ambientLight.light()) payload = struct.pack(">fff", temperature, humidity, luxval) s.send(payload) time.sleep(60) ``` Now, if we go in the "Live data" section of the TTN Application, we will see something like this: ![](https://i.imgur.com/jT8oF2k.png) That is the data coming from the three devices that are using this application. If you click on any of the lines of the data, you'll get: ![](https://i.imgur.com/t6DqHlE.png) where you can find a lot of information regarding the sending of you LoRa message. If you check the **Payload** field, you will see a sequence of bytes... and that is actually what we sent ... To see what we actually sent, check again the file above: ```shell=python= temperature = tempHum.temp() humidity = tempHum.humidity() luxval = raw2Lux(ambientLight.lux()) payload = struct.pack(">fff", temperature, humidity, luxval) s.send(payload) ``` As you can see we are sending the values of temperature, humidity and luminosity (lux) "compressed" as a sequence of 4*3= 12 bytes (:arrow_right: ``... = struct.pack(">fff",...``). Now, to allow TTN to interpret these sequence of bytes we have to go the the section **Payload Format** and insert some specific code to interpret the input bytes. ![](https://i.imgur.com/J0cjzTf.png) :::info Let's add a device! ::: # Block 1: MQTT basic examples ## Using a web client For the experiment you will use an MQTT client that can be used in any OS, it's called MQTT-explorer: ![](https://i.imgur.com/F8IPtvN.png) You have to install it from here: http://mqtt-explorer.com ### Connecting to a public broker There are various public MQTT brokers, for example: * broker.hivemq.com * broker.mqttdashboard.com * test.mosquitto.org * mqtt.eclipseprojects.io In this session we will use the one offered by HiveMQ (broker.hivemq.com). Fill in the data as indicated below: ![MQTT client](https://i.imgur.com/BJWDHby.png) Then click on ```ADVANCED```, and add a topic: ![](https://i.imgur.com/DSLEVh0.png) if you want you can assing your own client ID. Now click on ```BACK```, and then on ```CONNECT```. You will start seeing something like this: ![](https://i.imgur.com/vKfrVBA.png) ## Using Python :::info The code used in this lab is [in the repository ![](https://i.imgur.com/5Un0gCm.jpg =40x)](https://www.dropbox.com/sh/442v6qr74fxtn8v/AAByarfKfA7OTl9eX7qV1P_Oa?dl=0) You can execute the code either in your computer or online: * online. Create an account in https://repl.it ![](https://i.imgur.com/oW5EJIc.png) * or in your computer. You must have python3 installed and the `paho-mqtt` library: ``` $ sudo pip3 install paho-mqtt ``` The documentation of the MQTT Paho API is here: https://www.eclipse.org/paho/clients/python/docs/ ::: ### A simple subscriber File `sisub.py` cointains the code of a simple python subscriber. This code connects to a public broker and subscribes to topic `$SYS/#`. $> python3 sisub.py :::success The same can be tested with the MQTT-Explorer. Try it. ::: ### A simple producer File `sipub.py` contains the code of a simple python producer. This code connects to a public broker and periodically publishes random values to topic `"PMtest/rndvalue"` $> python3 sipub.py :::success Check if it is working using the MQTT-Explorer... how? ::: :::success Check now with the previous code `sisub.py`. How do you need to modify it? ::: ### Getting data from TTN The Things Network [uses MQTT](https://www.thethingsindustries.com/docs/integrations/pubsub/mqtt-client/) to publish device activations and messages and allows you to publish a message for a specific device in response. You will now read the values of two LoRaWAN sensors that periodically send their data to the TTN Network Server from the GRC lab. ![](https://i.imgur.com/JZFxSur.jpg =400x) The basic data you need is the following: ``` Broker: eu1.cloud.thethings.network Username: lopys2ttn@ttn Password: NNSXS.A55Z2P4YCHH2RQ7ONQVXFCX2IPMPJQLXAPKQSWQ.A5AB4GALMW623GZMJEWNIVRQSMRMZF4CHDBTTEQYRAOFKBH35G2A Topic: v3/+/devices/# ``` :::success Modify the code of `sisub.py` to get the raw information from TTN. ::: You should get something like this: ``` { "end_device_ids": { "device_id": "lopy4sense2", "application_ids": { "application_id": "lopys2ttn" }, "dev_eui": "70B3D5499608A244", "join_eui": "0000000000000000", "dev_addr": "260B43DA" }, "correlation_ids": [ "as:up:01FXTGDC6TQXGTZH98H4Y6GM9P", "gs:conn:01FXHXEGDMRRZATNNSWW65HXNK", "gs:up:host:01FXHXEGDWRG04REYV4BQVT42V", "gs:uplink:01FXTGDC01G90BWY69T2B77QVN", "ns:uplink:01FXTGDC01XEXV3088WCQV21AS", "rpc:/ttn.lorawan.v3.GsNs/HandleUplink:01FXTGDC01MS322H19W0DZWHBA", "rpc:/ttn.lorawan.v3.NsAs/HandleUplink:01FXTGDC6T7ED46SCJT58CDJHV" ], "received_at": "2022-03-10T18:10:47.131017155Z", "uplink_message": { "session_key_id": "AX8BHqGvSx0QOpMF/Qk6ZQ==", "f_port": 2, "f_cnt": 27774, "frm_payload": "QeLVyEIFhDg/+kgv", "decoded_payload": { "humidity": 33.379119873046875, "lux": 1.9553278684616089, "temperature": 28.354385375976562 }, "rx_metadata": [ { "gateway_ids": { "gateway_id": "rak-918100h46011026402", "eui": "AC1F09FFFE059482" }, "timestamp": 569230277, "rssi": -136, "channel_rssi": -136, "snr": -22, "uplink_token": "CiQKIgoWcmFrLTkxODEwMGg0NjAxMTAyNjQwMhIIrB8J//4FlIIQxYe3jwIaDAim/qiRBhCh76mzAyCI85LGyMdB", "channel_index": 7 }, { "gateway_ids": { "gateway_id": "main-gtw-grc", "eui": "B827EBFFFE7FE28A" }, "time": "2022-03-10T18:10:46.897854Z", "timestamp": 2408760916, "rssi": -3, "channel_rssi": -3, "snr": 8.2, "location": { "latitude": 39.482534878470204, "longitude": -0.3463913363006933, "altitude": 9, "source": "SOURCE_REGISTRY" }, "uplink_token": "ChoKGAoMbWFpbi1ndHctZ3JjEgi4J+v//n/iihDUjMv8CBoMCKb+qJEGEP3Q1bgDIKDw2qqN/UEqDAim/qiRBhCw1JCsAw==", "channel_index": 2 }, { "gateway_ids": { "gateway_id": "rak-gtw-grc", "eui": "B827EBFFFE336296" }, "timestamp": 1238159932, "rssi": -87, "channel_rssi": -87, "snr": 7.5, "location": { "latitude": 39.48272119427445, "longitude": -0.3471749450839346, "altitude": 9, "source": "SOURCE_REGISTRY" }, "uplink_token": "ChkKFwoLcmFrLWd0dy1ncmMSCLgn6//+M2KWELyks84EGgwIpv6okQYQ9q+RugMg4PTUwITbQQ==", "channel_index": 2 }, ... ``` :::success OPTIONAL: Now you can slightly modify the code to get the actual data sent from the sensors; something like this: ``` Got these values >> temp=23.078 hum=-1.000 lux=32.235 Got these values >> temp=28.000 hum=-1.000 lux=34.714 ``` ::: # Block 2: TIG Stack for the processing and visualization of data The TIG Stack is an acronym for a platform of open source tools built to make collection, storage, graphing, and alerting on time series data easy. ![](https://i.imgur.com/TzgFn2G.png) A **time series** is simply any set of values with a timestamp where time is a meaningful component of the data. The classic real world example of a time series is stock currency exchange price data. ![](https://i.imgur.com/QUUpRV5.png =600x) Some widely used tools are: * **Telegraf** is a metrics collection agent. Use it to collect and send metrics to InfluxDB. Telegraf’s plugin architecture supports collection of metrics from 100+ popular services right out of the box. * **InfluxDB** is a high performance Time Series Database. It can store hundreds of thousands of points per second. The InfluxDB SQL-like query language was built specifically for time series. * **Grafana** is an open-source platform for data visualization, monitoring and analysis. In Grafana, users can to create dashboards with panels, each representing specific metrics over a set time-frame. Grafana supports graph, table, heatmap and freetext panels. ![](https://i.imgur.com/aANRGpe.png) :::info In this Lab we will use the containers platform [Docker](https://www.docker.com/). You can use Docker: * on your machine by installing it following the instructions here: https://docs.docker.com/get-docker/ * or online: create your own **Docker ID** at https://hub.docker.com/signup and https://labs.play-with-docker.com/# ::: We will use the following images: * https://hub.docker.com/_/telegraf * https://hub.docker.com/_/influxdb * https://hub.docker.com/r/grafana/grafana ## Setting up InfluxDB InfluxDB is a time-series database compatible with SQL, so we can setup a database and a user easily. In a terminal execute the following: ``` $ docker run -d -p 8086:8086 --name=influxdb influxdb:1.8 ``` This will keep InfluxDB executing in the background (i.e., detached: `-d`). Now we connect to the CLI: ``` $ docker exec -it influxdb influx Connected to http://localhost:8086 version 1.7.9 InfluxDB shell version: 1.7.9 > ``` The first step consists in creating a database called **"telegraf"**: ``` > CREATE DATABASE telegraf > SHOW DATABASES name: databases name ---- _internal telegraf > ``` Next, we create a user (called **“telegraf”**) and grant it full access to the database: ``` > CREATE USER telegraf WITH PASSWORD 'uforobot' > GRANT ALL ON telegraf TO telegraf > SHOW USERS user admin ---- ----- telegraf false > ``` Finally, we have to define a **Retention Policy** (RP). A Retention Policy is the part of InfluxDB’s data structure that describes for *how long* InfluxDB keeps data. InfluxDB compares your local server’s timestamp to the timestamps on your data and deletes data that are older than the RP’s `DURATION`. So: ``` > CREATE RETENTION POLICY thirty_days ON telegraf DURATION 30d REPLICATION 1 DEFAULT > SHOW RETENTION POLICIES ON telegraf name duration shardGroupDuration replicaN default ---- -------- ------------------ -------- ------- autogen 0s 168h0m0s 1 false thirty_days 720h0m0s 24h0m0s 1 true > ``` Exit from the InfluxDB CLI: ``` > exit ``` ## Configuring Telegraf We have to configure the Telegraf instance to read data from the TTN (The Things Network) MQTT broker. We have to first create the configuration file `telegraf.conf` in our working directory with the content below: ```yaml [agent] flush_interval = "15s" interval = "15s" [[inputs.mqtt_consumer]] name_override = "TTN" servers = ["tcp://eu.thethings.network:1883"] qos = 0 connection_timeout = "30s" topics = [ "+/devices/+/up" ] client_id = "ttn" username = "lopy2ttn" password = "ttn-account-v2.TPE7-bT_UDf5Dj4XcGpcCQ0Xkhj8n74iY-rMAyT1bWg" data_format = "json" [[outputs.influxdb]] database = "telegraf" urls = [ "http://localhost:8086" ] username = "telegraf" password = "uforobot" ``` the values in `[[inputs.mqtt_consumer]]` are our example values. Then execute: ``` $ docker run -d -v $PWD/telegraf.conf:/etc/telegraf/telegraf.conf:ro --net=container:influxdb telegraf ``` Check if the data is sent from Telegraf to InfluxDB, by re-entering in the InfluxDB container: ``` $ docker exec -it influxdb influx ``` and then issuing an InfluxQL query using database 'telegraf': > use telegraf > select * from "TTN" you should start seeing something like: ``` name: TTN time counter host metadata_airtime metadata_frequency metadata_gateways_0_channel metadata_gateways_0_latitude metadata_gateways_0_longitude metadata_gateways_0_rf_chain metadata_gateways_0_rssi metadata_gateways_0_snr metadata_gateways_0_timestamp metadata_gateways_1_altitude metadata_gateways_1_channel metadata_gateways_1_latitude metadata_gateways_1_longitude metadata_gateways_1_rf_chain metadata_gateways_1_rssi metadata_gateways_1_snr metadata_gateways_1_timestamp metadata_gateways_2_altitude metadata_gateways_2_channel metadata_gateways_2_latitude metadata_gateways_2_longitude metadata_gateways_2_rf_chain metadata_gateways_2_rssi metadata_gateways_2_snr metadata_gateways_2_timestamp metadata_gateways_3_channel metadata_gateways_3_latitude metadata_gateways_3_longitude metadata_gateways_3_rf_chain metadata_gateways_3_rssi metadata_gateways_3_snr metadata_gateways_3_timestamp payload_fields_counter payload_fields_humidity payload_fields_lux payload_fields_temperature port topic ---- ------- ---- ---------------- ------------------ --------------------------- ---------------------------- ----------------------------- ---------------------------- ------------------------ ----------------------- ----------------------------- ---------------------------- --------------------------- ---------------------------- ----------------------------- ---------------------------- ------------------------ ----------------------- ----------------------------- ---------------------------- --------------------------- ---------------------------- ----------------------------- ---------------------------- ------------------------ ----------------------- ----------------------------- --------------------------- ---------------------------- ----------------------------- ---------------------------- ------------------------ ----------------------- ----------------------------- ---------------------- ----------------------- ------------------ -------------------------- ---- ----- 1583929110757125100 4510 634434be251b 92672000 868.3 1 39.47849 -0.35472286 1 -121 -3.25 2260285644 10 1 39.48262 -0.34657 0 -75 11.5 3040385692 1 0 -19 11.5 222706052 4510 2 lopy2ttn/devices/tropo_grc1/up 1583929133697805800 4511 634434be251b 51456000 868.3 1 39.47849 -0.35472286 1 -120 -3.75 2283248883 10 1 39.48262 ... ``` Exit from the InfluxDB CLI: ``` > exit ``` ## Visualizing data with Grafana Before executing Grafana to visualize the data, we need to discover the IP address assigned to the InfluxDB container by Docker. Execute: ``` $ docker network inspect bridge ```` and look for a line that look something like this: ``` "Containers": { "7cb4ad4963fe4a0ca86ea97940d339d659b79fb6061976a589ecc7040de107d8": { "Name": "influxdb", "EndpointID": "398c8fc812258eff299d5342f5c044f303cfd5894d2bfb12859f8d3dc95af15d", "MacAddress": "02:42:ac:11:00:02", "IPv4Address": "172.17.0.2/16", "IPv6Address": "" ``` This means private IP address **172.17.0.2** was assigned to the container "influxdb". We'll use this value in a moment. Execute Grafana: ``` $ docker run -d --name=grafana -p 3000:3000 grafana/grafana ``` Log into Grafana using a web browser: * Address: http://127.0.0.1:3000/login * Username: admin * Password: admin <!-- or, if on-line: ![](https://i.imgur.com/28myVV1.png =400x) --> the first time you will be asked to change the password (this step can be skipped). You have to add a data source: ![](https://i.imgur.com/xL6Ebpt.png =200x) and then: ![](https://i.imgur.com/MDRTHmK.png =400x) then select: ![](https://i.imgur.com/T2Weqp6.png) Fill in the fields: ![](https://i.imgur.com/js7MeUK.png) ![](https://i.imgur.com/YdcP0zZ.png) **(the IP address depends on the value obtained before)** ![](https://i.imgur.com/hbeOhER.png) and click on `Save & Test`. If everything is fine you should see: ![](https://i.imgur.com/l5Msx7V.png) Now you have to create a dashboard and add graphs to it to visualize the data. Click on ![](https://i.imgur.com/NjUXOMX.png =400x) then "**+ Add new panel**", You have now to specify the data you want to plot, starting frorm "select_measurement": ![](https://i.imgur.com/eqt1Oh3.png) you can actually choose among a lot of data "field", and on the right you have various option for the panel setting and visualization. ![](https://i.imgur.com/U6mNe1g.png =250x) You can add as many variables as you want to the same Dashboard. :::success Give it a try! ::: # Block 3: sending data to a cloud based platform (UBIDOTS) In this block you will experiment about how MQTT can be used to send data to a cloud based platform. This procedure allows you to store your data in a cloud based repository and to analyze your data with software tools made available by the used platform. For these experiments we will use the [Ubidots](https://ubidots.com/) plaftorm. ## Connecting to the Ubidots platform You will have to first create your free account in the Ubidots platform here: https://ubidots.com/stem/ Then you have to add a **Device** (select first the "Devices" section in the top on the web page): ![](https://i.imgur.com/cmCWh9I.png =x200) choose: ![](https://i.imgur.com/M7nUX31.png =x100) and add a name, like (**use the name that you want!!**): ![](https://i.imgur.com/Bry2Axc.png =x200) You'll get: ![](https://i.imgur.com/4NAGDF3.png) Now click on the device name and you'll get to the variables creation section: ![](https://i.imgur.com/ZLZWn9F.png =x300) click on "Add Variable" and create a "Raw" type variable. **Use the name that you want**: ![](https://i.imgur.com/TbfMs8a.png =x300) Now you have your web system prepared to receive the data ## Sending data to Ubidots Now you will send data to our device **using MQTT with Python**. Take a look first to the Ubidots MQTT API Reference: https://ubidots.com/docs/hw/?language=Python#mqtt The name of the broker for educational users is **"things.ubidots.com"**. To interact with it, you will need a TOKEN. To get yours click on “API Credentials” under your profile tab: ![](https://i.imgur.com/StmHEVg.png) In my case I have: ![](https://i.imgur.com/pCmKyeQ.png) To connect to the MQTT broker you'll have to use your **``Default Token``** as the MQTT username, and `None` as password. The **topic** you have to use is **`/v1.6/devices/{LABEL_DEVICE}`** where you have to replace your value for the **API label** `{LABEL_DEVICE}` (e.g., mydevice_pm). ![](https://i.imgur.com/lExOClB.png =x200) The data must be represented using JSON. So, for example, to send the value 25 to variable `the_variable` of device ``mydevice_pm`` the code should look like: ```python= ... msg_to_be_sent = {"the_variable":25} client.publish("/v1.6/devices/mydevice_pm", payload=json.dumps(msg_to_be_sent), qos=0, retain=False) ... ``` You'll get: ![](https://i.imgur.com/YsboDek.png) :::success Repeat the above steps with your Ubidots data (credentials and name of the device and variable used) and using the "producer" code (`sipub.py`). The code has to send each time a random number between 0 and 100. You may want to use a python dictionary and then pass it to JSON. ::: :::info 1) To generate a random number, you can use this function: `random.randint(0, 100)`. 2) To handle JSON data, Python provides the `json` class. Example: ``` >>> import json >>> d = {'sensorId': 25} Para pasar de un diccionario a un JSON: >>> dj = json.dumps(d) Para pasar de JSON a un diccionario: >>> nd = json.loads(dj) ``` ::: ## Creating dashbord elements. Ubidots allows to visualize the data in various graphical ways. Go to the Dashboard section and add new widget associated with the variable of your device. ![](https://i.imgur.com/YDQBZ4z.png) See the alternatives you have and how they can be configured. :::success Create a dashboard to your liking with the data you have sent in the previous exercise. ::: ## Sending data from TTN :::success Using the code in `ttn_ubidots.py` as a starting point, write a program that reads data from TTN, as in the previous block, selects a variable (e.g. temperature), and publishes it to Ubidots. ::: # Block 4: data to Telegram Bot In this Lab you'll have to do something similar to the previous one, but this time the data from the TTN have to be accessed using a Telegram bot. The final result should be something like this: ![](https://i.imgur.com/r8HM7xw.png =300x) What you have to do first is create your own Bot. To generate an Access Token, you have to talk to the BotFather following a few simple steps as described here: https://core.telegram.org/bots#6-botfather Basically, you need a Telegram account and the Telegram app installed in your smartphone or computer. The "dialogue" will be something like this: ![](https://i.imgur.com/4DRaWKZ.png =400x) To program the Bot you can use, as a skeleton, the file `ptelegram.py`. Fill in the places in `ptelegram.py` where the text `___FILL_IN_HERE___` is found with the proper values. You can execute your Bot either in your computer or in repl.it and test it using a smartphone with Telegram. The package https://github.com/python-telegram-bot/python-telegram-bot is necessary to running the Bot. :::info If you are using your computer, than you'll have to install it using: ``` $ pip install python-telegram-bot ``` If you are using `repl.it`, you have to install it from the Packages menu: ![](https://i.imgur.com/ecbQt6I.png =x150) and typing in the search field `python-telegram-bot` and then on the **+** button You should eventually get to something like this: ![](https://i.imgur.com/sTh1DDY.png =x200) ::: :::success Check that the broker is working, something like this: ![](https://i.imgur.com/rWVrO13.png =300x) Remember that the parameters needed to read data from TTN are: ``` Broker: eu1.cloud.thethings.network Username: lopys2ttn@ttn Password: NNSXS.A55Z2P4YCHH2RQ7ONQVXFCX2IPMPJQLXAPKQSWQ.A5AB4GALMW623GZMJEWNIVRQSMRMZF4CHDBTTEQYRAOFKBH35G2A Topic: v3/+/devices/# ``` ::: :::success Modify the Bot so that it can return the temperature, humidity and brightness value based on the request. You should get something like this: ![](https://i.imgur.com/VAvnltG.png) :::