Flask server for displaying and interacting with fridge
Web interface defined in template for displaying sensor data, camera capture and action buttons.
BIN
assets/capture.jpg
Normal file
|
After Width: | Height: | Size: 733 KiB |
BIN
assets/favicon/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
assets/favicon/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
assets/favicon/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/favicon/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 933 B |
BIN
assets/favicon/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/favicon/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
1
assets/favicon/site.webmanifest
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||||
72
server.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import yaml
|
||||||
|
from flask import Flask, request, render_template, send_file, redirect, send_from_directory
|
||||||
|
from brewSensor import BCM600Sensor, DHT11Sensor, BrewSensor
|
||||||
|
from brewCamera import BrewCamera
|
||||||
|
from brewRelay import BrewRelay
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
brewCamera = BrewCamera(20)
|
||||||
|
|
||||||
|
def readYaml(filePath):
|
||||||
|
loader = yaml.SafeLoader
|
||||||
|
loader.add_constructor('!Relay', BrewRelay.fromYaml)
|
||||||
|
loader.add_constructor('!bcm600', BCM600Sensor.fromYaml)
|
||||||
|
loader.add_constructor('!dht11', DHT11Sensor.fromYaml)
|
||||||
|
return yaml.load(open(filePath, "rb"), Loader=loader)
|
||||||
|
|
||||||
|
rangers = readYaml('brew.yaml')
|
||||||
|
sensors = rangers['sensors']
|
||||||
|
relays = rangers['relays']
|
||||||
|
|
||||||
|
if sys.argv[-1] == '-c':
|
||||||
|
brewCamera.spawnBackgroundCapture()
|
||||||
|
|
||||||
|
for sensor in sensors:
|
||||||
|
sensor.spawnBackgroundSensorLog()
|
||||||
|
|
||||||
|
def sensorTemp(location):
|
||||||
|
sensor = BrewSensor.getSensorByItsLocation(sensors, location)
|
||||||
|
if sensor:
|
||||||
|
return sensor.temp
|
||||||
|
return 'not found :('
|
||||||
|
|
||||||
|
@app.route('/toggle/<controls>', methods=['POST', 'GET'])
|
||||||
|
def toggle(controls):
|
||||||
|
if request.method == 'GET':
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
relay = BrewRelay.getRelayByWhatItControls(relays, controls)
|
||||||
|
if relay:
|
||||||
|
relay.set(not relay.state)
|
||||||
|
if relay.controls == 'light':
|
||||||
|
brewCamera.capture()
|
||||||
|
else:
|
||||||
|
print('relay {} not found'.format(controls))
|
||||||
|
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
@app.route('/assets/<filename>')
|
||||||
|
def assets(filename):
|
||||||
|
return send_file('./assets/{}'.format(filename))
|
||||||
|
|
||||||
|
@app.route('/favicon.ico')
|
||||||
|
def favicon():
|
||||||
|
faviconPath = os.path.join(app.root_path, 'assets/favicon')
|
||||||
|
return send_from_directory(faviconPath, 'favicon.ico')
|
||||||
|
|
||||||
|
@app.route('/feed')
|
||||||
|
def feed():
|
||||||
|
return send_file('./foo.jpg')
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return render_template('./index.html',
|
||||||
|
sensors=sensors,
|
||||||
|
sensorTemp=sensorTemp,
|
||||||
|
relays=relays,
|
||||||
|
captureInterval=brewCamera.interval)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0')
|
||||||
95
templates/index.html
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link rel="Brewpi icon" href="/favicon.ico">
|
||||||
|
<title>Brewpi</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="header-containers">
|
||||||
|
<div>
|
||||||
|
<h1>Brewpi</h1>
|
||||||
|
|
||||||
|
{% for sensor in sensors %}
|
||||||
|
<p>{{ sensor.location }} temp: {{ sensorTemp(sensor.location) }}°C</p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for relay in relays %}
|
||||||
|
<form action="/toggle/{{ relay.controls }}" method="post">
|
||||||
|
<p>{{ relay.controls }} relay is: <b>{{ relay.state }}</b></p>
|
||||||
|
<button type="submit">Toggle {{ relay.controls }}</button>
|
||||||
|
</form>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2><i>Live</i> brew-feed</h2>
|
||||||
|
|
||||||
|
<i style="display: block">Updates every {{ captureInterval }} seconds or on relay toggle</i>
|
||||||
|
|
||||||
|
<img src="{{url_for('assets', filename='capture.jpg')}}" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Graphs</h2>
|
||||||
|
|
||||||
|
<iframe style="width: 100%" src="https://kibana.schleppe.cloud/app/dashboards#/view/94460c50-1c9b-11ec-a070-f3f228e227b1?embed=true&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-24h%2Cto%3Anow))&show-time-filter=true&hide-filter-bar=true" height="920" ></iframe>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;600&display=swap');
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-containers {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
background-color: #0079D3;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem;
|
||||||
|
color: white;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 750px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-containers {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||