Air Quality Application

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:

 

HARDWARE LIST
1 UNIHIKER
1 BME280 Environmental Sensor
1 ENS160 Air Quality Sensor
STEP 1
Connect the sensors with UNIHIKER

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.

STEP 2
Project structure

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:

 

STEP 3
Python code

Python Tkinter is used for the GUI. The following class is used for this (air_quality_display.py).

CODE
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!

CODE
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)
STEP 4
Upload the project to UNIHIKER

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 root@10.1.2.3:/root/

STEP 5
Running the application

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:

 

STEP 6
Annotation

My roommate's farts are disgusting and annoying, but not dangerous to me! If you enjoyed this tutorial, leave a like and/or comment.

License
All Rights
Reserved
licensBg
1