mirror of
https://github.com/NotAShelf/air-quality-monitor.git
synced 2025-11-03 04:26:36 +00:00
init
This commit is contained in:
commit
9df325b73e
10 changed files with 268 additions and 0 deletions
28
src/AirQualityMonitor.py
Normal file
28
src/AirQualityMonitor.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
from sds011 import SDS011
|
||||
import redis
|
||||
|
||||
redis_client = redis.StrictRedis(host=os.environ.get('REDIS_HOST'), port=6379, db=0)
|
||||
|
||||
|
||||
class AirQualityMonitor():
|
||||
|
||||
def __init__(self):
|
||||
self.sds = SDS011(port='/dev/ttyUSB0')
|
||||
self.sds.set_working_period(rate=1)
|
||||
|
||||
def get_measurement(self):
|
||||
return {
|
||||
'time': int(time.time()),
|
||||
'measurement': self.sds.read_measurement(),
|
||||
}
|
||||
|
||||
def save_measurement_to_redis(self):
|
||||
"""Saves measurement to redis db"""
|
||||
redis_client.lpush('measurements', json.dumps(self.get_measurement(), default=str))
|
||||
|
||||
def get_last_n_measurements(self, n=10):
|
||||
"""Returns the last n measurements in the list"""
|
||||
return [json.loads(x) for x in redis_client.lrange('measurements', -n, -1)]
|
||||
75
src/app.py
Normal file
75
src/app.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import os
|
||||
import time
|
||||
from flask import Flask, request, jsonify, render_template
|
||||
from AirQualityMonitor import AirQualityMonitor
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
import redis
|
||||
import atexit
|
||||
from flask_cors import CORS, cross_origin
|
||||
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
cors = CORS(app)
|
||||
app.config['CORS_HEADERS'] = 'Content-Type'
|
||||
aqm = AirQualityMonitor()
|
||||
|
||||
scheduler = BackgroundScheduler()
|
||||
scheduler.add_job(func=aqm.save_measurement_to_redis, trigger="interval", seconds=60)
|
||||
scheduler.start()
|
||||
atexit.register(lambda: scheduler.shutdown())
|
||||
|
||||
|
||||
def reconfigure_data(measurement):
|
||||
"""Reconfigures data for chart.js"""
|
||||
current = int(time.time())
|
||||
measurement = measurement.reverse()
|
||||
return {
|
||||
'labels': [int((current - (x['time'])) / 60) for x in measurement],
|
||||
'pm10': {
|
||||
'label': 'pm10',
|
||||
'data': [x['measurement']['pm10'] for x in measurement],
|
||||
'backgroundColor': '#cc0000',
|
||||
'borderColor': '#cc0000',
|
||||
'borderWidth': 3,
|
||||
},
|
||||
'pm2': {
|
||||
'label': 'pm2.5',
|
||||
'data': [x['measurement']['pm2.5'] for x in measurement],
|
||||
'backgroundColor': '#42C0FB',
|
||||
'borderColor': '#42C0FB',
|
||||
'borderWidth': 3,
|
||||
},
|
||||
}
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Index page for the application"""
|
||||
context = {
|
||||
'historical': reconfigure_data(aqm.get_last_n_measurements()),
|
||||
}
|
||||
return render_template('index.html', context=context)
|
||||
|
||||
|
||||
@app.route('/api/')
|
||||
@cross_origin()
|
||||
|
||||
def api():
|
||||
"""Returns historical data from the sensor"""
|
||||
context = {
|
||||
'historical': reconfigure_data(aqm.get_last_n_measurements()),
|
||||
}
|
||||
return jsonify(context)
|
||||
|
||||
|
||||
@app.route('/api/now/')
|
||||
def api_now():
|
||||
"""Returns latest data from the sensor"""
|
||||
context = {
|
||||
'current': aqm.get_measurement(),
|
||||
}
|
||||
return jsonify(context)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True, use_reloader=False, host='0.0.0.0', port=int(os.environ.get('PORT', '8000')))
|
||||
6
src/requirements.txt
Normal file
6
src/requirements.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
sds011==0.2.0
|
||||
ipython
|
||||
flask
|
||||
redis
|
||||
APScheduler==3.7.0
|
||||
flask-cors
|
||||
59
src/templates/index.html
Normal file
59
src/templates/index.html
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<title>Raspberry Pi Air Quality Monitor</title>
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Raspberry Pi Air Quality Monitor</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<canvas id="historicalChart" width="400" height="200"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous">
|
||||
</script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.3.2/chart.min.js" integrity="sha512-VCHVc5miKoln972iJPvkQrUYYq7XpxXzvqNfiul1H4aZDwGBGC0lq373KNleaB2LpnC2a/iNfE5zoRYmB4TRDQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>
|
||||
|
||||
$.getJSON('http://10.0.0.172:8000/api/', function(data) {
|
||||
var ctx = document.getElementById('historicalChart').getContext('2d');
|
||||
var historicalChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: data.historical.labels,
|
||||
datasets: [data.historical.pm10, data.historical.pm2]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue