I was inspired to create this guide by one of my family members. I think each of you knows this problem too! It's about the fart, which slowly moves towards your own nose. You sit on the couch, clueless and peaceful, and a few seconds later you just want to run away. Since this happens very often with my abusers, I asked myself the question: How dangerous is this actually for me? Oh yes, ... that's him who makes my life difficult sometimes:
The I2C Gravity interfaces on the UNIHIKER make connecting the two sensors super easy! Simply connect the two sensors to the UNIHIKER (when switched off) to the two 4-pin ports. It doesn't matter which ports you use for which sensor!
When you are finished you can switch on the UNIHIKER.
Create a project folder named “AirQuality”. Inside of this directory create an other folder named “lib” and the Python file named “main.py”. In the “lib” folder, create the Python file called: “air_quality_display.py”. When you're finished the project should look like this:
Python Tkinter is used for the GUI. The following class is used for this (air_quality_display.py).
from tkinter import Tk, Canvas, BOTH
from math import cos, sin, radians
class AirQualityDisplay:
"""
Class for displaying air quality data as Python Tkinter canvas object
Requires values from recommended sensors like:
- ENS160 Air Quality Sensor
- BME280 Environmental Sensor
"""
_FONT_BIG: tuple = ("Arial", 12, "bold")
_FONT_SMALL: tuple = ("Arial", 10)
_FONT_COLOR: str = "black"
_ARC_SECTIONS: int = 5
_ARC_SECTION_COLORS: tuple = ("green", "lime", "yellow", "orange", "red")
_ARC_WIDTH: int = 25
_ARC_ANGLE: int = 180
def __init__(self, window: Tk, width: int, height: int, color: str, arc_start: list, arc_end: list):
"""
Tkinter Canvas display constructor
:param window: tkinter main window as object
:param width: width of current display as integer
:param height: height of current display as integer
:param color: background color for canvas object as string
:param arc_start: start position of arc section as list (x0, y0)
:param arc_end: end position of arc as list (x1, y1)
"""
self._root = window
self._screen_width = int(width)
self._screen_height = int(height)
self._bg_color = str(color)
self._x0, self._y0 = list(arc_start)
self._x1, self._y1 = list(arc_end)
self._center_x = (self._x0 + self._x1) / 2
self._center_y = (self._y0 + self._y1) / 2
self._radius = (self._y1 - self._y0) / 2 - self._ARC_WIDTH / 2
self._canvas = Canvas(self._root, bg=self._bg_color)
self._canvas.pack(fill=BOTH, expand=True)
self._draw_arc_scale()
def _draw_arc_scale(self) -> None:
"""
Draw canvas arc scale on tkinter canvas
:return: None
"""
start_angle = self._ARC_ANGLE
self._canvas.create_text(self._screen_width / 2, 20, font=self._FONT_BIG, text='Air Quality')
for i in range(self._ARC_SECTIONS):
color_index = i % len(self._ARC_SECTION_COLORS)
self._canvas.create_arc(self._x0, self._y0, self._x1, self._y1,
start=start_angle,
extent=-self._ARC_ANGLE / self._ARC_SECTIONS,
width=self._ARC_WIDTH,
outline="",
fill=self._ARC_SECTION_COLORS[color_index])
start_angle += -self._ARC_ANGLE / self._ARC_SECTIONS
self._canvas.create_oval(self._center_x - self._radius,
self._center_y - self._radius,
self._center_x + self._radius,
self._center_y + self._radius,
fill=self._bg_color,
outline="")
def _draw_arc_arrow(self, x: int, y: int, aqi: str) -> None:
"""
Draw arc arrow and quality information on tkinter canvas
:param x: x-coordinate of arc center as integer
:param y: y-coordinate of arc center as integer
:param aqi: air quality as string
:return: None
"""
self._canvas.create_line(self._center_x,
self._center_y,
int(x),
int(y),
width=2,
fill=self._FONT_COLOR,
arrow="last",
tags="pointer")
self._canvas.create_text(self._screen_width / 2,
165,
text=str(aqi),
fill=self._FONT_COLOR,
font=self._FONT_BIG,
tags="text")
def _draw_sensor_values(self, eco2: str, tvoc: str, temp: str, hum: str, press: str) -> None:
"""
Show sensor values and unit on tkinter canvas
:param eco2: eco2 value and unit as string
:param tvoc: tvoc value and unit as string
:param temp: temperature value and unit as string
:param hum: humidity value and unit as string
:param press: pressure value and unit as string
:return: None
"""
text_info = [
{"text": str(eco2), "y_pos": 200},
{"text": str(tvoc), "y_pos": 220},
{"text": str(temp), "y_pos": 240},
{"text": str(hum), "y_pos": 260},
{"text": str(press), "y_pos": 280}
]
for info in text_info:
self._canvas.create_text(25,
info["y_pos"],
text=info["text"],
fill=self._FONT_COLOR,
font=self._FONT_SMALL,
tags="text",
anchor="w")
def set_values(self, aqi: int, eco2: int, tvoc: int, temp: float, hum: float, press: float) -> None:
"""
Set all values from sensors to canvas object
:param aqi: aqi reference value from 1 to 5 as integer
:param eco2: eco2 concentration reference value as integer
:param tvoc: tvoc concentration reference value as integer
:param temp: temperature value in degrees Celsius as float
:param hum: humidity value in percent as float
:param press: pressure value in Pa as float
:return: None
"""
aqi_val = int(max(1, min(aqi, 5)))
eco2_val = int(eco2)
tvoc_val = int(tvoc)
temp_val = float(temp)
hum_val = float(hum)
press_val = float(press)
aqi_txt = {1: "Very Good", 2: "Good", 3: "Moderate", 4: "Poor", 5: "Very Poor"}.get(aqi_val, "")
eco2_txt = f'CO2 Concentration: {eco2_val} ppm'
tvoc_txt = f'TVOC Concentration: {tvoc_val} ppb'
temp_txt = f'Temperature: {temp_val} °C'
hum_txt = f'Humidity: {hum_val} %'
press_txt = f'Pressure: {press_val} Pa'
offset = (self._ARC_ANGLE / self._ARC_SECTIONS) / 2
angle = (self._ARC_ANGLE - (aqi_val - 1) * (self._ARC_ANGLE / self._ARC_SECTIONS)) - offset
x = self._center_x + self._radius * 0.9 * cos(radians(angle))
y = self._center_y - self._radius * 0.9 * sin(radians(angle))
self._canvas.delete("pointer")
self._canvas.delete("text")
self._draw_arc_arrow(x=x, y=y, aqi=aqi_txt)
self._draw_sensor_values(eco2=eco2_txt, tvoc=tvoc_txt, temp=temp_txt, hum=hum_txt, press=press_txt)
Here is the code for "main.py". Thanks to the existing PinPong libraries, you don't have to worry about the drivers for the two sensors!
from time import sleep
from tkinter import Tk
from pinpong.board import Board
from pinpong.libs.dfrobot_bme280 import BME280
from pinpong.libs.dfrobot_ens160 import Ens160
from lib.air_quality_display import AirQualityDisplay
SCREEN_WIDTH: int = 240
SCREEN_HEIGHT: int = 320
BG_COLOR: str = "white"
DELAY_SECONDS: int = 1
if __name__ == '__main__':
# init UNIHIKER pinpong
Board("UNIHIKER").begin()
# init environmental sensor
sensor_bme = BME280()
# init air quality sensor
sensor_ens = Ens160()
sensor_ens.set_pwr_mode(Ens160.ENS160_STANDARD_MODE)
sensor_ens.set_temp_hum(temp=sensor_bme.temp_c(), humi=sensor_bme.humidity())
sensor_status = int(sensor_ens.get_status())
if sensor_status is not 0:
print(f'Air quality sensor status: {sensor_status}')
# init tkinter UI object
root = Tk()
root.geometry(f"{SCREEN_WIDTH}x{SCREEN_HEIGHT}+0+0")
root.resizable(False, False)
window = AirQualityDisplay(window=root,
width=SCREEN_WIDTH,
height=SCREEN_HEIGHT,
color=BG_COLOR,
arc_start=[25, 40],
arc_end=[215, 230])
# get date from sensors and send to display canvas (continuously)
while True:
aqi = int(sensor_ens.get_aqi())
eco2 = int(sensor_ens.get_eco2())
tvoc = int(sensor_ens.get_tvoc())
temperature = float(sensor_bme.temp_c())
humidity = float(sensor_bme.humidity())
pressure = float(sensor_bme.press_pa())
window.set_values(aqi=aqi, eco2=eco2, tvoc=tvoc, temp=temperature, hum=humidity, press=pressure)
root.update()
sleep(DELAY_SECONDS)
I use SCP to upload the project folders and files to UNIHIKER, but you can use other preferred methods! Here you will find the documentation.
$ scp -r AirQuality [email protected]:/root/
Once you have loaded the project onto the UNIHIKER, you can start the applications and measure the quality of the air in your area. Here's an example:
My roommate's farts are disgusting and annoying, but not dangerous to me! If you enjoyed this tutorial, leave a like and/or comment.