:::info
# Tutorial on how to build a environmental monitor with alerts
:::
###### tags: `IoT`, `Temperature monitoring`, `Automation`, `AC`
#### By A
:::danger
Guide pending revision as Git links down.
:::
:::warning
The project is about building an temperature & humidity monitoring station. The purpose is to monitor the temperature and humidity and do certain actions depending what temperature and humidity it reaches.
:::
**Project cost:**: € 67,60 euro (approximately), 720 SEK
**Time spent**: To finish this tutorial it would take you a 4 - 8 hours depending on prior experience and familiarity with IoT.
## Objective
I chose this project as I have a habit of closing my room and not opening the window which results in discomfort. I need a way to monitor my room and do certain actions depending on what temperature it gets.
In turn this would give me more experience about IoT and also be a useful product I can use after the course is over.
The purpose of the project is to monitor my room temperature and alert me when the temperature goes over a certain limit. In addition it also serves to trigger certain actions when a temperature reaches a certain point to turn on a fan, but this could be adapted for an AC as easily.
The project will give me a more accurate sense of my temperature readings in my room and help me figure out retroactive and proactive solutions such as opening the window and also deciding if I should get an AC and monitor temperature.
Insights could be further gotten by correlating my data with the ambient weather outside and seeing how much difference there is, such research could give me insight into how well isolated my room is from the outside.
In addition it will help improve my comfort in my room better especially during summer times.
## Material
### Materials
:::info
You can optionally buy any generic 5v 1a power adapter if you want to power with power,
I can recommend [Kjell Power Adapter](https://www.kjell.com/se/produkter/dator/raspberry-pi/luxorparts-natadapter-for-raspberry-pi-25-a-med-strombrytare-p88056) for 249,90 SEK.
:::
The material of bill contains approximate prices. As the parts were bought in Sweden the links are to Swedish webstores.
Some parts were bought in bulk but only a few were used as they are often cheaper in bulk.
:::info
Any ESP-32 board will work but may require minor modifications to the code or a different pin.
:::
| Hardware | Quantity | Description | Price and link |
| ------------------------- |:---------|:----------------------- |:---------------|
| AZDelivery ESP32 NodeMCU-modul WLAN WiFi Dev Kit C Development Board med CP2102 | x1 | ESP-32 is a microcontroller. | [134,99 SEK](https://www.amazon.se/gp/product/B078SVZB1X/ "Amazon") |
| DHT22 Temperature Sensor | x1 | DHT22 is a temperature and humidity sensor to measure the environment. | [115,99 SEK](https://www.amazon.se/gp/product/B078SVZB1X/ "Amazon") |
| AZDelivery SPI TFT-screen | x1 | The SPI-TFT screen is a powerful screen that an show a range variety of colors and have a high refresh rate. | [110,99 SEK](https://www.amazon.se/gp/product/B078J5TS2G/ "Amazon") |
|
TP-Link Kasa mini smart plug (WiFi)| x1 | TP-Link Kasa mini smart plug is a plug & play device that can interface with various IoT solutions to toggle power enabling powerful control. | [185,90 SEK](https://www.amazon.se/gp/product/B08339SP84/ "Amazon") |
| AZDelivery 5 x MB-102 Breadboard 830 Point Lödlös Tie-Point Experimental Board - MB102 PCB| 1 bundle | Breadboard used for prototypes, helps organize components. | [119,99 SEK](https://www.amazon.se/gp/product/B07VCG6Q68/ "Amazon") |
| AZDelivery Bygelkabel 3 x 40 ST. 20 cm M2M / F2M / F2F | 1 bundle | Wires for connecting components | [90,29 SEK](https://www.amazon.se/gp/product/B074P726ZR/ "Amazon") |
| Resistor 200 ohm | x2 | Resistor used to regulate current | [2 * 2,40 SEK](https://www.electrokit.com/produkt/motstand-metallfilm-0-125w-1-220ohm-220r/ "Electrokit")
### Purchasing decision
I decided to go with a ESP-32 for my project, for the price of around 14 euro you get a very capable microcontroller with WiFi and Bluetooth connectivity with 38 pins.
The ESP32 I went for supports bluetooth and Wi-Fi, in my case as it is a IoT device designed for home use I decided to opt for Wi-Fi. There is no available LoRa gateways around me. Hence the option to use LoRa did not interest me but is an possibility and would be a few changes in the code to accompany it.
The smart plug decision was based on the fact that I acquired another brand smart plug (Mi Smart Plug) but due to manufacturer constraints I could not connect programmatically to the device, which made it obsolete to use in Node-RED hence it was returned. After Mohammed (TA) suggestion I decided to go with TP-Link Kasa instead.
In addition the temperature sensor was compared to an off the shelf temperature sensor and it got similar results, at some point even having same result when pointed at the same point. As the off-the-shelf temperature sensor updates more than the LCD which updates every 60 seconds they were not completely in sync, but this is not due to measurement but rather code measures taken. This in ensures it has some good accuracy compared to other temperature products on the market.

>Fig. 1. Temperature comparison
Also I tried out a LMD35DZ but it seemed calibrating it seemed to be a pretty big task and relied on having reliable power. As seen on the [internet](https://forum.arduino.cc/t/basic-temperature-reading-lm35-problems-with-adc-values/556144) there was several people with issues calibrating those.

>Fig. 2. LMD35DZ bad values.
## Computer setup
### Firmware
I decided to flash Micropython on it, as Python is a easy language to learn and pick up. Python was also the language used in the course. It allows for easy writing a program and debugging it compared to C language which requires a higher entry-barrier level.
The device is programmed with ESPTool to push the latest MIcropython firmware onit.
#### Flashing
These instructions take in account Windows.
Go to https://micropython.org/download/esp32/
Retrieve the latest firmware, as of this writing it's `v1.19 (2022-06-16) .bin`.
To flash we have to plug the board into the computer with a USB cable, at this point you may need to install the drivers, on my computer it worked without drivers for this specific board. If you do need drivers you have to lookup the USB serial chip and download drivers for those.
Some boards require having to hold boot button while flashing but not this board, check material bill section for reference to what board it is.
Opening device manager should show you the COM device and note down the port, if you are not aware which device it is you could unplug and plug it in again to note down the port.
The website for flashing the ESP-32 is [ESP Web Flasher](https://nabucasa.github.io/esp-web-flasher/).
Select the 460800 baud rate as it improves upload speed, if you experience issues due to different cables as such try a different lower option, it should only affect firmware write speed. Baud rate defines the communication speed from and to the device. See figure 4.
Once you press connect it will show you the COM devices, pick the COM port you noted above, for me it is COM4.

>Fig. 3. Baud option.
Now it will load and you will get an option to upload files, as our device requires the offset to be 1000 we will type 1000 in the first field and then choose the file we downloaded from MicroPython, see figure 5.

>Fig. 4. Flashing 1000 offset.
After you press Program a progress bar will appear, let it finish. See figure 6.

>Fig. 5. Flashing progress bar.
Once that is done we can proceed with the IDE installation.
### IDE Setup
1. Install [Atom](https://atom.io/) following the instructions for your operating system, Atom will be used to program the board and is an IDE (integrated development environment.).
The LNU [guide](https://hackmd.io/@lnu-iot/SydH7MTcw) can be followed to install Atom and Pymakr. Node.js is also required and is linked in the above guide.
The Atom [install guide](https://flight-manual.atom.io/getting-started/sections/installing-atom/) can also be looked at if issues occur with above guide.
2. Install PyMakr, if following the LNU guide above you can skip this part. Follow [plugin install](https://flight-manual.atom.io/using-atom/sections/atom-packages/) and choose Pymakr from the list. For configuration refer to [plugin page](https://atom.io/packages/pymakr).
### Uploading code
Uploading guide is done with Atom, after configuring the plugin and the IDE you should be able to connect the device. To get a more detailed guide follow this [guide](https://hackmd.io/@lnu-iot/SJ91R_jSO#Windows-OS).
## Putting everything together
The circuit diagram below is provided for reference where to connect each board. I will show this connection with a breadboard as it makes assembly easier.

>Fig. 6. Circuit diagram, Made in [Fritzling](https://fritzing.org)
| LCD Pin | ESP-32 Pin | Color |
| ---------- | ---------- | ------ |
| LED (3.3v) | 3.3v | Red |
| SCK | 18 | Blue |
| SDA | 23 | White |
| A0 | 2 | Green |
| RESET | 4 | Gray |
| CS | 5 | Yellow |
| GND | GND | Black |
| VCC (3.3v) | 3.3v | Red |
The LED communicates with I2C.
In my setup I took two breadboards, and removed one of the voltage pieces of it so I could connect all of the 38 ESP-32 pins and access them easier.

>Fig. 7. Two breadboards
The part being easily removed from the breadboard with a simple flick.

>Fig. 8. Breadboard removing part
The ESP-32, temperature sensor and LCD mounted on the board, in this case it is easier to mount on a breadboard then directly doing wires to the ESP-32, as debugging is easier if we wanna add more wires or components.

>Fig. 9. Components mounted except wiring
I will now show my breadboard setup, refer to figure 7 and [breadboard wiring guide](http://wiring.org.co/learning/tutorials/breadboard/) to understand how to wire with a breadboard.

>Fig. 10. Components mounted with wiring.
After all components are wired with the wiring table at the start of this section you are ready to proceed to the next steps. This setup is mostly for prototyping, with minimal modification this can be mounted in it's own case and used in actual long-term production.
The LCD is connected with two 220 Ohm resistors to ensure it can not damage the board as it can handle a maximal 15 mA per pin and in total 80 mA in the output.
I would also suggest setting up the smart plug following this [guide](https://www.tp-link.com/us/support/faq/946/) so it can be reached on the network for Node-RED.
### Power calculations
:::info
Right click and open image in a new tab to get a better detail.
:::
For the LCD as we have no information from the datasheet some estimates were made, and a 220 Ohm resistor was averaged to offset both VCC pins.

>Fig. 11. ESP-32 electrical diagram, AZDelivery ESP-32 electrical schematic
A pinout diagram is also supplied incase you want to develop this project further and to give an overview,

>Fig. 12. ESP-32 pinout diagram, AZDelivery ESP-32 electrical schematic
To calculate capacity of battery divide capacity with mA and then you get hours.
#### ESP32
:::danger
These values have some assumptions due to lacking information. Contacting Espressif and AZDelivery would have taken longer to clarify some terms.
:::
:::info
Some values are also simplified to make it easier.
:::
The data sheet states that the average working current is 80 mAh, the data sheet does not detail any further information.
The board itself has a variety of conflicting information, as detailed information for my specific revision is not available, I will take a generic ESP-32 board.
Per [ESP32 energy discussion](https://esp32.com/viewtopic.php?t=2662) we can conclude CPU + electronics takes an average of 73 mA, in addition reading about deep sleep some statistics are posted about radio, as WiFi supports sleep and the device seems to support it natively we can add approximately 95 mA to round up.
If we have a 3000 mAh battery just the ESP-32 without any components will run for about `3000 / 95 = 32` hours. This could be improved by doing away with the LCD, which would make you lose real-time statistics.
Power calculation: (capacity) / mA = hours
Source for board details, [AZDelivery product page](https://www.az-delivery.de/products/esp32-developmentboard)
The board in addition has the WiFi cost calculated into the average working current as per spreadsheet. And on board boot-up WiFi has to connect which is when WiFi takes the highest usage, once it's connected the power usage is vastly different.
The ESP-32 board usage is a constant variable as it's an average, meaning the average value is taken and then can be estimated for one hour,
| Component | Usage | Note |
| ------------------------- |:---------|:-----|
| ESP-32 Board | 80 mA | Average from specification added with transmit specifications |
| DHT22 | 1 mA (Per measurement) | Usage per measurement |
| LCD | 20 mA | Usage constantly |
The DHT22 (temperature & humidity sensor) takes on average 40 uA when on standby mode, and on active mode it takes 1 mA

>Fig. 13. DHT22 datasheet power usage, AZDelivery DHT22 [source, -> datasheet](https://www.az-delivery.de/products/dht22-temperatursensor-modul)
There is no mAh calculation present in the data sheet, as such we will estimate, we know take measurements every minute, in one hour we take on average 60 mAh.
:::info
Setting the polling time to longer intervals would take overall less power.
:::

>Fig. 14. DHT22 timing diagram, [source, -> datasheet](https://www.az-delivery.de/products/dht22-temperatursensor-modul)
Seeing figure x a period lasts for around 50 microseconds, to simplify calculations and make calculations easier we will assume a measurement is around 1 second.
In addition as a microampere (µA) is a very low unit it is not interesting to know how much it takes idle in our case, as it will not impact calculations as it's insignificent. As such we will omit it from the calculations.
#### LCD - AZDelivery SPI TFT-Screen
:::warning
The datasheet for this specific LCD contains no relevant data. As such the closest matching LCD data was found and used. With proper equipment actual measurements could be taken but as I did not have those, the measurements could not be taken.
:::
The LCD takes around 20 mA while in operation, as the LCD is powered always as it's meant to be wall-mounted we can use this to calculate overall power usage, as a measurement with a similar chipset and LCD specifications were calculated which will be used. [Youtube Link](https://www.youtube.com/watch?v=M-fKkN0bKA0).
#### Deep sleep - Thoughts
As the device is always meant to be plugged into a power source there would minimal gains by using deep sleep, in other cause if it was battery powered savings could be made.
As data is taken every minute there there would be savings when using a battery. One change with using deep sleep would be switching to LoRa instead of WiFi as WiFi takes longer to power up and also transmit data. Looking at [deep sleep data](https://randomnerdtutorials.com/esp32-deep-sleep-arduino-ide-wake-up-sources/) on average we can conclude that WiFi takes around 150 mA when transmitting, compared to LoRa that takes around 4.2 mA when transmitting [source](https://www.digikey.com/en/articles/how-to-implement-lora-fota-minimal-power-consumption).
Deep sleep was explored but as the application uses WiFi it would not give much savings, as once the device came out of deep sleep it would force itself to restablish a connection. As the device itself supports modem sleep which seems to be enabled by default on my revision.
In conclusion as the board itself would give around 32 hours battery time the temperature sensor would not impact much of that, and if this were to be battery operated then the LCD would not be a part of the final design.
The electrical calculations for power usage consists of getting the mA and then adding the average over a period of one hour to convert into mAh (mili-ampere hours), as this device is power-plugged these calculations other then ESP-32 would not make much difference.
## Platform
### Platform selection
For platform I picked InfluxDB for storage and database solution, and Grafana for visualization purposes. In addition I picked MQTT as a broker, LNU already had a server running MQTT, but as I needed privacy and long-term usage I opted to also host my own MQTT server. Node-RED was picked as the brain and controller of the logic, as it allows very high flexibility to customize.
:::info
Docker files which were used to run the platform choices can be found at [Github Repository](https://github.com/alija3000/environmental_iot/tree/main/docker%20configuration)
Familiarity with Docker is required. Docker documentation can be found at [link](https://docs.docker.com/).
:::
I opted to look into options that could be hosted on my own device, hence cloud alternatives were disregarded due to privacy and wanting to keep it all self-contained for future self-sustainability.
Hence I went with these platforms:
 **InfluxDB** is a time series database, it has a lot of query options and a lot of supported software written for it. As it is enterprise-grade the support and longevity maintenance is very good. This is used to store all the metrics data.
 **Grafana** is a visualization platform that supports a variety of sources. It has support for showcasing the data in multiple ways. Grafana can also be self-hosted and has a lot of options to install plugins and customize it.
**MQTT** is a publish / subscribe system which allows devices to keep in touch with each other and notify on changes. As MQTT is supported on every platform and configuration it is a good choice as it interacts with a lot of other software I picked for this project. I run the mosquitto server which implements the MQTT protcol.
 **Node-RED** is a modular block building platform for logical flows, as it supports over thousands of third-party addons and customization it has a big community behind it. In addition it has built-in native support for MQTT and an addon for InfluxDB. It can be used to write custom alerts to any platform due to it supporting HTTP requests as well, hence it can communicate with almost anything.

The stack runs on Docker which is hosted on a Linux server, Docker itself can run on a variety of devices such as Raspberry Pi. For installing Docker referring to the [documentation](https://docs.docker.com/).
Docker also supports a feature called compose which is a way of running several docker containers in a unified manner. Docker can be ran on Raspberry Pi which is a cheap way to get the stack going and portable. A raspberry PI can be gotten for around. To the [LNU docker compose](https://github.com/iot-lnu/tig-stack) I added Node-Red following this [guide](https://nodered.org/docs/getting-started/docker). I also added Mosquitto (MQTT Broker) following this [guide](https://github.com/vvatelot/mosquitto-docker-compose). In addition my docker compose files are linked above at the section.

>Fig. 15. Raspberry Pi, https://www.raspberrypi.com/products/raspberry-pi-4-model-b/

>Fig. 16. Personalized platform stack, Inspiration from: https://lucassardois.medium.com/handling-iot-data-with-mqtt-telegraf-influxdb-and-grafana-5a431480217
The device connects to WiFi and communicates with the MQTT broker and uses MQTT to send temperature and humidity stats and also keep the status of the connected smart plug to know when the fan is active, this data is actively sent every time fan updates the status.
In addition temperature it sent every minute, but this is covered in the data section of this document.
Once Node-RED receives the data it handles storing the data in InfluxDB, where Grafana can query and show graphs for the data. In addition Node-RED also hosts a web dashboard for temperature controls to know w
In the original revision I originally had used telegraf to ingest data into Grafana, but as I moved over to Node-RED for alerts and controlling the fan I decided to also move the storing to influxDB to Node-RED. In this case using Telegraf would have been redundant.
Grafana also supports alerts natively which means you can send a message to any of the supported services and see if the increases. This allows me to simply check my phone for any alerts sent.
The notification channels you can send to receive on alerts are many but the most notable ones in no specific order, and as webhooks are supported the customization you can do is very massive:
* Discord
* Email
* Slack
* Telegram
* Webhook
The alerts in this case were done with Node-RED as it offered more options how to structure an alert and also writing javascript functions.
One issue with Grafana is alerts can not trigger multiple times but the alert has to be reset, in this case Node-RED gives way more flexiblity with alerts. For visualization purposes Grafana wins hands down due to the flexibility and the graphs can be structured in multiple different ways, such as showing a gauge or a chart.
There was a lot of different applications such as datacake, but as I scoped my project to being able to self-host the entire stack I did not review these offerings any further. Node-RED also had very good third-party support for my smart plug (TP-Link Kasa) which allowed me to control the smart plug. In addition the TP-Link API supports sending events such as when it is triggered by another application, this allows me to keep the LCD updated with the fan status always. The Kasa application for the smart plug also offered timers and various options which could still be used with this setup.
The mobile application MQTT dashboard was tried out very briefly, it uses MQTT to communicate with other endpoints. In this case the issue was the application always has to be running to stay updated with data. As I was already using Grafana to display data I opted to use node-red to leverage a web dashboard only for control purposes.
Docker was also picked as it allows launching my entire platform stack in one simple command once everything is setup. This allows me to also make backups in a consolidated manner as the data is stored in Docker volumes and folders.
InfluxDB is a very powerful time series database, and has very good support with most software hence it is a good choice. In addition the queries can be made very powerful, in this case as my data is time oriented it made more sense to use this than a relational database that would have made querying time queries harder.
### Privacy concerns and conclusions
:::warning
MQTT transmissions can be encrypted using certificates when sending over the internet, in this configuration as the local network is secured and the server is also locally it is not necessary but would improve security.
:::
In addition this ensures I have full control over the privacy and data aspects of the data I collect, there can be some privacy concerns with using a public MQTT server as anyone can read the data, this becomes a bigger issue when you have control aspects. A malicious actor could have toggled the power of my device a thousand several times which the power plug may have protection against but not all of them. This also ensures the smartplug is isolated only on my local network and isn't exposed from outside. If I want to access the services from outside a VPN such as Wireguard that can be hosted on the Raspberry Pi, this also ensures one single point of entry.
The reason I picked this platform is contained in this section. After comparing features from other platforms these two gave me the most customization. Node-RED is very powerful in terms of what you can do, everything is paired together with building blocks.
For the InfluxDB and Grafana they are enterprise applications and are powerful in terms of customization, the open-source nature of Grafana allows it to have many plugins and custom dashboards that can be downloaded and utilized.
In addition some alerts are sent on Discord, but this is leveraged by Node-RED.
The entire platform runs locally on a server, the start of the section includes the Docker files necessary to launch your own stack.
## The code
:::info
The code can be found under [Github Repository](https://github.com/alija3000/environmental_iot).
:::
### Sensors
#### DHT22
Library: Uses the built-in ESP32 DHT library.

>Fig. 17. DHT22 Temperature Sensor. Found in [Material](##Material) section.
#### Setup
```python=
dht22 = dht.DHT22(machine.Pin(22))
```
In this case only the pin has to be picked when wanting to access it from code.
#### Usage
After the setup the DHT22 Sensor has three functions, one to measure and the other two to retrieve the values.
```python=
# Measurement
dht22.measure()
# Temperature value
temperature = dht22.temperature()
# Humidity value
humidity = dht22.humidity()
```
This code is every minute. In addition the MQTT stats are sent after the temperature is queried.
#### Display(ST7735)

>Fig. 18. AZDelivery SPI TFT-screen. Found in [Material](##Material) section.
##### Setup
:::info
The display is not a commonly used one hence finding libraries was a hard task
:::
Library: [link](https://github.com/boochow/MicroPython-ST7735)
I have included my code but if you were to add this library into your device you would transfer it over into the main directory.
In addition fonts were were taken from [link](https://github.com/GuyCarver/MicroPython/tree/master/lib).
The display is setup once at startup.
```python=
# LCD init stuff
def init_lcd():
print("Init LCD; wait.")
spi = SPI(2, baudrate=20000000, polarity=0, phase=0, sck=Pin(18), mosi=Pin(23))
tft=TFT(spi,2,4,5)
tft.initr()
tft.rgb(True)
# Fill display with black
tft.fill(TFT.BLACK)
print("Init LCD done")
return tft
tft = init_lcd()
```
##### Usage
After the display is started the tft class can be used.
The color shown on the display depends what threshold the temperature is,
these same values are used at Grafana.
```python=
# Used to change temperature color on display
def get_color(self):
color = TFT.GREEN
if(self.temperature >= 28.0):
color = TFT.RED
elif(self.temperature >= 27.0):
color = TFT.YELLOW
return color
```
In addition the LCD writing logic consists of running a function that populates the LCD,
```python=
def draw_current_status(self):
tft.fill(TFT.BLACK);
v = 0
tft.text((0, v), "Envbox", TFT.RED, seriffont, 2, nowrap=True)
v += sysfont["Height"] * 3
tft.text((0, v), f"Fan Status", TFT.YELLOW, sysfont, 2, nowrap=False)
v += sysfont["Height"] * 2
tft.text((0, v), f"{'On' if self.fan else 'Off'}", TFT.RED if self.fan else TFT.GREEN, terminalfont, 2, nowrap=False)
v += sysfont["Height"] * 3
tft.text((0, v), f"Temperature", TFT.GREEN, sysfont, 2, nowrap=False)
v += sysfont["Height"] * 3
tft.text((0, v), f"{self.temperature}c", self.get_color(), terminalfont, 2, nowrap=False)
v += sysfont["Height"] * 3
tft.text((0, v), f"Humidity", TFT.BLUE, sysfont, 2, nowrap=False)
v += sysfont["Height"] * 3
tft.text((0, v), f"{self.humidity}%", TFT.BLUE, terminalfont, 2, nowrap=False)
self.counter = self.counter + 1
print("Drawing current values to screen.")
```
The fan status is gotten from MQTT and updated asynchronously in the class, this allows the LCD to be aware when the fan is turned on and off.
In this case a MQTT callback is used and a topic is subscribed to.
```python=
def sub_cb(self, topic, msg):
print((topic, msg))
try:
state = msg.decode("utf-8")
if(state == "0"):
if(self.fan == False):
print("Received same state (false), skipping update.")
return
self.fan = False
print(f"Received fan state, {self.fan}")
self.draw_current_status()
elif(state == "1"):
if(self.fan == True):
print("Received same state (true), skipping update.")
return
self.fan = True
print(f"Received fan state, {self.fan}")
self.draw_current_status()
else:
print(f"Invalid parsing, fan state skipping. {state}")
except Exception as e:
print("Invalid MQTT input, ignoring.", e)
```
The payload for this consists of 0 or 1 indicating if the fan is off or on.
The final result for how the LCD looks, as the fan status goes from red to green as well. The temperature also has color coded values.

>Fig. 19. LCD finished setup.
### Asynchronous
I use [uasyncio](https://docs.micropython.org/en/latest/library/uasyncio.html) which allows me to spawn tasks.
In this case running the LCD display and sending the sensor data are two different tasks.
Creating the program as a class allows me to save class variables which can be easily accessed by any function in the class. Allowing easier interoperability.
:::warning
Some code snippets in this example refer to the [Display ST7735](####Display(ST7735)), [DHT22](####DHT22), [Connection](###Connection) and sections.
:::
:::info
This code does not show entire code.
:::
```python=
# Main function in class
async def start_main_loop(self):
# Starts the measure_temp task which loops.
uasyncio.create_task(self.measure_temp())
# Draw LCD
await self.draw_current_status()
while True:
# Lightweight call check if wifi is off and reconnect if it's off
try_connect_wifi()
# Sleep 60 seconds asynchronously so it is not blocknig anything else
await uasyncio.sleep_ms(60_000)
# Draw the display again.
await self.draw_current_status()
# Start the application class and populate with initial values
application = Application(temperature, humidity)
# Start the asyncronous function which runs a loop
await application.start_main_loop()
```
### Configuration
In boot.py we read our config.json file
```python=
config = ujson.loads(open('config.json').read())
```
This allows us to configure all the settings required in one place.
:::info
For the config.json also linked in the repository you have to change the variables.
`WIFI NAME` replace with your WiFi SSID name
`WIFI PASSWORD` replace with your WiFi password
`MQTT USERNAME` replace with your Mosquitto docker password
`MQTT HOST` IP replace with your Mosquitto host IP, see platform section
:::
```json=
{
"SSID": "WIFI NAME",
"SSID_PASSWORD": "WIFI PASSWORD",
"user_mqtt": "MQTT USERNAME",
"pass_mqtt": "MQTT PASSWORD",
"broker_url": "MQTT HOST IP"
}
```
### Connection
:::warning
Bluetooth is disabled by default and no action is required.
:::
When the board boots up we configure WiFi.
boot.py
```python=
# Network set to station mode
def setup_network():
import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
return wlan
# Assign the network class to wlan
wlan = setup_network()
# Connecting to WiFi
def try_connect_wifi():
# If WiFi is connected do nothing
if not wlan.isconnected():
print('connecting to network...')
wlan.connect(config["SSID"], config["SSID_PASSWORD"])
while not wlan.isconnected():
pass
# Only print network info at first boot
if(FIRST_LAUNCH):
print('network config:', wlan.ifconfig())
# Connect to WiFi
try_connect_wifi()
```
While the application is running we check if WiFi is enabled after each iteration, this loops through each minute and checks if WiFi is on so the sensors can send data. This ensures we always have connectivity and if we lost WiFi due to router restart or similar it would wait and reconnect. In addition some parts have watchdog support allowing them to monitor and automatically restart if something hangs, in this case we handle it in software.
:::danger
The code block does not contain the full logic, WiFi is only showcased
:::
main.py
```python=
while True:
# Delay
# Calling wifi function
try_connect_wifi()
#..... code
```
### Recovery handling
In addition to WiFi being checked periodicially we use MQTT which has to be handled to make sure data is received as at any point it could fail.
:::danger
This code does not show entire logic but parts necessary for the failure to function.
:::
```python=
class Application():
def __init__(self, temperature, humidity):
self.failure = 0
async def measure_temp(self):
while True:
#.....
# Failure starts at 0 if no exceptions
self.failure = 0
# Send to MQTT.
try:
# ......
except Exception as e:
print("Failed to send to MQTT", e)
# Increase failure
self.failure += 1
# If failure is 3 reset the machine
if(self.failure > 2):
machine.reset()
```
The logic shown above makes sure that if we receive a MQTT failure more than 2 times we lost at most 2 measurements as we take a measurement every minute. This is not a big issue but makes sure our application is robust.
### MQTT
:::danger
This code does not show entire logic but parts necessary to understand MQTT.
:::
MQTT is used to receive fan status and also send temperature and humidity data.
```python=
topic_pub = 'devices/envbox/'
# Send data to MQTT topic (devices/envbox)
self.mqtt.publish(topic_pub, f"{self.temperature},{self.humidity}")
```
In addition MQTT is subscribed to fan updates
```python=
# Subscribes to the fan topic
self.mqtt.subscribe(topic_sub)
# Used to set a function that is ran when a subscribed topic has new messages.
self.mqtt.set_callback(self.sub_cb)
```
The callback function in this case handles the fan and is clarified under [Display ST7735](####Display(ST7735)) section.
## Transmitting the data / connectivity
The data is submitted to a local MQTT server with authentication.
The data is packaged into a simple structure for simpler processing and then handled in Node-RED and re-sent to all users of the data,
When the data arrives if the temperature is 28.5 and humidity is 40.5 then the data the board sends will be
`28.5,40.5` which is handled at Node-RED to turn it into a JSON structure which can be ingested into InfluxDB.
From the board the code looks like this.
```python=
self.mqtt.publish(topic_pub, f"{self.temperature},{self.humidity}")
```
This ensures the ESP-32 takes less power and the brunt of the processing goes to Node-RED.
String manipulation does not take much resources but when dealing with a low-power device any power you can save can help save power. In our case we do not use a battery so those power savings do not make much difference for this specific use-case.
As our device is meant to be in a room running uninterrupted and showing user current stats on a LCD it will be connected to power.
WiFi is used as every home nowadays has WiFi and especially 2.4 GHz even though some routers do not have it enabled as default, it can be enabled. If running from battery LoRa would have been the better option. But as this device will sit in a single spot and have availability to a power spot WiFi is the better option.
There was also other options such as LTE, but when building a low-cost device this adds additional cost, the project could be repurposed to be used outside, in this case removing the LCD and using LoRa or LTE would have given better power savings.
The temperature and humidity data is polled every minute and sent in time of measurement. This is to make sure we can get more accurate long-time readings and catch short-term changes.
:::info
The DHT11 can be called no more than once per second and the DHT22 once every two seconds for most accurate results. Sensor accuracy will degrade over time. Each sensor supports a different operating range. Refer to the product datasheets for specifics. [Source](https://docs.micropython.org/en/latest/esp8266/tutorial/dht.html)
:::
The sleep functionality would not have allowed us to operate the LCD at real-time accuracy, as the state of the fan can be updated at any point of time by third-party apps or by user just pushin the smart plug button. Other solutions consists of using a motion sensor to send an interrupt to start the LCD; this would be unreliable if you are passing by or looking at the LCD from some distance.
The data is also sent asynchronously which means other code can run on the device as long as it is not blocking. To read more about asynchronous as Python is not entirely asyncronous due to GIL refer to [documentation](https://docs.python.org/3/library/asyncio.html).
Another approach other than polling a fixed amount would have been to poll very slowly and on changes start increasing the polling linearly.
WiFi is being used for mostly two reasons, there is no LoRa gateways in my location and WiFi is available at any home mostly where LoRa may not be.
For transporting data MQTT is used. MQTT is a very light-weight protocol has a very simple publish/subscribe system. For my project we only needed to publish hence we did not subscribe to any topics.
A topic can be thought of data changes you wanna listen in on. This would have been useful if we leveraged node-red to control a fan, then that could have sent a command to enable the fan that MQTT would have forwarded to our ESP-32 board.
For sending alerts to Discord webhooks are used from Node-RED, this ensures high compatibility and saves time and developer hours having to code for Discord's API as webhooks are mostly universal.
The entire flow consists MQTT sending data to Node-RED.
Where Node-RED sends the data to InfluxDB and handles the data for the web dashboard and automation purposes.
Grafana queries InfluxDB for data.
Node-RED alerts are handled with webhooks and JSON, which makes Node-RED very powerful to customize
```javascript=
var date = new Date();
msg.payload = {"embeds": [
{
"type": "rich",
"title": `IoT monitoring system`,
"description": "No response for 5 minutes.",
"color": 0xFF0000,
"timestamp": date.toISOString()
}
]
}
msg.headers = {'Content-Type': ['application/json']}
return msg;
```
As seen in the code block Discord allows webhooks to be very customizable.
## Presenting the data
Data is preserved without no retention, as it's just two values temperature and humidity the storage space used is minimal even over a long period of time. InfluxDB supports setting [retention](https://docs.influxdata.com/influxdb/v1.7/query_language/database_management/#create-retention-policies-with-create-retention-policy) and can be configured if needed, in addition previous data can be deleted following this [guide](https://docs.influxdata.com/influxdb/cloud/write-data/delete-data/).
The dashboard to see historical data, and see informatical status of the fan is built using Grafana.

>Fig. 21. Grafana dashboard.
Current temperature and humidity is shown and also the temperature over a period of time, by default 7 days. With a single click on Grafana you can pull up any range or make your own range. For visualization purposes only one graph per stat is shown, as they can be filtered by time easily it gave more space to show other statistics when done in this manner.

>Fig. 21. Grafana time-range options.
The dashboard also shows how long the fan has been running with the help of the smart plug and the stats are logged using Node-RED. Fan Online changes from Fan Offline depending if the fan is active or not.
:::info
The Grafana dashboard and the Node-RED web editor is secured and requires login, both dashboards can be accessed from the phone. The Node-RED fan control dashboard is also secured.
:::
Grafana Dashboard link: [Download Link](https://github.com/alija3000/environmental_iot/blob/main/grafana-dashboard.json), to import follow the [import guide](https://grafana.com/docs/grafana/latest/dashboards/export-import/).
While Grafana has a mobile support it lacks a bit in the controlling the fan aspect, hence Node-RED is also used.
Node-RED is used to ingest the data from MQTT and in this case dependancy on Telegraf can be removed. Which is another stat gathering software.
Node-RED is used to handle the logic of ingesting the data and also controlling the fan (smart plug) in this case. Node-RED has a plugin to enable a web dashboard,

>Fig. 22. Node-RED web dashboard.
:::info
As the Node-RED dashboard is more tricky to export then Grafana dashboard contact me on Discord if you want some guidance,
:::spoiler
keyboardwarrior#3234
:::
The web dashboard can be used to enable auto mode, which turns fan on and off at defined temperature ranges.
The Node-RED flow consists of getting the data through MQTT by the ESP-32 board, then adding it into InfluxDB to be showcased in Grafana. In addition the web dashboard is notified of the new values.

>Fig. 23. Node-RED part of flow.
InfluxDB was picked as it is a time-series database and has support by various other software making integration easily. In addition it is backed by enterprise companies, and a time-series database is best for this data where relational database is more meant for structured data.
Being able to filter data using time as a query primarily makes organizing the graphs smoother.
Another very powerful time-series database I glanced at was Prometheus which also would have worked, there is not much difference for my use-case between those two. Grafana supportsd both InfluxDB and Prometheus natively.
Node-RED plugin for InfluxDB is more supported than the Prometheus variant, making InfluxDB the better choice in my case.
There is several automations at play, the first automation is when MQTT receives the data from ESP-32 and Node-RED is notified that a new MQTT message has been received.
In this case data is stored in InfluxDB and Node-RED checks if the temperature ranges are over the allowed range, this is controlled by the web dashboard.
The watchdog checks if there has been no response for 5 minutes and if it is longer it sends an alert on Discord through a webhook.

>Fig. 24. Node-RED webhook.

>Fig. 25. Node-RED watchdog flow.
If the values for temperature are set higher or lower than the configured values then the smart plug will be turned off with the Node-RED kasa plugin, in addition the smart plug is configured in such a way that new events are received from the TP-Link Kasa API, this ensures that we do not get an outdated state if power is on or off eliminating those bugs, as it's not dependant on a value in Node-RED.
In addition figure 19 shows the fan status being updated on the board, this is triggered by a MQTT publish by Node-RED when fan status changes. So there is bi-directional communication between ESP-32 and MQTT which Node-RED interfaces with.

>Fig. 26. Node-RED Kasa flow.
There is also discord alerts for when the temperature hits the max allowed range. In this case the user can know the temperature has been raised and temperature alerts can be taken action on unless auto fan is enabled which will handle it automatically.

>Fig. 26. Discord temperature alert
Node-RED allows changing these values easily, as seen in the figure 27 it uses the web dashboard values, making it fully automatic.

>Fig. 27. Node-RED switch temperature
When fan is toggled on and off there is a notification posted to Discord, this is done with Node-RED.

>Fig. 27.1. Node-RED Discord fan status flow

>Fig. 27.2. Discord notification for fan toggle
As with webhooks we can do any alert we so wish.
## Finalizing the design
### Final Product
The final product
The external adapter to power the ESP-32 board, any would work or connected to computer directly.

>Fig. 28. Generic adapter
The smart plug responsible for controlling the fan.

>Fig. 29. Smart plug
The finished state with the ESP-32 running and outputting data.

>Fig. 30. Finished temperature box
### Thoughts
The project ended up very nicely, in addition the project can easily be adapted to change the fan into an heating element or similar without further changes to code or
platforms.
In addition the flow on node-red is heavily robust and will withstand any changes and won't repeat with the smart plug.
I am quite impressed with the overall state of the project, if I did this any differently I would have tried make the design more compact, as this is just a prototype it did not matter as much.
### Improvements
LCD is always active now, in a previous iteration I had a motion sensor that would work to detect the LCD but due to power constraints I decided to remove it, even if I had used the motion sensor the LCD was connected straight to the 3v both for VCC and LCD backlight which resulted in issues controlling the backlight. As this board specifications were not properly documented I did not want to try plug it in a pin that has a max current draw of 15 mAh.
If I did this any different as well I would have decided to cache temperature on the device if the MQTT server was unreachable, but considering it relies on the MQTT server to set automatic temperature range the functionality of the device would have been void, hence better option would be to load-balance MQTT ensuring it does not go down so data can be persisted there.
### Past revisions
The project went through a lot of revisions, the original purchase of the components were to make a notification for upcoming TV episodes that will air, as that idea did not prove feasible due to third-party API support being limited and not enough time to focus for webscraping the site it was dropped.
Webscraping is a form of gathering data directly from the website itself without using a API which is a more programmatical manner of retrieving the data, web scraping is what you see is what you get.
Hence components that were omitted from the final revision consisted of motion sensor, RGB led, button which were no longer needed.
There was a DHT11 temperature sensor I experimented it but there was a mistake on my part that melted the sensor, hence I acquired the more accurate DHT22 sensor.

>Fig. 3. DHT11 melted.