Move coffee scale to web2.0

This commit is contained in:
Jan Tuomi
2017-09-18 23:16:19 +03:00
parent 7bdd970845
commit 5da4b52bde
15 changed files with 432 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
from . import mqtt
mqtt.client.loop_start()
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class CoffeeScaleConfig(AppConfig):
name = 'coffee_scale'
View File
+3
View File
@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.
+65
View File
@@ -0,0 +1,65 @@
import paho.mqtt.client as mqtt
import logging
import datetime
from collections import deque
TOPIC_TEMP = "sik/kiltahuone/kahvivaaka/temperature"
TOPIC_WEIGHT = "sik/kiltahuone/kahvivaaka/weight"
BREWING_DIFFERENTIAL = 60
# setting down the pan creates at least 800 g of weight
BREWING_DIFFERENTIAL_ERROR_MAX = 800
WEIGHT_DEQUE_LENGTH = 20
weight_deque = deque(maxlen=WEIGHT_DEQUE_LENGTH)
latest = {
'last_brew': datetime.datetime.now()
}
def on_connect(client, userdata, flags, rc):
logging.info('Subscribing to all topics on mqtt.sik.party.')
client.subscribe("#")
def on_message(client, userdata, msg):
if msg.topic == TOPIC_TEMP:
latest['temp'] = float(msg.payload.decode('utf-8'))
elif msg.topic == TOPIC_WEIGHT:
weight = float(msg.payload.decode('utf-8'))
latest['weight'] = weight
weight_deque.appendleft(weight)
def on_disconnect(client, userdata, rc):
client.loop_stop(force=False)
if rc != 0:
print("Unexpected disconnection.")
else:
print("Disconnected")
def get_latest():
if len(weight_deque) > 2:
first = weight_deque[0]
last = weight_deque[len(weight_deque) - 1]
diff = first - last # reverse order
if len(weight_deque) < WEIGHT_DEQUE_LENGTH:
brewing = False
else:
if BREWING_DIFFERENTIAL < diff < BREWING_DIFFERENTIAL_ERROR_MAX:
brewing = True
latest['last_brew'] = datetime.datetime.now()
else:
brewing = False
latest['brewing'] = brewing
return latest
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.on_disconnect = on_disconnect
client.connect("mqtt.sik.party", 1883, 60)
Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

+292
View File
@@ -0,0 +1,292 @@
<html>
<head>
<title>Coffee Cups @Guild Room - AYY SIK RY</title>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="3600">
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<style>
body {
background-color: white;
font-family: monospace;
color: black;
}
#container{
position:relative;
width:95%;
margin-left:auto;
margin-right:auto;
height:100%;
overflow:hidden;
}
#upper{
background-size: contain;
background-repeat: no-repeat;
background-position: bottom center;
background-image: url("/static/img/smokes.png");
transform-origin: bottom;
animation: smokes 8s ease-in-out 0s infinite;
opacity:0;
height:40%;
}
#lower{
position:relative;
background-size: contain;
background-repeat: no-repeat;
background-position: top center;
background-image: url("/static/img/coffeecup3.png");
height:60%;
}
#scale{
position:absolute;
top:80%;
width:90%;
height:10%;
margin: 0% 5% 0% 5%;
background: lightgrey;
border-radius: 10px;
overflow:hidden;
}
#scale2{
width: 0%;
transition: width 2s;
height:100%;
background: green;
border-radius: 10px;
}
#brewtime{
text-align:right;
position:absolute;
right:0px;
z-index:5;
font-size:10vw;
}
#address{
text-align:left;
position:absolute;
left:0px;
z-index:5;
font-size:4vw;
color: #333;
}
noscript{
color:red;
}
#text{
color:green;
position:absolute;
top:50%;
left:50%;
}
.brewing{
animation: brewing 5s ease-in-out 0s infinite;
}
.hurry{
color:red !important;
}
.unknown{
color:orange !important;
animation: unknown 5s ease-in-out 0s infinite;
}
.friday{
animation: friday 20s ease-in-out 0s infinite;
}
.normal{
animation: normal 1000s ease-in-out 0s infinite;
}
.coffeeready{
animation: coffeeready 10s ease-in-out 0s;
}
@keyframes smokes {
0% {transform: skewX(-10deg);}
50% {transform: skewX(10deg);}
100% {transform: skewX(-10deg);}
}
@keyframes brewing {
0% {color:green;}
50% {color: transparent;}
100% {color:green;}
}
@keyframes coffeeready {
0% {background-color:white;}
25% {background-color:green;}
50% {background-color:white;}
75% {background-color:green;}
100% {background-color:white;}
}
@keyframes unknown {
0%,40% {transform: rotate(0deg);}
60%,100% {transform: rotate(360deg);}
}
@keyframes friday {
0% {transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}
@keyframes normal {
0%,49% {transform: rotate(0deg);}
50%,100% {transform: rotate(360deg);}
}
</style>
<script>
var len = 0;
var lastBrew = "&infin;";
var brewtext = "";
$(document).ready(function(){
$('#text').bind("DOMSubtreeModified", resize);
fetchdata();
updateTime();
setInterval(updateTime,1000);
formatBrewTime();
setInterval(formatBrewTime,10000);
});
$(window).resize(resize);
var defaultBackoffTime = 1000;
var modified = 0;
function fetchdata(hxr, status){
var backoffTime = defaultBackoffTime;
modified = 1;
if(typeof status !== 'undefined'){
if(status == "success"){
backoffTime = defaultBackoffTime;
parseData(hxr.responseText);
}
else if(status == "error"){
backoffTime *= 2;
modified = 0
setTimeout(fetchdata, backoffTime);
handleError();
return;
}
}
setTimeout(function() {
$.ajax({cache: false, ifModified: modified, url: '/coffee/cups', complete: fetchdata, timeout:120000});
}, 2000);
}
function formatBrewTime(){
if(!brewtext && lastBrew instanceof Date){
var now = new Date();
var timeDiff = Math.max(now.getTime()-lastBrew.getTime(),0);
if(timeDiff < 3600000)
tmp = Math.round(timeDiff/60000) + ' min';
else
tmp = '~' + Math.round(timeDiff/3600000*2)/2 + ' h';
$("#brewtime").html(tmp);
}else
$("#brewtime").html(brewtext);
}
function handleError(){
setData("?",0,0,Number.MAX_VALUE,Number.MAX_VALUE);
}
function parseData(string) {
var data = JSON.parse(string);
if (data) {
var date = new Date(data.date);
lastBrew = new Date(data.last_brew);
var now = new Date();
var cups = data.cups;
var brewing = data.brewing;
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(),0);
var opa = Math.max(100-timeDiff/90000,0);
setData(cups, data.temp, opa,now.getTime()-date.getTime(), timeDiff, brewing);
}
else{
handleError();
}
}
function setData(cups, temp, opa, timeFromUpdate, timeFromBrew, brewing){
var now = new Date();
if(cups == 0)
opa = 0;
brewtext = "";
$("#upper").css({opacity: opa/100});
$("#scale2").css({width: Math.min(cups/9*100,100) + '%'});
$("#text,body").removeClass();
if(timeFromUpdate > 600000){
cups = "?";
brewtext = "&infin;";
$("#text").addClass("unknown");
}
else if(brewing){
cups = "+";
brewtext = ":)";
$("#text").addClass("brewing");
}
else if(cups <= 2){
$("#text").addClass("hurry");
}
formatBrewTime();
if($("#text").html() == "+" && !brewing)
$("body").addClass("coffeeready");
len = cups.toString().length;
$("#text").html(cups);
}
function updateTime(){
var now = new Date();
$("#time").html(formatTime(now.getHours(),now.getMinutes(),now.getSeconds()));
}
function formatTime(hours, minutes, seconds){
var str = "";
if(hours < 10)
str += "0";
str += hours;
str += ":";
if(minutes < 10)
str += "0";
str += minutes;
str += ":";
if(seconds < 10)
str += "0";
str += seconds;
return str;
}
function resize(){
var w = $("#container").width();
var h = $("#container").height();
var s = w > h ? h : w;
var font = s*0.8*0.38/Math.sqrt(len);
$("#text").css({ top: s*0.16-font/2 + 'px', fontSize: font + 'px', marginLeft: -font*len*3/10 + 'px'});
}
</script>
</head>
<body>
<div id="container">
<span id="brewtime"></span>
<span id="address">
Kahvivaaka
<noscript><br>This page uses JavaScript!</noscript>
<br><span id="time"></span></span>
</span>
<div id="upper">
</div>
<!--Kahvinkeitin on rikki. Varakeittimellä keitettyä kahvia saattaa olla.-->
<div id="lower" class="normal">
<div id="text"></div>
<div id="scale"><div id="scale2"></div></div>
</div>
</div>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+14
View File
@@ -0,0 +1,14 @@
from django.conf.urls import url
from django.views.generic.base import RedirectView
from .views import coffee_view, cups_view
favicon_view = RedirectView.as_view(url='static/img/favicon.ico', permanent=True)
urlpatterns = [
# landing page
url(r'^$', coffee_view),
url(r'^cups', cups_view),
]
+40
View File
@@ -0,0 +1,40 @@
from django.shortcuts import render
from django.http import JsonResponse
import datetime
from .mqtt import get_latest
def get_cups_from_weight(weight):
if not weight:
return 0
EMPTY = 820 # grams
FULL = 2000 # grams
cups = 10 * (weight - EMPTY) / (FULL - EMPTY)
cups = round(cups, 1)
if cups < 0:
cups = 0
if cups > 10:
cups = 10
return cups
def coffee_view(request):
return render(request, 'coffee.html')
def cups_view(request):
now = datetime.datetime.now()
latest = get_latest()
cups = get_cups_from_weight(latest.get('weight'))
data = {
'date': now,
'cups': cups,
'temp': latest.get('temp'),
'last_brew': latest.get('last_brew'),
'brewing': latest.get('brewing')
}
return JsonResponse(data)
+1
View File
@@ -25,3 +25,4 @@ dealer==2.0.5
django-modeltranslation==0.12.1
django-auditlog==0.4.3
django-phonenumber-field==1.3.0
paho-mqtt==1.3.0
+1
View File
@@ -50,6 +50,7 @@ INSTALLED_APPS = [
'webapp',
'members',
'infoscreen',
'coffee_scale',
'rest_framework',
'django_nose',
'bootstrap3',
+2
View File
@@ -25,11 +25,13 @@ from django.contrib.staticfiles import views as static_views
import webapp.urls
import infoscreen.urls
import members.urls
import coffee_scale.urls
urlpatterns = [
url(r'', include('webapp.urls')),
url(r'^members/', include('members.urls')),
url(r'^infoscreen/', include('infoscreen.urls')),
url(r'^coffee/', include('coffee_scale.urls')),
# admin
url(r'^admin/', admin.site.urls),