This repository contains code for Airy hobby project. The goal of the project is to collect air quality measurements in the appartment and display those on a dashboard in a web app.
For simplicity the code is written only to meet the specific requirements of this project. However with minimal changes it should be possible to extend the data model and API to store and display other kinds of measurements.
Backend is written in Golang and consists of 2 applications:
serverprovides an HTTP API to query measurements, also renders graphs to view in the browserprocessorconnects to MQTT broker, subscribes to measurements sent by IoT and persists those to a postgres database.
- Arduino Nano 33 IoT with BME680 air sensor. It collects air quality, temperature, humidity and other enviromental data and sends it to an MQTT broker.
Copy sample env file and provide correct values.
cp .env.sample .env
To use default credentials do:
cp __binds/mosquitto/config/password_file.sample __binds/mosquitto/config/password_fileThat would allow connecting using default credentials, which can be found in .env.sample file.
See mosquitto_passwd docs for more information.
Run dependencies in Docker and apps on the host:
make docker-dev
make server
make processorOr run everything in Docker
make docker-prodAt this point we should be able to see an empty graph at http://localhost:8081/api/graphs?resolution=60
We can publush a test measurement with make test-publisher. Wait a minute and publush another measurement. If all worked well we should be able to see the measurement on the graph with a minute resolution. Ensure correct BROKER_ADDRESS in .env file for command to work.
The app was designed to be deployed on a Digital Ocean VM which has Docker, Certbot and Nginx installed. The instructions below provide the steps I used in my case, but there are probably different ways to do it.
Certificates are configured using certbot:
sudo certbot --nginx -d amiselaytes.com -d airy.amiselaytes.com
This command generates autrenewabale certificates stored in /etc/letsencrypt/live/amiselaytes.com folder.
See also those docs
SSL connection is terminated in NGINX, then traffic from NGINX to MQTT in docker container is not encrypted.
The following nginx config is used:
stream {
upstream mosquitto {
server 127.0.0.1:1883;
}
server {
listen 8883 ssl;
ssl_certificate /etc/letsencrypt/live/amiselaytes.com/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/amiselaytes.com/privkey.pem;
proxy_pass mosquitto;
}
}
Message format for the measurements is white space separate string in the following order
SensorID IAQ CO2 VOC Pressure Temperature Humidity.
For example bedroom 51.86 607.44 0.52 100853 27.25 60.22 would create a measurement for
sensorId=bedroom, IAQ=51.86, CO2=607.44, VOC=0.52, Pressure=100853, Temperature=27.25, and Humidity=60.22.
Messages should be sent to the broker address and /measurement route.
GET /api/graphs
Query parameters
viewoptional, default today, can be one ofday,weekdateoptional, default to today, in the yyyy-mm-dd format, such as 2024-01-01resolutionmust be in ms, for example 86400 for a day, 3600 for an hour
GET /api/measurements?resolution=86400&to=1702156335&from=1701810734
frommust be a unix timestamp in mstomust be a unix timestamp in msresolutionmust be in ms, for example 86400 for a day, 3600 for an hour
[
{
"timestamp": 1701734400,
"sensorId": "bedroom",
"iaq": 108.49368098159503,
"co2": 949.001042944785,
"voc": 1.4850920245398769,
"pressure": 101128.12865030681,
"temperature": 18.051226993865033,
"humidity": 44.831656441717776
},
{
"timestamp": 1701734400,
"sensorId": "livingroom",
"iaq": 98.50804878048783,
"co2": 940.1318902439023,
"voc": 1.377317073170732,
"pressure": 101159.84170731703,
"temperature": 20.659268292682928,
"humidity": 42.94823170731707
}
]POST /api/events
{
"startTimestamp": 1698090929,
"eventType": "window:open",
"locationId": "bedroom"
}startTimestamprequired, must be unix timestamps in ms.eventTyperequired, must be a string, can be anythingeventTyperequired, must be a string, one ofbedroom,livingroom
curl -X POST -H "Content-Type: application/json" -d '{"startTimestamp": 1698090929, "eventType": "window:open", "locationId": "bedroom"}' http://localhost:8081/api/eventsGET /api/events?from=1698090929&to=1698090930
frommust be unix timestamps in ms.tomust be unix timestamps in ms.tomust be greater thanfrom
[{
"id": "uuid",
"startTimestamp": 1698090929,
"endTimestamp": 1698090929,
"eventType": "window:open",
"locationId": "bedroom"
}]PATCH /api/events/:eventId
{"endTimestamp": 1698090929}curl -X PATCH -H "Content-Type: application/json" -d '{"endTimestamp": 1698090929}' http://localhost:8081/api/events
