Simplepush Blog

Monitor CO2 with your Raspberry Pi

Raspberry Pi Zero W

This article explains how to connect a Raspberry Pi with a SCD30 sensor module and send push notification to your phone when the CO2 level rises above a certain threshold. While the SCD30 sensor module is not the cheapest, it is quite accurate and easy to use. In addition to the CO2 level, the SCD30 can measure temperature, humidity and works within a wide voltage range (between 3.3V and 5.5V).

In this article we will focus on the very basics of the SCD30. Check out this in-depth article about the SCD30, if you want to know more about what the SCD30 is capable of.

What you will need

For this article we will use a Raspberry Pi Zero W but any Raspberry Pi can be used. If you choose another Raspberry Pi, make sure to buy the correct power supply for it.

If you don’t want to permanently solder everything together, consider to also buy a pin header for your Pi and the SCD30 module.

Connect Raspberry Pi and SCD30

Raspberry Pi Zero W with SCD30 module

Let’s start by wiring the Raspberry Pi to the SCD30.

You need to connect the following pinouts:

  • Raspberry Pi: SDA1 (I2C) ⟷ SCD30: RX/SDA
  • Raspberry Pi: SCL1 (I2C) ⟷ SCD30: TX/SCL
  • Raspberry Pi: 3.3V/5V ⟷ SCD30: VIN
  • Raspberry Pi: GND ⟷ SCD30: GND

Click here to see the pin numbering for the Raspberry Pi Zero W and for other models.

Install requirements

Download Simplepush

Simplepush is an app for iOS and Android which lets you send push notifications to your phone from your Raspberry Pi. Simplepush also lets you send encrypted push notifications.

Download the app from Google Play or the App Store.

When you open the app for the first time, you will immediately get a six-digit key that identifies your phone with the Simplepush API. We will later need this key to send out an alert when the CO2 level rises above a certain threshold. Sending a notification with Simplepush is as simple as making an HTTP request.

Enable the I2C interface

I2C is a commonly used standard which gives modules, like the SCD30, the ability to talk to other devices that support the I2C standard.

In order to enable I2C on your Raspberry Pi enter the following command on your Pi:

sudo raspi-config

This will open the raspi-config utility where you need to select “Interface Options”.

Raspberry Pi Software Configuration Tool

Then select the I2C option and select “Yes” once you get asked if the I2C interface should be enabled.

Raspberry Pi Software Configuration Tool

Read out SCD30 data

First we will need to install Python 3.

sudo apt install python3

Then clone the following repository and follow the steps in its README.md. Since you already enabled I2C and wired everything together, you can ignore the first two steps and continue with installing required packages.

git clone https://github.com/tymm/Raspi-Driver-SCD30.git

Once you finished all the steps and installed the SCD30 service on your Pi, you should be able to read out the sensor data from /run/sensors/scd30/last.

cat /run/sensors/scd30/last
gas_ppm{sensor="SCD30",gas="CO2"} 1215.47241211
temperature_degC{sensor="SCD30"} 20.16136169
humidity_rel_percent{sensor="SCD30"} 51.53808594

Calibrate the CO2 sensor

CO2 sensors have to be calibrated in order to give accurate measurements. The CO2 sensor of the SCD30 can work in automatic self-calibration (ASC) or forced re-calibration (FRC) mode.

In ASC mode the lowest measurements within a seven day period get assigned to 400ppm. Therefore for ASC mode to work well, the SCD30 should be exposed to fresh air regularly. The sensor also has to run without being turned off for the whole period to finish the self-calibration successfully.

For proper function of ASC field-calibration algorithm SCD30 has to be exposed to air with CO2 concentration 400 ppm regularly
SCD30 Datasheet

If your sensor can’t be exposed to fresh air regularly, you need to manually calibrate the sensor in FRC mode. Since ASC mode is the default for the SCD30 and therefore the easiest to use, we will ignore FRC mode for now.

Send an alert

Now all that is left is creating the script that sends out a push notification once the CO2 level is above a certain threshold.

#!/usr/bin/env python3

from urllib import request, parse
import os
from time import time

# The Simplepush key where alerts should be sent to
SIMPLEPUSH_KEY = "YourSimplepushKey"
# Alert threshold in ppm
THRESHOLD = 1300
# Minimum time in minutes that needs to pass between two alerts
TIME_BUFFER_MINUTES = 30
TMP_FILE = "/tmp/co2-level-too-high.lock"

f = open("/run/sensors/scd30/last", "r")
data = f.read()
f.close()

co2 = int(float(data.split('\n')[0].split(' ')[1]))

if co2 > THRESHOLD and not os.path.exists(TMP_FILE):
    notification = parse.urlencode({'key': SIMPLEPUSH_KEY, 'title': 'CO2 level too high', 'msg': str(co2) + 'ppm', 'event': 'co2'}).encode()
    req = request.Request("https://api.simplepush.io/send", data=notification)
    request.urlopen(req)
    with open(TMP_FILE, "a") as f:
        f.write(str(time()))
elif co2 <= THRESHOLD and os.path.exists(TMP_FILE):
    with open(TMP_FILE, "r") as f:
        last_alarm_timestamp = float(f.read())
        if last_alarm_timestamp + TIME_BUFFER_MINUTES * 60 < time():
            os.remove(TMP_FILE)

Save the script to /usr/local/bin/co2-alert.py and make it executable. Replace YourSimplepushKey with the key that you got when installing the Simplepush app on your phone.

wget https://gist.githubusercontent.com/tymm/d07547b84c29656db63596066ccf142f/raw/ee2d53bf263c31778837af2e48e1a9f9d7fee49c/co2-alert.py
mv co2-alert.py /usr/local/bin/
chmod +x /usr/local/bin/co2-alert.py

Create a cronjob for the CO2 alert script

Add a cronjob that runs every five minutes:

crontab -e

Append the following to the end of your cronjob file:

*/5 * * * * /usr/local/bin/co2-alert.py

After saving the cronjob file, the script runs every five minutes and sends out a notification when the CO2 level is above the defined threshold.

Full disk alert to your Android or iOS phone with Simplepush

Sometimes proper server monitoring is just not worth the effort. However it can still be useful to know when a disk on your server starts to become full.

The following Bash script is perfect for this. All you need is the Simplepush app installed on your Android or iOS device in order to receive the alerts. Since full disk alerts shouldn’t happen too often, the free version of the Simplepush app should be good enough.

Replace YourKey with your Simplepush key that you receive immediately after installing the Simplepush app. You can also change the threshold to adjust the percentage of space that needs to be used up before an alert is triggered.

#!/bin/bash

SIMPLEPUSH_KEY=YourKey
THRESHOLD=90

df -H | grep -vE '^Filesystem|tmpfs|cdrom' | awk '{ print $5 " " $1 }' | while read line;
do
  percentage=$(echo $line | awk '{ print $1}' | cut -d'%' -f1  )
  disk=$(echo $line | awk '{ print $2 }' )
  if [ $percentage -ge $THRESHOLD ]; then
    curl --silent --output /dev/null --data "key=$SIMPLEPUSH_KEY&title=$disk&msg=Disk usage at $percentage%!" https://api.simplepush.io/send
  fi
done

You can also find the bash script here: https://gist.github.com/simplepush/af1b9c87e9203664fbd40b7bb300175a.

You can make this script run once a day by creating a cronjob. Type crontab -e and then append the following line where you adjust the path to the disk monitoring script:

@daily /path/to/disk-monitor.sh

Now you will get a notification once a day for any disk that is at least 90% full.

Web scraping dynamic content created by Javascript with Python

Scraping websites which contain dynamic content created by Javascript sounds easier than it is. This is partly because browser technology is constantly evolving which forces web scraping libraries to change with them. Therefore many articles written about the topic reference deprecated libraries like PhantomJS and dryscrape which makes it difficult to find information that is up-to-date.

In this article we will show you how to scrape dynamic content with Python and Selenium in headless mode. Selenium is a web scraping library similar to BeautifulSoup with the difference that it can handle website content that was loaded from a Javascript script.

To make things more exciting we will do so by providing an example that has a real life use case. Namely sending a notification to your Android or iOS device when certain TeamSpeak users enter or leave a given TeamSpeak server.

First make sure to install Selenium and the Simplepush library.

sudo pip3 install selenium
sudo pip3 install simplepush

Then we need to make sure to have the ChromeDriver installed.

On Ubuntu or Raspbian:

sudo apt install chromium-chromedriver

On Debian:

sudo apt install chromium-driver

On MacOS:

brew cask install chromedriver

Now we can start coding.

#!/usr/bin/python3
from multiprocessing import Process, Manager
from requests.exceptions import ConnectionError
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from simplepush import send
import time

# Get this URL from the tsviewer.com search
TSVIEWER_URL = "https://www.tsviewer.com/index.php?page=ts_viewer&ID=1111111"
# If you squint, you can derive the TSVIEWER_ID from TSVIEWER_URL
TSVIEWER_ID = "ts3viewer_1111111"
# You will immediately get your personal Simplepush key after installing the Simplepush app
SIMPLEPUSH_KEY = "YourSimplepushKey"
# The usernames of your friends you want to be notified about
FRIENDS = ["TeamSpeakUser"]

def update(friends_online):
    driver = webdriver.Chrome(options=options)

    try:
        driver.get(TSVIEWER_URL)
        # Wait until Javascript loaded a div where the id is TSVIEWER_ID
        WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, TSVIEWER_ID)))
        html = driver.page_source

        # This check unfortunately seems to be necessary since sometimes WebDriverWait doesn't do its job
        if TSVIEWER_ID in html:
            for friend in FRIENDS:
                send_notification_on_change(friend, f"{friend} entered the server", f"{friend} left the server", html, friends_online)
    except:
        print("Error")
    finally:
        driver.close()
        driver.quit()

def send_notification_on_change(name, message_join, message_leave, html, friends_online):
    name = name.lower()
    html = html.lower()

    if name in html and name not in friends_online:
        try:
            friends_online.append(name)
            send(SIMPLEPUSH_KEY, "A friend joined", message_join)
        except ConnectionError:
            friends_online.remove(name)

    if name in friends_online and name not in html:
        try:
            friends_online.remove(name)
            send(SIMPLEPUSH_KEY, "A friend left",  message_leave)
        except ConnectionError:
            friends_online.append(name)

if __name__ == "__main__":
    options = webdriver.ChromeOptions()
    options.set_headless = True
    options.add_argument('headless')

    manager = Manager()
    friends_online = manager.list()

    try:
        while(1):
            p = Process(target=update, args=[friends_online])
            p.start()
            # make the main process wait for `update` to end
            p.join()
            # all memory used by the subprocess will be freed to the OS
            time.sleep(5)
    except (KeyboardInterrupt, SystemExit):
        print("Stopped")

Did you notice how we use the multiprocessing library to start Selenium in its own process? This is because otherwise our program could run out of memory since Python has difficulties collecting unused WebDriver instances. By running them inside their own processes we make sure that all memory is released back to the OS once a process finishes.

Now if you run our little program, it will check tsviewer.com every five seconds to see if one of our friends joined or left the server (as defined by TSVIEWER_URL and TSVIEWER_ID). If that was the case, it will send out a notification to the Simplepush key defined by SIMPLEPUSH_KEY.

Support for critical alerts on iOS

The new version of our iPhone/iPad app adds support for Apple’s Critical Alerts.

Critical Alerts can bypass Do not Disturb mode and your mute switch. If you want to use Critical Alerts on your iOS device you can edit or create an event and activate Critical Alerts there. Also make sure to give Simplepush your permission for Critical Alerts if you want to use them.

Events screen with Critical Alerts turned on

Sending (end-to-end encrypted) push notifications from your Raspberry Pi Zero W

The Raspberry Pi Zero W is an impressively small device with a single core 1GHz CPU and 512MB of RAM. You can do all kind of great stuff with it. For me the most interesting use cases happen in a headless setup where you do not connect your Raspberry to any peripherals. See this great explanation on how to set up your Raspberry Pi Zero W without any monitor or keyboard.

When running your Raspberry Pi headlessly, you probably find yourself in the position where you want to get information from your Pi to yourself. This is where Simplepush comes into play.

With Simplepush a few lines of bash will make it possible to send push notifications to your Android device. The following example will send the amount of available memory on your Pi as a push notification to the user with the Simplepush key HuxgBB (replace this key with your own. You get your key by installing the Simplepush app - no registration required).

msg=$(cat /proc/meminfo | grep "MemAvailable:") && curl "https://api.simplepush.io/send/HuxgBB/Available memory on your Pi/$msg" &> /dev/null

With Simplepush this also works with end-to-end encryption:

sudo apt-get install git
git clone https://github.com/simplepush/send-encrypted.git
sudo cp send-encrypted/simplepush.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/simplepush.sh
msg=$(cat /proc/meminfo | grep "MemAvailable:") && simplepush.sh -k HuxgBB -t "Available memory on your Pi" -m "$msg" -p yourpassword -s yoursalt

You can set your password in the encryption section of the app. This is also where you find your salt.

We also provide libraries for sending push notifications (normal and end-to-end encrypted) from within programming languages.

Imprint