###### tags: `IoT` `Lopy4` `Pycom` `TIG-stack` `MQTT` `tutorials` # Indoor environmental monitoring using Micropython, MQTT and TIG-stack *Written by: Daniel Rodin, dr222ht* ## Table of contents 1. [Introduction](##Introduction) 2. [Project overview](##Project_overview) 3. [Objective](##Objective) 4. [Material](##Material) 5. [Computer setup](##Computer_setup) 6. [Putting everything together](##Putting_everything_together) 7. [Platform](##Platform) 8. [The code](##The_code) 9. [Transmitting the data / connectivity](##Transmitting_the_data_/_connectivity) 10. [Presenting the data](##Presenting_the_data) 11. [Finalizing the design](##Finalizing_the_design) 12. [Conclusion](##Conclusion) ## Introduction We are all concerned with the weather and climate outside but we often overlook our indoor climate where most of us spend a lot of our time. With peronal allergies growing it's nice to have the data points to make the possible changes and correct your indoor environment as much as possible. ![](https://i.imgur.com/fSfdlsf.jpg) ## Project overview This tutorial will show you how to build a IoT, Internet of Things, sensor node to monitor any indoor environment and present the data in a really fancy and cool dashboard. In this project we will use sensors to monitor the temperature, humidity, eCO² and tVOC but any sensor is appliclable in this scenario, it's a matter of what you want to measure. Light in the bedroom or humidity in the bathroom perhaps? The data from the sensors will be handled locally with a Raspberry Pi 3B+. The Pi will be acting as the server for all the services that we need for processing and presenting the data from the sensor node. **Estimated time to build:** ~4-10 hours (depending on user experience). ## Objective The objective with this project and tutorial is to explore and build a complete IoT-solution. From choosing sensors, connecting the hardware, coding the software for the sensor node and finally to process and present the data through our locally hosted data platform. The goal is to get a deeper knowledge how a full IoT-solution works and to get insights into how the indoor environment behaves and fluctuates over days, weeks, months and seasons. #### Bonus objective: At the end we will add a OLED display to present some graphics and the values of the sensors. Why? Beacause it's fun! ## Material Below are tables of the materials, sensors and equipment used in this project and an estimated cost of purchase for each part. Some of this you might already have and some parts are directly interchangeable with similar devices. <center> <img width="400px" src="https://pycom.io/wp-content/uploads/2018/08/wipy3DScreen.png" alt="lopy4 microcontroller"> <p> <a href="https://pycom.io">pycom.io</a> </p> </center> :::warning :question: Parts in the *"Nice to have"*-table are optional and not necessary to complete this tutorial. ::: #### Main components | Part | Function | Cost | Where? | | -------- | -------- | -------- | -------- | | Pycom Lopy4 | Microcontroller | €39 | [pycom.io](https://www.pycom.io/) | | Pycom Exp 3.1 | Expansion board for easy develeopment | €18 | [pycom.io](https://www.pycom.io/)| | USB data cable| Data and power | €5 | [Electrokit](https://www.electrokit.com/) or look in your cable box... | | Jump wires | Connecting sensors | €4 | [Electrokit](https://www.electrokit.com/) | | Breadboard | Simplify connections | €7 | [Electrokit](https://www.electrokit.com/) | :::warning :exclamation: Make sure the USB cable is data capable, not only for charging. ::: #### Sensors | Part | Function | Cost | Where? | | -------- | -------- | -------- | -------- | | DHT11 | Digital temperature and humidity sensor | €5 | [Electrokit](https://www.electrokit.com/) | | CCS811 | eCO2 and tVOC sensor| €29 | [Electrokit](https://www.electrokit.com/) | #### Nice to have | Part | Function | Cost | Where? | | -------- | -------- | -------- | -------- | | LoRa antenna | To connect via LoRa | €15 | [pycom.io](https://www.pycom.io/) | | LiPo-battery | If you want to take your solution on a walk | €17 | [Electrokit](https://www.electrokit.com/) | | Case (IP67) | Protect your gear! | €9 | [Biltema](https://www.biltema.se/bygg/elinstallationer/eldosor/kopplingsdosa-2000034324) | | OLED display | If you want to display text and graphics | €7 | [PchButik](https://pchbutik.se/kretskort/1523-oled-display-096-tummed-128x64-vita-pixlar-i2c-ssd1306.html) | ## Computer setup The development environment consists of Ubuntu 21.04 as the OS and Atom as the personal choice of IDE (Integrated Development Environment). The choice of IDE is deeply connected to the compatibility with Pycom and their plugin Pymakr which is compatible with Atom and Visual Studio Code. If you have chosen Pycom products, this [guide](https://docs.pycom.io/gettingstarted/) will help you to get started. #### What you need to do: 1. Install your choice of IDE. ([Atom](https://atom.io) / [VSC](https://code.visualstudio.com)) 2. Install Node.js (if required for your setup). ([Node.js](https://nodejs.org/en/download/)) 3. Install Pymakr plugin. ([Pymakr](https://pycom.io/products/supported-networks/pymakr/)) 4. Close the IDE. 5. Connect your microcontroller and make sure the firmware of the device is updated, if not, update according to device documentation. [Here](https://docs.pycom.io/updatefirmware/) you can find it for the Lopy4. 6. After the firmware is updated you can start your IDE. 7. If you are using Atom the Pymakr-plugin should start automatically and the REPL (microcontroller command line), it should look similar to below: ``` >>> >>> >>> ``` 7. Test it out with a simple calculation: ``` >>> >>> >>>1+1 2 >>> ``` 8. Your code can then be uploaded via USB to the device. Remember that `boot.py` and `main.py` are the filenames that will be read, in that order, by the microcontroller. :::warning :bulb: Remember, the documentation is always your best friend when trying to figure out how your specific device works. Always reference the documentation before you try something. ::: :::warning :exclamation: Trying to interface via Pymakr in Atom I ran into a problem with not being able to connect to the microcontroller via USB. The issue was that my user was not in the dialout group. If you encounter something similar use this command to add your user to the system dialout group: ```bash sudo usermod -aG dialout $USER ``` ::: ## Putting all the stuff together If you haven't played around with microcontrollers or electrical circuitry before I recommend that you catch up on some video tutorials on this topic since there is a real risk that you could damage components if you connect things the wrong way. Read the documentation for your specific microcontroller, in this specific case [Pycom docs](http://docs.pycom.io/) applies and [this video](https://www.youtube.com/watch?v=fq6U5Y14oM4) is a good start on how to use a breadboard at Youtube. The circuitry is quite straight forward, especially if you take advantage of a breadboard to share the power supplied from the microcontroller. You will need to use one digital pin on the **Lopy4** for the **DHT11**-sensor and the **SDA and SCL**-pins for the **CCS811**-sensor that uses the I2C-protocol. The I2C-protocol can handle multiple sensors on one input, in this case the wiring for one digital sensor and two I2C-capable sensors/devices with power and ground comes out to the below schematic. For this task you should reference your microcontrollers pinout, the pinout of the Lopy4 can be found [here](https://docs.pycom.io/gitbook/assets/lopy4-pinout.pdf). #### What you need to do: ![](https://i.imgur.com/oD02uQR.png) *This shows the complete circuitry. Pay attention to the crossed SDA/SCL-connections to chain multiple I2C-components, CCS811 and the OLED-display.* <hr> ![](https://i.imgur.com/cgHNjKO.jpg) *Here you can see the breadboard circuitry done without the sensors in place.* <hr> #### Power consumption To calculate the power consumption of the whole unit we reference the datasheets and do some of our own measurements to check the actual power draw during operation. Since this setup is mainly meant for static measurement indoors the amount of power draw is not critical but is more a choice of how often the measurements need to be made. The battery I've incorporated in to the design is more for testing purposes but at the same time it also acts as backup power. | Part | mA | State | | -------- | -------- | -------- | | Lopy4 | ~107 mA | WiFI transmit | | Lopy4 | ~30 mA | Idle | |DHT11 | ~0.2 mA |Read value | | CCS811 | ~30 mA | Read value | |SSD1306 OLED | ~10mA | Active display | Since the sensor values are read and sent once every minute the calculation of power consumption can be calculated like this: ``` 107 + 0.2 + 30 + 10 = 147.2 mA device consumption ``` If we consider the battery and how long the application could run on it in case of power loss, it comes out to a battery time of: ``` Battery life = Battery capacity / device consumption 2000 mAh / 147.2 mA = 13.58 hours ≈ 13.5 hours ``` Like previously mentioned, considering this device is not designed for battery operation decreasing the amount of reads and transmits will greatly increase battery life. ## Platform The platform chosen for this project is a self hosted, free and open source solution. The data will be sent from the microcontroller via [MQTT](https://mqtt.org/) to a MQTT-broker set up on a Raspberry Pi where also the so called **TIG**-stack (**T** for Telegraf, **I** for InfluxDB and **G** for Grafana) processes and presents the data. A all-in-one solution for our IoT-platform needs, lovely! [This](https://hackmd.io/kf2VG07gSZOSXlS5c4ERGg) is a nice guide to get you started with your TIG-stack. #### MQTT [MQTT](https://mqtt.org/) is a lightweight communication protocol especially suited for IoT. We will use it in this scenario to send the sensor data from our microcontroller to our MQTT broker (MQTT server) acting as a mailbox for our data stack to collect the data from. #### Telegraf [Telegraf](https://www.influxdata.com/time-series-platform/telegraf/) acts as the data colletor for the database and connects to the MQTT-broker for data collecting. #### InfluxDB [InfluxDB](https://www.influxdata.com/products/influxdb/) is the database we are going to use for this project. InfluxDB is a time series database and works perfect to store time defined data. #### Grafana [Grafana](https://grafana.com/) is a data monitoring solution where you can set up pretty much any kind of dashboard for your data. We will use it set up graphs and alerts for our collected sensor data. ## The code This part of the tutorial will be every specific to the hardware I used in this project but like previously mentioned, you will need to research what is required for the hardware you will be using if your choices differ from the ones in this tutorial. :::success :nerd_face: The complete code for the project can be found [here](https://github.com/d-rodin/iot_indoor_monitoring). ::: So, what do we need in terms of code? When it comes to connecting sensors available libraries is the key. I think I will put in a little box here for good measure: :::warning :exclamation: Before buying sensors for your project you should check the availibility of usable libraries in the language you will use. ::: Ok, with that heads up out of the way let's dive in to the code part of this project. #### Code for WiFi connectivity The Lopy4 defines a separate `boot.py` where code to be run at boot can be placed. This is where we will connect to our network via WiFi. Replace the `'WiFi SSID'` with your SSID and `'WiFi password'`. ```Python from network import WLAN import time import machine wlan = WLAN(mode=WLAN.STA) wlan.connect(ssid='WiFI SSID', auth=(WLAN.WPA2, 'WIFI password')) while not wlan.isconnected(): machine.idle() print("WiFi connected succesfully") print(wlan.ifconfig()) ``` This code will run before the `main.py` so if you have something else you want to define outside of your main code, for example a LoRa-connection, this is where you should define those things. #### Coding the application The `main.py` is where we call functions from the different sensor and other libraries like MQTT and the OLED display, define the I/O-pins we will use on the board and the main loop function to read and transmit the sensor data. #### Code libraries Below are the libraries used for this project. Like mentioned before, search for the correct libraries needed for your sensor and hardware setup, [github](https://github.com) and Google are your best friends. | Library | Used for | Link | | -------- | -------- | -------- | | `CCS811.py` | eCO2/tVOC sensor | [github](https://github.com/d-rodin/iot_indoor_monitoring/tree/main/lib) | | `dht.py` | Temp./Humidity sensor | [github](https://github.com/d-rodin/iot_indoor_monitoring/tree/main/lib) | | `icons.py` | Icons for the OLED display | [github](https://github.com/d-rodin/iot_indoor_monitoring/tree/main/lib) | | `mqtt.py` | MQTT client setup | [github](https://github.com/d-rodin/iot_indoor_monitoring/tree/main/lib) | | `SSD1306.py` | OLED display | [github](https://github.com/d-rodin/iot_indoor_monitoring/tree/main/lib) | Below are some of the key functions from `main.py`: Here we define **I2C** communication for the **CCS811** (and OLED display) and set the pins 9 and 10 as SDA and SDL pins. (See another example on I2C [here](https://docs.pycom.io/tutorials/hardware/i2c/#app).) ```python i2c = I2C(0) i2c = I2C(0, I2C.MASTER) i2c = I2C(0, pins=('P9','P10')) # create and use default PIN assignments (P9=SDA, P10=SCL) i2c.init(I2C.MASTER, baudrate=10000) # init as a master ``` To define the **DHT11** pin and setting the variable `th`: ```python th = DHT(Pin('P23', mode=Pin.OPEN_DRAIN), 0) # Type 0 = dht11 ``` Sending the data via MQTT requires defining the MQTT broker and the topic we will publish our data to: ```python broker_url = '192.168.1.254' # Address local MQTT broker client_id = '1' client = MQTTClient(client_id, '192.168.1.254', port=1883) client.set_callback(sub_cb) client.connect() client.subscribe(topic="devices/indoorenv/control") ``` To get a continous function of the sensor node we write a loop that will read and transmit the data endlessly until the node is shut down or loses power: ```python while True: time.sleep(5) # give the sensor some breathing room ... ccs.data_ready() # read from CSS811 eco2 = ccs.eCO2 # define the variable for eco2 tVOC = ccs.tVOC # define the variable for tVOC result = th.read # read from the DHT11 while not result.is_valid(): time.sleep(.5) result = th.read() ``` At the end of while-loop the MQTT send function is defined as below. Here we format the message in JSON-format so that it can be easily read by InfluxDB: ```python client.publish(topic="devices/indoorenv", msg='{"indoor_sensor": {"eco2":' + str(eco2) + ',"tvoc":'+ str(tVOC) + ',"temp":' + str(result.temperature) + ',"rh":' + str(result.humidity) + '}}') print('Sensor data sent!') ``` #### The OLED display So, here comes the extra bonus part if you want to add a OLED display to your solution. The library is for the SSD1306 OLED and I used [this](https://www.dcode.fr/binary-image) website to convert images to binary images (1's and 0's). We import the library and the define the display size with the width and height in pixels. ```python import SSD1306 OLED_WIDTH = 128 OLED_HEIGHT = 64 # initalize the ssd1306 oled screen oled = SSD1306.SSD1306_I2C(OLED_WIDTH, OLED_HEIGHT, i2c) black = 0x000000 # black color # draw a black rectangle as a way to clear the screen def clear_oled(oled): oled.fill_rect(0,0,OLED_WIDTH,OLED_HEIGHT,black) # draw a symbol from icons.py list-icons def draw_symbol(oled, shape, x=0, y=0): for i, row in enumerate(shape): for j, c in enumerate(row): oled.pixel(j+x, i+y, c) ``` This next part is the part where we will draw the icons and text on the display. I think the code speaks for itself, but to give a little a more detail; the `clear_oled(oled)` function is what resets the screen to empty and black, `draw_symbol()` draws the icons from `icons.py` library and `oled.text()` writes out plain text on the display. All these functions need a **x** and a **y** value to define where on the screen the function should draw. Below is the code I used to draw the icons and values from the sensors connected: ```python clear_oled(oled) draw_symbol(oled,icons.temp,0,0) oled.text("Temp: "+str(result.temperature)+"C",32,15) draw_symbol(oled,icons.rh,0,34) oled.text("rH: "+str(result.humidity)+"%",32,49) oled.show() time.sleep(10) clear_oled(oled) draw_symbol(oled,icons.co2,0,0) oled.text("eCO2: "+str(eco2)+" ppm",32,15) draw_symbol(oled,icons.tvoc,0,34) oled.text("tVOC: "+str(tVOC)+" ppb",32,49) oled.show() time.sleep(10) clear_oled(oled) oled.show() time.sleep(60) ``` ## Transmitting the data / connectivity The data is transmitted from the sensor node to the local data stack via WiFi and MQTT once a minute. The choice for WiFI is simple, the LoRa coverage in my area is unreliable and the time to set up a gateway at home was not really there. A gateway will definitely be a future project! #### Power and range The issue with using WiFi and sending so frequently as I do will of course limit the possibilties of running the sensor node on battery alone. Since the goal of the project is to monitor the indoor environment I wasn't too concerned with the power consumption since the placement of the sensor node can be made close to a power outlet to power the device via USB and the battery acting as a power backup. The transmission range is of course limited to the WiFi coverage but monitoring inside a home or business should not be an issue. The placement can be quite flexible as long as there is a power outlet available within a few meters. #### MQTT Setting up a MQTT broker locally on a Raspberry Pi is quite straight forward, I choose the open source application [Mosquitto](https://mosquitto.org/) as my MQTT broker. I can recommend [this guide](https://appcodelabs.com/introduction-to-iot-build-an-mqtt-server-using-raspberry-pi) to try out and understand how to setup your own local MQTT broker. Another excellent tool to understand and troubleshoot your solution is [MQTT Explorer](https://mqtt-explorer.com/), it is also possible to plot graphs of the values sent over MQTT in this application! ![](https://i.imgur.com/zJ9ajuc.png) ## Presenting the data The dashboard is built in **Grafana** with the data colletced and stored in the **InfluxDB** database. Regarding the amount of time the sensor data is preserved in the database is an ongoing investigation. I want to test the limits of the Raspberry Pi and adapt the storage time of data or perhaps expand on storage. The interesting part is monitoring the environment of over an extended amount of time. Because of this I have not limited the storage time as of yet. #### Why InfluxDB? The reason is quite straight forward. It's a free and open source product and with proven functionality within the TIG-stack. InfluxDB bases it's data on time series which fits perfectly with data collected over time. ![](https://i.imgur.com/NVSAuVK.png) #### Trigger happy With Grafana it's very easy to integrate alerts that can trigger a notification. I decided to integrate Discord for my notification needs. One of the solutions is when a extended temperature of over 25 degrees Celcius is obeserved Grafan triggers an alert and notifies me in my Discord server: ![](https://i.imgur.com/DwY0187.png) ![](https://i.imgur.com/hnQya6t.png) ![](https://i.imgur.com/9iMpOZv.png) Your own imagination is the limit when it comes to defining your own detailed alerts. Be creative! ![](https://i.imgur.com/x40Lpnf.png) This is how the Discord message looks like when Grafana sends out the alert notification to my Discord server. You can define a specific *#channel* and a *@'user'* in the alert configuration in Grafana to make sure the notification doesn't go unnoticed. ## Finalizing the design Ok, almost there! To finalize the project is decided to put all my fancy components in a somewhat sturdy case. I looked through the electrial hardware section of a few local stores and found a electrical connection box classified as IP67 that suited my needs. I'm not gonna be submerging it any time soon but the cable passthroughs I'm gonna glose with filters as soon as I find a suitable material. Below are some pictures to show the solution mounted in the case: ![](https://i.imgur.com/J9N6yvE.jpg) ![](https://i.imgur.com/209bnyn.jpg) ![](https://i.imgur.com/QJE2cN4.jpg) ![](https://i.imgur.com/tt0xZYD.jpg) ![](https://i.imgur.com/hbMADHZ.jpg) ## Conclusion Overall I am more than happy how the project turned out. Especially that the plan to have it applicable anywhere with minimal change to the code or configuration. To have it implemented somewhere else, merely the networking aspects, like IP addresses has to be changed and that is not a very time consuming job. Some final points on what I feel could be improved: * **Circuitry** - Tidy up, soldering the components on a prototype circuit board. * **Code** - The code can without a doubt be improved. It's very much "prototyped" as of now. * **Case/Enclosure** - A 3D-printer... :drooling_face: * **Power** - Optimise the power consumption for periodic battery operation. Maybe construct a outdoor version? All in all the journey doesn't end here. I'm already planning for the next IoT-adventure, and so should you! I hope you enjoyed this tutorial! ## Video presentation <iframe src="https://player.vimeo.com/video/583903359" width="640" height="360" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>