Displaying Aranet4 CO2 readings on my website
January 7, 2024
2 min read
January 7, 2024
2 min read
Co2 monitor and it's a delightful little gadget. It's small and simple and after inserting the 2 AA batteries I could immediately see the CO2 levels on a clear little screen. I've been told it's also one of the most accurate monitors on the market. Using an unofficial Python library and my Raspberry Pi I was also able to show the measurements on my website. Follow along if you're interested in doing something similar yourself.
The setup I'm using involves the following:
Install the following packages on your Raspberry Pi:
sudo apt install bluetooth pi-bluetooth bluez blueman
Then use bluetoothctl to pair with your Aranet4:
sudo bluetoothctl
> scan on
> pair $MAC
> scan off
I adapted the Python script I found on this blog to get the current measurements of my Aranet4 monitor. I’m storing the data in a PostgreSQL database (hosted on Supabase) so that I can easily access it on my website. First, install the Python libraries aranet4 and psycopg2:
pip install aranet4 psycopg2
Then I use the following Python script to store the latest measurement in a PostgreSQL database:
import configparser
import time
import aranet4
import psycopg2
import os
from datetime import datetime
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
config = configparser.ConfigParser()
config.read(os.path.join(BASE_DIR, 'config.ini'))
NUM_RETRIES = int(config['SETTINGS']['num_retries'])
DEVICES = dict(config['DEVICES'])
DATABASE = {
'database': config['DATABASE']['database'],
'host': config['DATABASE']['host'],
'user': config['DATABASE']['user'],
'password': config['DATABASE']['password'],
'port': config['DATABASE']['port']
}
def create_table(cursor):
cursor.execute('''CREATE TABLE IF NOT EXISTS readings(
device TEXT,
timestamp INTEGER,
temperature REAL,
humidity INTEGER,
pressure REAL,
CO2 INTEGER,
PRIMARY KEY(device, timestamp)
)''')
def get_readings(mac):
for attempt in range(NUM_RETRIES):
try:
return aranet4.client.get_current_readings(mac)
except Exception as e:
print('Attempt', attempt, 'failed, retrying:', e)
return None
def insert_readings(cursor, name, readings):
data = (
name,
time.mktime(datetime.now().timetuple()),
readings.temperature,
readings.humidity,
readings.pressure,
readings.co2
)
print('Inserting', data)
cursor.execute(
'INSERT INTO readings VALUES(%s, %s, %s, %s, %s, %s) ON CONFLICT DO NOTHING', data)
def main():
with psycopg2.connect(**DATABASE) as conn:
cursor = conn.cursor()
create_table(cursor)
conn.commit()
for name, mac in DEVICES.items():
readings = get_readings(mac)
if readings is not None:
insert_readings(cursor, name, readings)
conn.commit()
if __name__ == "__main__":
main()
The database credentials and configuration for the Aranet4 MAC addresses can be found in the config.ini file. Here's an example:
[DATABASE]
database = postgres
host = db.<your-id-here.supabase.co
user = postgres
password = <your db password here>
port = 5432
[DEVICES]
office = <your-aranet4-address-here>
[SETTINGS]
num_retries = 2
The script runs every minute in a cronjob to make sure the measurements are always up to date.
* * * * * /usr/bin/python3 /home/pi/Code/aranet4/load_data.py >> /home/pi/Code/aranet4/logs/load_data.txt 2>&1
0 9,21 * * * /usr/bin/python3 /home/pi/Code/aranet4/cleanup.py >> /home/pi/Code/aranet4/logs/cleanup.txt 2>&1
As you can see I've also included a cleanup script that deletes any entries older than a day. This is to make sure I don't run out of disk space on my free Supabase subscription.
I'll leave out the front-end code in this tutorial as it is quite straightforward. For those interested in how it works it's a combination of a React component and a Next.JS api route that calls the following service.
The live demo has been retired. The tutorial and example code remain available above.