mirror of
https://github.com/Manuel83/craftbeerpi3
synced 2026-08-09 18:52:40 +02:00
First Version
This commit is contained in:
+13
@@ -0,0 +1,13 @@
|
|||||||
|
.idea/
|
||||||
|
*.log
|
||||||
|
craftbeerpi.db
|
||||||
|
**/node_modules/*
|
||||||
|
*.pyc
|
||||||
|
*.templog
|
||||||
|
|
||||||
|
modules/plugins/*
|
||||||
|
!modules/plugins/my_plugin/__init__.py
|
||||||
|
!modules/plugins/cbpi**/__init__.py
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
|
||||||
@@ -1 +1,21 @@
|
|||||||
# craftbeerpi3
|
#CraftBeerPi V3.0
|
||||||
|
|
||||||
|
This is CraftBeerPi version 3.0. It's currently in beta status.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Open a terminal window on Raspberry Pi and type:
|
||||||
|
|
||||||
|
<code> git clone http://github.com/craftbeerpi/craftbeerpi</code>
|
||||||
|
|
||||||
|
This will download (clone) the software to your local Raspberry Pi.
|
||||||
|
|
||||||
|
Type <code>cd craftbeerpi</code> to navigate into the craftbeerpi folder.
|
||||||
|
|
||||||
|
Type <code>sudo ./install.sh</code>
|
||||||
|
|
||||||
|
## ATTENTION
|
||||||
|
|
||||||
|
CraftBeerPi 3.0 is a complete rewrite. Server as well as user interface. I recommend to use a second SD card for testing.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: craftbeerpi
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: Put a short description of the service here
|
||||||
|
# Description: Put a long description of the service here
|
||||||
|
### END INIT INFO
|
||||||
|
|
||||||
|
# Change the next 3 lines to suit where you install your script and what you want to call it
|
||||||
|
DIR=#DIR#
|
||||||
|
DAEMON=$DIR/run.py
|
||||||
|
DAEMON_NAME=CraftBeerPI
|
||||||
|
|
||||||
|
# Add any command line options for your daemon here
|
||||||
|
DAEMON_OPTS=""
|
||||||
|
|
||||||
|
# This next line determines what user the script runs as.
|
||||||
|
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
|
||||||
|
DAEMON_USER=root
|
||||||
|
|
||||||
|
# The process ID of the script when it runs is stored here:
|
||||||
|
PIDFILE=/var/run/$DAEMON_NAME.pid
|
||||||
|
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
do_start () {
|
||||||
|
log_daemon_msg "Starting system $DAEMON_NAME daemon"
|
||||||
|
start-stop-daemon --start --background --pidfile $PIDFILE --make-pidfile --user $DAEMON_USER --chuid $DAEMON_USER --chdir $DIR --startas $DAEMON -- $DAEMON_OPTS
|
||||||
|
log_end_msg $?
|
||||||
|
}
|
||||||
|
do_stop () {
|
||||||
|
log_daemon_msg "Stopping system $DAEMON_NAME daemon"
|
||||||
|
start-stop-daemon --stop --pidfile $PIDFILE --retry 10
|
||||||
|
log_end_msg $?
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
|
||||||
|
start|stop)
|
||||||
|
do_${1}
|
||||||
|
;;
|
||||||
|
|
||||||
|
restart|reload|force-reload)
|
||||||
|
do_stop
|
||||||
|
do_start
|
||||||
|
;;
|
||||||
|
|
||||||
|
status)
|
||||||
|
status_of_proc "$DAEMON_NAME" "$DAEMON" && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
CREATE TABLE kettle
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80),
|
||||||
|
sensor VARCHAR(80),
|
||||||
|
heater VARCHAR(10),
|
||||||
|
automatic VARCHAR(255),
|
||||||
|
logic VARCHAR(50),
|
||||||
|
config VARCHAR(1000),
|
||||||
|
agitator VARCHAR(10),
|
||||||
|
target_temp INTEGER,
|
||||||
|
height INTEGER,
|
||||||
|
diameter INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE step
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
"order" INTEGER,
|
||||||
|
name VARCHAR(80),
|
||||||
|
type VARCHAR(100),
|
||||||
|
stepstate VARCHAR(255),
|
||||||
|
state VARCHAR(1),
|
||||||
|
start INTEGER,
|
||||||
|
end INTEGER,
|
||||||
|
config VARCHAR(255),
|
||||||
|
kettleid INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE sensor
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
type VARCHAR(100),
|
||||||
|
name VARCHAR(80),
|
||||||
|
config VARCHAR(500),
|
||||||
|
hide BOOLEAN
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE fermenter_step
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80),
|
||||||
|
hours INTEGER,
|
||||||
|
minutes INTEGER,
|
||||||
|
days INTEGER,
|
||||||
|
temp INTEGER,
|
||||||
|
direction VARCHAR(1),
|
||||||
|
"order" INTEGER,
|
||||||
|
state VARCHAR(1),
|
||||||
|
start INTEGER,
|
||||||
|
timer_start INTEGER,
|
||||||
|
end INTEGER,
|
||||||
|
fermenter_id INTEGER,
|
||||||
|
FOREIGN KEY (fermenter_id) REFERENCES fermenter (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE fermenter
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(80),
|
||||||
|
brewname VARCHAR(80),
|
||||||
|
sensor VARCHAR(80),
|
||||||
|
sensor2 VARCHAR(80),
|
||||||
|
sensor3 VARCHAR(80),
|
||||||
|
heater VARCHAR(10),
|
||||||
|
logic VARCHAR(50),
|
||||||
|
config VARCHAR(1000),
|
||||||
|
cooler VARCHAR(10),
|
||||||
|
target_temp INTEGER
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE config
|
||||||
|
(
|
||||||
|
name VARCHAR(50) PRIMARY KEY NOT NULL,
|
||||||
|
value VARCHAR(255),
|
||||||
|
type VARCHAR(50),
|
||||||
|
description VARCHAR(255),
|
||||||
|
options VARCHAR(255)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
INSERT INTO config VALUES ('kettle_cols', 4, 'select', 'Adjust the width of a kettle widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||||
|
INSERT INTO config VALUES ('actor_cols', 4, 'select', 'Adjust the width of a actor widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||||
|
INSERT INTO config VALUES ('sensor_cols', 4, 'select', 'Adjust the width of a sensor widget on the brewing dashboard', '[1,2,3, 4, 5, 6, 7, 8, 9, 10, 11, 12]');
|
||||||
|
INSERT INTO config VALUES ('unit', 'C', 'select', 'Temperature Unit', '["C","F"]');
|
||||||
|
INSERT INTO config VALUES ('brewery_name', 'My Home Brewery', 'text', 'Your brewery name', NULL );
|
||||||
|
INSERT INTO config VALUES ('buzzer', 16, 'select', 'Buzzer GPIO', '[16,17,18,19,20]');
|
||||||
|
INSERT INTO config VALUES ('setup', 'YES', 'select', 'Show the Setup dialog', '["YES","NO"]');
|
||||||
|
|
||||||
|
CREATE TABLE actor
|
||||||
|
(
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name VARCHAR(50),
|
||||||
|
type VARCHAR(100),
|
||||||
|
config VARCHAR(500),
|
||||||
|
hide BOOLEAN
|
||||||
|
);
|
||||||
Executable
+149
@@ -0,0 +1,149 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#CraftBeerPi Installer
|
||||||
|
# Copy 2017 Manuel Fritsch
|
||||||
|
|
||||||
|
confirmAnswer () {
|
||||||
|
whiptail --title "Confirmation" --yes-button "Yes" --no-button "No" --defaultno --yesno "$1" 10 56
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
show_menu () {
|
||||||
|
# We show the host name right in the menu title so we know which Pi we are connected to
|
||||||
|
OPTION=$(whiptail --title "CraftBeerPi 3.0" --menu "Choose your option:" 15 56 7 \
|
||||||
|
"1" "Install CraftBeerPi" \
|
||||||
|
"2" "Clear Database" \
|
||||||
|
"3" "Add To Autostart" \
|
||||||
|
"4" "Remove From Autostart" \
|
||||||
|
"5" "Start CraftBeerPi" \
|
||||||
|
"6" "Stop CraftBeerPi" \
|
||||||
|
"7" "Software Update (git pull)" \
|
||||||
|
"8" "Reset File Changes (git reset --hard)" \
|
||||||
|
"9" "Clear all logs" \
|
||||||
|
"10" "Reboot Raspberry Pi" 3>&1 1>&2 2>&3)
|
||||||
|
|
||||||
|
BUTTON=$?
|
||||||
|
# Exit if user pressed cancel or escape
|
||||||
|
if [[ ($BUTTON -eq 1) || ($BUTTON -eq 255) ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ $BUTTON -eq 0 ]; then
|
||||||
|
case $OPTION in
|
||||||
|
1)
|
||||||
|
confirmAnswer "Would you like run apt-get update & apt-get upgrade?"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
apt-get -y update; apt-get -y upgrade;
|
||||||
|
fi
|
||||||
|
|
||||||
|
confirmAnswer "Would you like to install wiringPI? This is required to control the GPIO"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
git clone git://git.drogon.net/wiringPi;
|
||||||
|
cd wiringPi;
|
||||||
|
./build; cd ..;
|
||||||
|
rm -rf wiringPi;
|
||||||
|
fi
|
||||||
|
|
||||||
|
apt-get -y install python-setuptools
|
||||||
|
easy_install pip
|
||||||
|
apt-get -y install python-dev
|
||||||
|
apt-get -y install libpcre3-dev
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
confirmAnswer "Would you like to add active 1-wire support at your Raspberry PI now? IMPORTANT: The 1-wire thermometer must be conneted to GPIO 4!"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
#apt-get -y update; apt-get -y upgrade;
|
||||||
|
echo '# CraftBeerPi 1-wire support' >> "/boot/config.txt"
|
||||||
|
echo 'dtoverlay=w1-gpio,gpiopin=4,pullup=on' >> "/boot/config.txt"
|
||||||
|
|
||||||
|
fi
|
||||||
|
|
||||||
|
whiptail --title "Installition Finished" --msgbox "CraftBeerPi installation finished! You must hit OK to continue." 8 78
|
||||||
|
show_menu
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
confirmAnswer "Are you sure you want to clear the CraftBeerPi. All hardware setting will be deleted"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
sudo rm -f craftbeerpi.db
|
||||||
|
whiptail --title "Database Delted" --msgbox "The CraftBeerPi database was succesfully deleted. You must hit OK to continue." 8 78
|
||||||
|
show_menu
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
confirmAnswer "Are you sure you want to add CraftBeerPi to autostart"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
sed "s@#DIR#@${PWD}@g" config/craftbeerpiboot > /etc/init.d/craftbeerpiboot
|
||||||
|
chmod 755 /etc/init.d/craftbeerpiboot;
|
||||||
|
update-rc.d craftbeerpiboot defaults;
|
||||||
|
whiptail --title "Added succesfull to autostart" --msgbox "The CraftBeerPi was added to autostart succesfully. You must hit OK to continue." 8 78
|
||||||
|
show_menu
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
4)
|
||||||
|
confirmAnswer "Are you sure you want to remove CraftBeerPi from autostart"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
sudo rm -f /etc/init.d/craftbeerpiboot
|
||||||
|
show_menu
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
5)
|
||||||
|
sudo /etc/init.d/craftbeerpiboot start
|
||||||
|
whiptail --title "CraftBeerPi started" --msgbox "Please connect via Browser: http://<IP-ADDRESS>:5000" 8 78
|
||||||
|
show_menu
|
||||||
|
;;
|
||||||
|
6)
|
||||||
|
sudo /etc/init.d/craftbeerpiboot stop
|
||||||
|
whiptail --title "CraftBeerPi stoped" --msgbox "The software is stoped" 8 78
|
||||||
|
show_menu
|
||||||
|
;;
|
||||||
|
7)
|
||||||
|
confirmAnswer "Are you sure you want to pull a software update?"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
|
||||||
|
whiptail --textbox /dev/stdin 20 50 <<<"$(git pull)"
|
||||||
|
show_menu
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
8)
|
||||||
|
confirmAnswer "Are you sure you want to reset all file changes for this git respository (git reset --hard)?"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
whiptail --textbox /dev/stdin 20 50 <<<"$(git reset --hard)"
|
||||||
|
show_menu
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
9)
|
||||||
|
confirmAnswer "Are you sure you want to delete all CraftBeerPi log files"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
sudo rm -rf logs/*.log
|
||||||
|
whiptail --title "Log files deleted" --msgbox "All CraftBeerPi Files are deleted. You must hit OK to continue." 8 78
|
||||||
|
show_menu
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
10)
|
||||||
|
confirmAnswer "Are you sure you want to reboo the Raspberry Pi?"
|
||||||
|
if [ $? = 0 ]; then
|
||||||
|
sudo reboot
|
||||||
|
else
|
||||||
|
show_menu
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]
|
||||||
|
then whiptail --title "Please run as super user (sudo)" --msgbox "Please run the install file -> sudo install.sh " 8 78
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_menu
|
||||||
Executable
Executable
+65
@@ -0,0 +1,65 @@
|
|||||||
|
import json
|
||||||
|
import pprint
|
||||||
|
import sys, os
|
||||||
|
from flask import Flask, render_template, redirect
|
||||||
|
from flask_socketio import SocketIO, emit
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
import logging
|
||||||
|
# Define the WSGI application object
|
||||||
|
|
||||||
|
from app_config import *
|
||||||
|
import pprint
|
||||||
|
|
||||||
|
from modules.core.db import get_db
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return redirect('ui')
|
||||||
|
|
||||||
|
|
||||||
|
# Define the database object which is imported
|
||||||
|
# by modules and controllers
|
||||||
|
|
||||||
|
|
||||||
|
import modules.steps
|
||||||
|
import modules.config
|
||||||
|
import modules.logs
|
||||||
|
import modules.sensors
|
||||||
|
import modules.actor
|
||||||
|
import modules.notification
|
||||||
|
import modules.fermenter
|
||||||
|
import modules.addon
|
||||||
|
import modules.ui
|
||||||
|
import modules.system
|
||||||
|
import modules.buzzer
|
||||||
|
import modules.stats
|
||||||
|
import modules.kettle
|
||||||
|
from app_config import cbpi
|
||||||
|
# Build the database:
|
||||||
|
# This will create the database file using SQLAlchemy
|
||||||
|
|
||||||
|
|
||||||
|
pp = pprint.PrettyPrinter(indent=6)
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
print "INIT DB"
|
||||||
|
with app.app_context():
|
||||||
|
db = get_db()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with app.open_resource('../config/schema.sql', mode='r') as f:
|
||||||
|
db.cursor().executescript(f.read())
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
init_db()
|
||||||
|
cbpi.run_init()
|
||||||
|
|
||||||
|
cbpi.run_background_processes()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import time
|
||||||
|
from flask_classy import route
|
||||||
|
from modules import DBModel, cbpi
|
||||||
|
from modules.core.baseview import BaseView
|
||||||
|
|
||||||
|
class Actor(DBModel):
|
||||||
|
__fields__ = ["name","type", "config", "hide"]
|
||||||
|
__table_name__ = "actor"
|
||||||
|
__json_fields__ = ["config"]
|
||||||
|
|
||||||
|
class ActorView(BaseView):
|
||||||
|
model = Actor
|
||||||
|
cache_key = "actors"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_init_callback(self, obj):
|
||||||
|
obj.state = 0
|
||||||
|
obj.power = 100
|
||||||
|
|
||||||
|
def post_post_callback(self, m):
|
||||||
|
self.api.init_actor(m.id)
|
||||||
|
|
||||||
|
def post_put_callback(self, m):
|
||||||
|
|
||||||
|
self.api.init_actor(m.id)
|
||||||
|
|
||||||
|
@route("<int:id>/switch/on", methods=["POST"])
|
||||||
|
def on(self, id):
|
||||||
|
self.api.switch_actor_on(id)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route("<int:id>/switch/off", methods=["POST"])
|
||||||
|
def off(self, id):
|
||||||
|
self.api.switch_actor_off(id)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route("<int:id>/power/<int:power>", methods=["POST"])
|
||||||
|
def power(self, id, power):
|
||||||
|
self.api.actor_power(id, power)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route("<int:id>/toggle", methods=["POST"])
|
||||||
|
def toggle(self, id):
|
||||||
|
|
||||||
|
if self.api.cache.get("actors").get(id).state == 0:
|
||||||
|
self.on(id)
|
||||||
|
else:
|
||||||
|
self.off(id)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def toggleTimeJob(self, id, t):
|
||||||
|
self.api.cache.get("actors").get(int(id)).timer = int(time.time()) + int(t)
|
||||||
|
self.toggle(int(id))
|
||||||
|
self.api.socketio.sleep(t)
|
||||||
|
self.api.cache.get("actors").get(int(id)).timer = None
|
||||||
|
self.toggle(int(id))
|
||||||
|
|
||||||
|
@route("/<id>/toggle/<int:t>", methods=["POST"])
|
||||||
|
def toggleTime(self, id, t):
|
||||||
|
t = self.api.socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=1000)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE ACTOR MODULE"
|
||||||
|
cbpi.app.logger.info("INITIALIZE ACTOR MODULE")
|
||||||
|
ActorView.register(cbpi.app, route_base='/api/actor')
|
||||||
|
ActorView.init_cache()
|
||||||
|
cbpi.init_actors()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import endpoints
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from flask import Blueprint, request, send_from_directory
|
||||||
|
from importlib import import_module
|
||||||
|
from modules import socketio, cbpi
|
||||||
|
|
||||||
|
|
||||||
|
from git import Repo
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
blueprint = Blueprint('addon', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
modules = {}
|
||||||
|
|
||||||
|
def merge(source, destination):
|
||||||
|
"""
|
||||||
|
Helper method to merge two dicts
|
||||||
|
:param source:
|
||||||
|
:param destination:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for key, value in source.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
# get node or create one
|
||||||
|
node = destination.setdefault(key, {})
|
||||||
|
merge(value, node)
|
||||||
|
else:
|
||||||
|
destination[key] = value
|
||||||
|
|
||||||
|
return destination
|
||||||
|
|
||||||
|
@blueprint.route('/', methods=['GET'])
|
||||||
|
def getPlugins():
|
||||||
|
"""
|
||||||
|
Endpoint for all plugins
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for filename in os.listdir("./modules/plugins"):
|
||||||
|
if filename.endswith(".DS_Store") or filename.endswith(".py") or filename.endswith(".pyc"):
|
||||||
|
continue
|
||||||
|
result.append(filename)
|
||||||
|
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
@blueprint.route('/<name>', methods=['GET'])
|
||||||
|
def getFile(name):
|
||||||
|
"""
|
||||||
|
Returns plugin code
|
||||||
|
:param name: plugin name
|
||||||
|
:return: the plugin code from __init__.py
|
||||||
|
"""
|
||||||
|
return send_from_directory('./plugins/'+name, "__init__.py")
|
||||||
|
|
||||||
|
@blueprint.route('/<name>', methods=['PUT'])
|
||||||
|
def createPlugin(name):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Create a new plugin file
|
||||||
|
:param name: the plugin name
|
||||||
|
:return: empty http response 204
|
||||||
|
"""
|
||||||
|
if not os.path.exists("./modules/plugins/"+name):
|
||||||
|
os.makedirs("./modules/plugins/"+name)
|
||||||
|
with open("./modules/plugins/" + name + "/__init__.py", "wb") as fo:
|
||||||
|
fo.write("")
|
||||||
|
cbpi.emit_message("PLUGIN %s CREATED" % (name))
|
||||||
|
return ('', 204)
|
||||||
|
else:
|
||||||
|
cbpi.emit_message("Failed to create plugin %s. Name arlready in use" % (name))
|
||||||
|
return ('', 500)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/<name>', methods=['POST'])
|
||||||
|
def saveFile(name):
|
||||||
|
|
||||||
|
"""
|
||||||
|
save plugin code. code is provides via http body
|
||||||
|
:param name: the plugin name
|
||||||
|
:return: empty http reponse
|
||||||
|
"""
|
||||||
|
with open("./modules/plugins/"+name+"/__init__.py", "wb") as fo:
|
||||||
|
fo.write(request.get_data())
|
||||||
|
cbpi.emit_message("PLUGIN %s SAVED" % (name))
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@blueprint.route('/<name>', methods=['DELETE'])
|
||||||
|
def deletePlugin(name):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Delete plugin
|
||||||
|
:param name: plugin name
|
||||||
|
:return: HTTP 204 if ok - HTTP 500 if plugin not exists
|
||||||
|
"""
|
||||||
|
if os.path.isdir("./modules/plugins/"+name) is False:
|
||||||
|
return ('Dir Not found', 500)
|
||||||
|
shutil.rmtree("./modules/plugins/"+name)
|
||||||
|
cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@blueprint.route('/<name>/reload/', methods=['POST'])
|
||||||
|
def reload(name):
|
||||||
|
"""
|
||||||
|
hot reload plugnin
|
||||||
|
:param name:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if name in cache["modules"]:
|
||||||
|
reload(cache["modules"][name])
|
||||||
|
cbpi.emit_message("REALOD OF PLUGIN %s SUCCESSFUL" % (name))
|
||||||
|
return ('', 204)
|
||||||
|
else:
|
||||||
|
cache["modules"][name] = import_module("modules.plugins.%s" % (name))
|
||||||
|
return ('', 204)
|
||||||
|
except Exception as e:
|
||||||
|
cbpi.emit_message("REALOD OF PLUGIN %s FAILED" % (name))
|
||||||
|
return json.dumps(e.message)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/list', methods=['GET'])
|
||||||
|
def plugins():
|
||||||
|
"""
|
||||||
|
Read the central plugin yaml to get a list of all official plugins
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
response = requests.get("https://raw.githubusercontent.com/craftbeerpi/pluginlist/master/plugins.yaml")
|
||||||
|
cbpi.cache["plugins"] = merge(yaml.load(response.text), cbpi.cache["plugins"])
|
||||||
|
for key, value in cbpi.cache["plugins"].iteritems():
|
||||||
|
value["installed"] = os.path.isdir("./modules/plugins/%s/" % (key))
|
||||||
|
|
||||||
|
return json.dumps(cbpi.cache["plugins"])
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/<name>/download', methods=['POST'])
|
||||||
|
def download_addon(name):
|
||||||
|
|
||||||
|
plugin = cbpi.cache["plugins"].get(name)
|
||||||
|
plugin["loading"] = True
|
||||||
|
if plugin is None:
|
||||||
|
return ('', 404)
|
||||||
|
try:
|
||||||
|
Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name))
|
||||||
|
cbpi.notify("Download successful", "Plugin %s downloaded successfully" % name)
|
||||||
|
finally:
|
||||||
|
plugin["loading"] = False
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@blueprint.route('/<name>/update', methods=['POST'])
|
||||||
|
def update_addon(name):
|
||||||
|
repo = Repo("./modules/plugins/%s/" % (name))
|
||||||
|
o = repo.remotes.origin
|
||||||
|
info = o.pull()
|
||||||
|
cbpi.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
|
||||||
|
def loadCorePlugins():
|
||||||
|
for filename in os.listdir("./modules/base_plugins"):
|
||||||
|
if filename.endswith(".py") or filename.endswith(".pyc"):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
modules[filename] = import_module("modules.base_plugins.%s" % (filename))
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
|
||||||
|
cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
|
||||||
|
cbpi.app.logger.error(e)
|
||||||
|
|
||||||
|
def loadPlugins():
|
||||||
|
for filename in os.listdir("./modules/plugins"):
|
||||||
|
if filename.endswith(".py") or filename.endswith(".pyc"):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
modules[filename] = import_module("modules.plugins.%s" % (filename))
|
||||||
|
except Exception as e:
|
||||||
|
cbpi.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
|
||||||
|
cbpi.app.logger.error(e)
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=1)
|
||||||
|
def initPlugins(app):
|
||||||
|
print "INITIALIZE CUSTOM PLUGINS"
|
||||||
|
loadCorePlugins()
|
||||||
|
loadPlugins()
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=2)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE ADDON MODULE"
|
||||||
|
cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
|
||||||
|
import json
|
||||||
|
import sys, os
|
||||||
|
from flask import Flask, render_template, redirect, json, g
|
||||||
|
|
||||||
|
|
||||||
|
from flask_socketio import SocketIO, emit
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||||
|
|
||||||
|
from modules.core.core import CraftBeerPi, ActorBase, SensorBase
|
||||||
|
from modules.core.db import DBModel
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
logging.basicConfig(filename='./logs/app.log',level=logging.INFO)
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///../craftbeerpi.db'
|
||||||
|
|
||||||
|
#app.config['SQLALCHEMY_ECHO'] = False
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
app.config['SECRET_KEY'] = 'craftbeerpi'
|
||||||
|
app.config['UPLOAD_FOLDER'] = './upload'
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
@app.teardown_appcontext
|
||||||
|
def close_connection(exception):
|
||||||
|
db = getattr(g, '_database', None)
|
||||||
|
if db is not None:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
class ComplexEncoder(json.JSONEncoder):
|
||||||
|
def default(self, obj):
|
||||||
|
try:
|
||||||
|
if isinstance(obj, DBModel):
|
||||||
|
return obj.__dict__
|
||||||
|
elif isinstance(obj, ActorBase):
|
||||||
|
return obj.state()
|
||||||
|
elif isinstance(obj, SensorBase):
|
||||||
|
return obj.get_value()
|
||||||
|
elif hasattr(obj, "callback"):
|
||||||
|
return obj()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
except TypeError as e:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
app.json_encoder = ComplexEncoder
|
||||||
|
socketio = SocketIO(app, json=json)
|
||||||
|
cbpi = CraftBeerPi(app, socketio)
|
||||||
|
|
||||||
|
app.logger.info("##########################################")
|
||||||
|
app.logger.info("### NEW STARTUP Version 3.0")
|
||||||
|
app.logger.info("##########################################")
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
from modules.core.props import Property, StepProperty
|
||||||
|
from modules.core.step import StepBase
|
||||||
|
from modules import cbpi
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.step
|
||||||
|
class MashStep(StepBase):
|
||||||
|
'''
|
||||||
|
Just put the decorator @cbpi.step on top of a method
|
||||||
|
'''
|
||||||
|
# Properties
|
||||||
|
temp = Property.Number("Temperature", configurable=True)
|
||||||
|
kettle = StepProperty.Kettle("Kettle")
|
||||||
|
timer = Property.Number("Timer in Minutes", configurable=True)
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
'''
|
||||||
|
Initialize Step. This method is called once at the beginning of the step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
# set target tep
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
@cbpi.action("Start Timer Now")
|
||||||
|
def start(self):
|
||||||
|
'''
|
||||||
|
Custom Action which can be execute form the brewing dashboard.
|
||||||
|
All method with decorator @cbpi.action("YOUR CUSTOM NAME") will be available in the user interface
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.stop_timer()
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.set_target_temp(0, self.kettle)
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
'''
|
||||||
|
This method is execute in an interval
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Check if Target Temp is reached
|
||||||
|
if self.get_kettle_temp(self.kettle) >= int(self.temp):
|
||||||
|
# Check if Timer is Running
|
||||||
|
if self.is_timer_finished() is None:
|
||||||
|
self.start_timer(int(self.timer) * 60)
|
||||||
|
|
||||||
|
# Check if timer finished and go to next step
|
||||||
|
if self.is_timer_finished() == True:
|
||||||
|
self.next()
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.step
|
||||||
|
class MashInStep(StepBase):
|
||||||
|
'''
|
||||||
|
Just put the decorator @cbpi.step on top of a method
|
||||||
|
'''
|
||||||
|
# Properties
|
||||||
|
temp = Property.Number("Temperature", configurable=True)
|
||||||
|
kettle = StepProperty.Kettle("Kettle")
|
||||||
|
s = False
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
'''
|
||||||
|
Initialize Step. This method is called once at the beginning of the step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
# set target tep
|
||||||
|
self.s = False
|
||||||
|
self.set_target_temp(self.temp, self.kettle)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
'''
|
||||||
|
This method is execute in an interval
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Check if Target Temp is reached
|
||||||
|
if self.get_kettle_temp(self.kettle) >= int(self.temp) and self.s is False:
|
||||||
|
self.s = True
|
||||||
|
self.notify("Step Temp Reached!", "Please press the next button to continue", timeout=None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
from modules import cbpi, socketio
|
||||||
|
from modules.core.hardware import SensorActive
|
||||||
|
from modules import cbpi
|
||||||
|
from modules.core.props import Property
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.sensor
|
||||||
|
class DummyTempSensor(SensorActive):
|
||||||
|
temp = Property.Number("Temperature", configurable=True, default_value=5)
|
||||||
|
|
||||||
|
def get_unit(self):
|
||||||
|
return "°C" if self.get_config_parameter("unit", "C") == "C" else "°F"
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
while self.is_running():
|
||||||
|
|
||||||
|
self.data_received(self.temp)
|
||||||
|
socketio.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from modules import cbpi
|
||||||
|
from modules.core.controller import KettleController, FermenterController
|
||||||
|
from modules.core.props import Property
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.fermentation_controller
|
||||||
|
class Hysteresis(FermenterController):
|
||||||
|
|
||||||
|
on = Property.Number("Offset On", True, 0)
|
||||||
|
off = Property.Number("Offset Off", True, 0)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
|
||||||
|
super(FermenterController, self).stop()
|
||||||
|
self.heater_off()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self.is_running():
|
||||||
|
print "Temp %s" % self.get_temp()
|
||||||
|
if self.get_temp() < self.get_target_temp() - int(self.on):
|
||||||
|
self.heater_on(100)
|
||||||
|
elif self.get_temp() >= self.get_target_temp() - int(self.off):
|
||||||
|
self.heater_off()
|
||||||
|
else:
|
||||||
|
self.heater_off()
|
||||||
|
self.sleep(1)
|
||||||
|
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import time
|
||||||
|
|
||||||
|
from modules import cbpi
|
||||||
|
from modules.core.hardware import ActorBase, SensorPassive, SensorActive
|
||||||
|
from modules.core.props import Property
|
||||||
|
|
||||||
|
try:
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
except Exception as e:
|
||||||
|
print e
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.actor
|
||||||
|
class GPIOSimple(ActorBase):
|
||||||
|
|
||||||
|
gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26])
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
GPIO.setup(int(self.gpio), GPIO.OUT)
|
||||||
|
GPIO.output(int(self.gpio), 0)
|
||||||
|
|
||||||
|
def on(self, power=0):
|
||||||
|
print "GPIO ON %s" % str(self.gpio)
|
||||||
|
GPIO.output(int(self.gpio), 1)
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
print "GPIO OFF"
|
||||||
|
GPIO.output(int(self.gpio), 0)
|
||||||
|
|
||||||
|
@cbpi.actor
|
||||||
|
class GPIOPWM(ActorBase):
|
||||||
|
|
||||||
|
gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26])
|
||||||
|
duty_cylce = Property.Number("Duty Cycle", configurable=True)
|
||||||
|
|
||||||
|
p = None
|
||||||
|
power = 100
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
GPIO.setup(int(self.gpio), GPIO.OUT)
|
||||||
|
GPIO.output(int(self.gpio), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def on(self, power=None):
|
||||||
|
if power is not None:
|
||||||
|
self.power = int(power)
|
||||||
|
|
||||||
|
if self.duty_cylce is None:
|
||||||
|
duty_cylce = 50
|
||||||
|
|
||||||
|
self.p = GPIO.PWM(int(self.gpio), int(self.duty_cylce))
|
||||||
|
self.p.start(int(self.power))
|
||||||
|
|
||||||
|
def set_power(self, power):
|
||||||
|
if power is not None:
|
||||||
|
self.power = int(power)
|
||||||
|
self.p.ChangeDutyCycle(self.power)
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
print "GPIO OFF"
|
||||||
|
self.p.stop()
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.actor
|
||||||
|
class RelayBoard(ActorBase):
|
||||||
|
|
||||||
|
gpio = Property.Select("GPIO", options=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26])
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
GPIO.setup(int(self.gpio), GPIO.OUT)
|
||||||
|
GPIO.output(int(self.gpio), 1)
|
||||||
|
|
||||||
|
def on(self, power=0):
|
||||||
|
|
||||||
|
GPIO.output(int(self.gpio), 0)
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
|
||||||
|
GPIO.output(int(self.gpio), 1)
|
||||||
|
|
||||||
|
@cbpi.actor
|
||||||
|
class Dummy(ActorBase):
|
||||||
|
|
||||||
|
def on(self, power=100):
|
||||||
|
print "ON"
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
print "OFF"
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from modules import cbpi
|
||||||
|
from modules.core.controller import KettleController
|
||||||
|
from modules.core.props import Property
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.controller
|
||||||
|
class Hysteresis(KettleController):
|
||||||
|
|
||||||
|
# Custom Properties
|
||||||
|
|
||||||
|
on = Property.Number("Offset On", True, 0)
|
||||||
|
off = Property.Number("Offset Off", True, 0)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
'''
|
||||||
|
Invoked when the automatic is stopped.
|
||||||
|
Normally you switch off the actors and clean up everything
|
||||||
|
:return: None
|
||||||
|
'''
|
||||||
|
super(KettleController, self).stop()
|
||||||
|
self.heater_off()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
'''
|
||||||
|
Each controller is exectuted in its own thread. The run method is the entry point
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
while self.is_running():
|
||||||
|
if self.get_temp() < self.get_target_temp() - int(self.on):
|
||||||
|
self.heater_on(100)
|
||||||
|
elif self.get_temp() >= self.get_target_temp() - int(self.off):
|
||||||
|
self.heater_off()
|
||||||
|
else:
|
||||||
|
self.heater_off()
|
||||||
|
self.sleep(1)
|
||||||
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
|
from subprocess import Popen, PIPE, call
|
||||||
|
|
||||||
|
from modules import cbpi, app
|
||||||
|
from modules.core.hardware import SensorPassive
|
||||||
|
import json
|
||||||
|
import os, re, threading, time
|
||||||
|
from flask import Blueprint, render_template, request
|
||||||
|
from modules.core.props import Property
|
||||||
|
|
||||||
|
blueprint = Blueprint('one_wire', __name__)
|
||||||
|
|
||||||
|
temp = 22
|
||||||
|
|
||||||
|
def getSensors():
|
||||||
|
try:
|
||||||
|
arr = []
|
||||||
|
for dirname in os.listdir('/sys/bus/w1/devices'):
|
||||||
|
if (dirname.startswith("28") or dirname.startswith("10")):
|
||||||
|
arr.append(dirname)
|
||||||
|
return arr
|
||||||
|
except:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class myThread (threading.Thread):
|
||||||
|
|
||||||
|
value = 0
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, sensor_name):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.value = 0
|
||||||
|
self.sensor_name = sensor_name
|
||||||
|
self.runnig = True
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.runnig = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
while self.runnig:
|
||||||
|
try:
|
||||||
|
app.logger.info("READ TEMP")
|
||||||
|
## Test Mode
|
||||||
|
if self.sensor_name is None:
|
||||||
|
return
|
||||||
|
with open('/sys/bus/w1/devices/w1_bus_master1/%s/w1_slave' % self.sensor_name, 'r') as content_file:
|
||||||
|
content = content_file.read()
|
||||||
|
if (content.split('\n')[0].split(' ')[11] == "YES"):
|
||||||
|
temp = float(content.split("=")[-1]) / 1000 # temp in Celcius
|
||||||
|
self.value = temp
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
time.sleep(4)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.sensor
|
||||||
|
class ONE_WIRE_SENSOR(SensorPassive):
|
||||||
|
|
||||||
|
sensor_name = Property.Select("Sensor", getSensors())
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
|
||||||
|
self.t = myThread(self.sensor_name)
|
||||||
|
|
||||||
|
def shudown():
|
||||||
|
shudown.cb.shutdown()
|
||||||
|
shudown.cb = self.t
|
||||||
|
|
||||||
|
self.t.start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
try:
|
||||||
|
self.t.stop()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
if self.get_config_parameter("unit", "C") == "C":
|
||||||
|
self.data_received(self.t.value)
|
||||||
|
else:
|
||||||
|
self.data_received(format(9.0 / 5.0 * self.t.value + 32, '.2f'))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_global(self):
|
||||||
|
try:
|
||||||
|
call(["modprobe", "w1-gpio"])
|
||||||
|
call(["modprobe", "w1-therm"])
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/<int:t>', methods=['GET'])
|
||||||
|
def set_temp(t):
|
||||||
|
global temp
|
||||||
|
temp = t
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.initalizer()
|
||||||
|
def init(cbpi):
|
||||||
|
cbpi.app.logger.info("INITIALIZE ONE WIRE MODULE")
|
||||||
|
cbpi.app.register_blueprint(blueprint, url_prefix='/api/one_wire')
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import time
|
||||||
|
from thread import start_new_thread
|
||||||
|
from modules import cbpi
|
||||||
|
|
||||||
|
try:
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Buzzer(object):
|
||||||
|
|
||||||
|
sound = ["H", 0.1, "L"]
|
||||||
|
def __init__(self, gpio):
|
||||||
|
try:
|
||||||
|
cbpi.app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
|
||||||
|
self.gpio = gpio
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setup(gpio, GPIO.OUT)
|
||||||
|
self.state = True
|
||||||
|
cbpi.app.logger.info("BUZZER SETUP OK")
|
||||||
|
except Exception as e:
|
||||||
|
self.state = False
|
||||||
|
|
||||||
|
def beep(self):
|
||||||
|
if self.state is False:
|
||||||
|
cbpi.app.logger.error("BUZZER not working")
|
||||||
|
return
|
||||||
|
|
||||||
|
def play(sound):
|
||||||
|
try:
|
||||||
|
for i in sound:
|
||||||
|
if (isinstance(i, str)):
|
||||||
|
if i == "H":
|
||||||
|
GPIO.output(int(self.gpio), GPIO.HIGH)
|
||||||
|
else:
|
||||||
|
GPIO.output(int(self.gpio), GPIO.LOW)
|
||||||
|
else:
|
||||||
|
time.sleep(i)
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
start_new_thread(play, (self.sound,))
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=0)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INIT BUZZER"
|
||||||
|
cbpi.app.logger.info("INIT BUZZER")
|
||||||
|
gpio = cbpi.get_config_parameter("buzzer", 16)
|
||||||
|
cbpi.buzzer = Buzzer(gpio)
|
||||||
|
cbpi.beep()
|
||||||
|
cbpi.app.logger.info("INIT OK")
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from flask import json, request
|
||||||
|
from flask_classy import route
|
||||||
|
from modules import DBModel, cbpi, get_db
|
||||||
|
from modules.core.baseview import BaseView
|
||||||
|
|
||||||
|
class Config(DBModel):
|
||||||
|
__fields__ = ["type", "value", "description", "options"]
|
||||||
|
__table_name__ = "config"
|
||||||
|
__json_fields__ = ["options"]
|
||||||
|
__priamry_key__ = "name"
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigView(BaseView):
|
||||||
|
model = Config
|
||||||
|
cache_key = "config"
|
||||||
|
|
||||||
|
@route('/<name>', methods=["PUT"])
|
||||||
|
def put(self, name):
|
||||||
|
|
||||||
|
data = request.json
|
||||||
|
data["name"] = name
|
||||||
|
update_data = {"name": data["name"], "value": data["value"]}
|
||||||
|
|
||||||
|
if self.api.cache.get(self.cache_key) is not None:
|
||||||
|
#self.pre_post_callback(self.api.cache.get(self.cache_key)[name])
|
||||||
|
self.api.cache.get(self.cache_key)[name].__dict__.update(**update_data)
|
||||||
|
m = self.model.update(**self.api.cache.get(self.cache_key)[name].__dict__)
|
||||||
|
self.post_put_callback(self.api.cache.get(self.cache_key)[name])
|
||||||
|
return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__)
|
||||||
|
|
||||||
|
@route('/<id>', methods=["GET"])
|
||||||
|
def getOne(self, id):
|
||||||
|
return ('NOT SUPPORTED', 400)
|
||||||
|
|
||||||
|
@route('/<id>', methods=["DELETE"])
|
||||||
|
def delete(self, id):
|
||||||
|
return ('NOT SUPPORTED', 400)
|
||||||
|
|
||||||
|
@route('/', methods=["POST"])
|
||||||
|
def post(self):
|
||||||
|
return ('NOT SUPPORTED', 400)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_cache(cls):
|
||||||
|
|
||||||
|
with cls.api.app.app_context():
|
||||||
|
cls.api.cache[cls.cache_key] = {}
|
||||||
|
for key, value in cls.model.get_all().iteritems():
|
||||||
|
cls.post_init_callback(value)
|
||||||
|
cls.api.cache[cls.cache_key][value.name] = value
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=1)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE CONFIG MODULE"
|
||||||
|
ConfigView.register(cbpi.app, route_base='/api/config')
|
||||||
|
ConfigView.init_cache()
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
from flask import request, json
|
||||||
|
from flask_classy import route, FlaskView
|
||||||
|
from modules import cbpi
|
||||||
|
|
||||||
|
|
||||||
|
class BaseView(FlaskView):
|
||||||
|
|
||||||
|
as_array = False
|
||||||
|
cache_key = None
|
||||||
|
api = cbpi
|
||||||
|
|
||||||
|
@route('/<int:id>')
|
||||||
|
def getOne(self, id):
|
||||||
|
|
||||||
|
if self.api.cache.get(self.cache_key) is not None:
|
||||||
|
return json.dumps(self.api.cache.get(self.cache_key).get(id))
|
||||||
|
else:
|
||||||
|
return json.dumps(self.model.get_one(id))
|
||||||
|
|
||||||
|
@route('/')
|
||||||
|
def getAll(self):
|
||||||
|
if self.api.cache.get(self.cache_key) is not None:
|
||||||
|
return json.dumps(self.api.cache.get(self.cache_key))
|
||||||
|
else:
|
||||||
|
return json.dumps(self.model.get_all())
|
||||||
|
|
||||||
|
def pre_post_callback(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def post_post_callback(self, m):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@route('/', methods=["POST"])
|
||||||
|
def post(self):
|
||||||
|
data = request.json
|
||||||
|
self.pre_post_callback(data)
|
||||||
|
m = self.model.insert(**data)
|
||||||
|
if self.api.cache.get(self.cache_key) is not None:
|
||||||
|
self.api.cache.get(self.cache_key)[m.id] = m
|
||||||
|
|
||||||
|
self.post_post_callback(m)
|
||||||
|
|
||||||
|
return json.dumps(m)
|
||||||
|
|
||||||
|
def pre_put_callback(self, m):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_put_callback(self, m):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@route('/<int:id>', methods=["PUT"])
|
||||||
|
def put(self, id):
|
||||||
|
data = request.json
|
||||||
|
data["id"] = id
|
||||||
|
try:
|
||||||
|
del data["instance"]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
if self.api.cache.get(self.cache_key) is not None:
|
||||||
|
self.pre_put_callback(self.api.cache.get(self.cache_key)[id])
|
||||||
|
self.api.cache.get(self.cache_key)[id].__dict__.update(**data)
|
||||||
|
m = self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
|
||||||
|
self.post_put_callback(self.api.cache.get(self.cache_key)[id])
|
||||||
|
return json.dumps(self.api.cache.get(self.cache_key)[id])
|
||||||
|
else:
|
||||||
|
m = self.model.update(**data)
|
||||||
|
|
||||||
|
self.post_put_callback(m)
|
||||||
|
return json.dumps(m)
|
||||||
|
|
||||||
|
|
||||||
|
def pre_delete_callback(self, m):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def post_delete_callback(self, id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@route('/<int:id>', methods=["DELETE"])
|
||||||
|
def delete(self, id):
|
||||||
|
if self.api.cache.get(self.cache_key) is not None:
|
||||||
|
self.pre_delete_callback(self.api.cache.get(self.cache_key)[id])
|
||||||
|
del self.api.cache.get(self.cache_key)[id]
|
||||||
|
m = self.model.delete(id)
|
||||||
|
|
||||||
|
def post_delete_callback(self, id):
|
||||||
|
pass
|
||||||
|
return ('',204)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_init_callback(cls, obj):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_cache(cls):
|
||||||
|
with cls.api.app.app_context():
|
||||||
|
|
||||||
|
if cls.model.__as_array__ is True:
|
||||||
|
cls.api.cache[cls.cache_key] = []
|
||||||
|
|
||||||
|
for value in cls.model.get_all():
|
||||||
|
cls.post_init_callback(value)
|
||||||
|
cls.api.cache[cls.cache_key].append(value)
|
||||||
|
else:
|
||||||
|
cls.api.cache[cls.cache_key] = {}
|
||||||
|
for key, value in cls.model.get_all().iteritems():
|
||||||
|
cls.post_init_callback(value)
|
||||||
|
cls.api.cache[cls.cache_key][key] = value
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
from modules import cbpi
|
||||||
|
|
||||||
|
|
||||||
|
class ActorController(object):
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def actor_on(self, power=100, id=None):
|
||||||
|
|
||||||
|
if id is None:
|
||||||
|
id = self.heater
|
||||||
|
self.api.switch_actor_on(int(id), power=power)
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def actor_off(self, id=None):
|
||||||
|
if id is None:
|
||||||
|
id = self.heater
|
||||||
|
|
||||||
|
self.api.switch_actor_off(int(id))
|
||||||
|
|
||||||
|
|
||||||
|
class SensorController(object):
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_sensor_value(self, id=None):
|
||||||
|
|
||||||
|
if id is None:
|
||||||
|
id = self.sensor
|
||||||
|
|
||||||
|
return cbpi.get_sensor_value(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControllerBase(object):
|
||||||
|
__dirty = False
|
||||||
|
__running = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def init_global():
|
||||||
|
print "GLOBAL CONTROLLER INIT"
|
||||||
|
|
||||||
|
def notify(self, headline, message, type="success", timeout=5000):
|
||||||
|
self.api.notify(headline, message, type, timeout)
|
||||||
|
|
||||||
|
def is_running(self):
|
||||||
|
return self.__running
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.__running = True
|
||||||
|
|
||||||
|
|
||||||
|
def sleep(self, seconds):
|
||||||
|
self.api.socketio.sleep(seconds)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.__running = False
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
for a in kwds:
|
||||||
|
super(ControllerBase, self).__setattr__(a, kwds.get(a))
|
||||||
|
self.api = kwds.get("api")
|
||||||
|
self.heater = kwds.get("heater")
|
||||||
|
self.sensor = kwds.get("sensor")
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class KettleController(ControllerBase, ActorController, SensorController):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
ControllerBase.__init__(self, *args, **kwds)
|
||||||
|
self.kettle_id = kwds.get("kettle_id")
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def heater_on(self, power=100):
|
||||||
|
k = self.api.cache.get("kettle").get(self.kettle_id)
|
||||||
|
if k.heater is not None:
|
||||||
|
self.actor_on(power, int(k.heater))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def heater_off(self):
|
||||||
|
k = self.api.cache.get("kettle").get(self.kettle_id)
|
||||||
|
if k.heater is not None:
|
||||||
|
self.actor_off(int(k.heater))
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_temp(self, id=None):
|
||||||
|
if id is None:
|
||||||
|
id = self.kettle_id
|
||||||
|
return self.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor))
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_target_temp(self, id=None):
|
||||||
|
if id is None:
|
||||||
|
id = self.kettle_id
|
||||||
|
return self.api.cache.get("kettle").get(id).target_temp
|
||||||
|
|
||||||
|
class FermenterController(ControllerBase, ActorController, SensorController):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
ControllerBase.__init__(self, *args, **kwds)
|
||||||
|
self.fermenter_id = kwds.get("fermenter_id")
|
||||||
|
self.cooler = kwds.get("cooler")
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_target_temp(self, id=None):
|
||||||
|
|
||||||
|
if id is None:
|
||||||
|
id = self.fermenter_id
|
||||||
|
return self.api.cache.get("fermenter").get(id).target_temp
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def heater_on(self, power=100):
|
||||||
|
f = self.api.cache.get("fermenter").get(self.fermenter_id)
|
||||||
|
if k.heater is not None:
|
||||||
|
self.actor_on(int(f.heater))
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def heater_off(self):
|
||||||
|
f = self.api.cache.get("fermenter").get(self.fermenter_id)
|
||||||
|
if k.heater is not None:
|
||||||
|
self.actor_off(int(f.heater))
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_temp(self, id=None):
|
||||||
|
|
||||||
|
if id is None:
|
||||||
|
id = self.fermenter_id
|
||||||
|
return self.get_sensor_value(int(self.api.cache.get("fermenter").get(id).sensor))
|
||||||
|
|
||||||
@@ -0,0 +1,452 @@
|
|||||||
|
import pprint
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
from flask import make_response, g
|
||||||
|
import datetime
|
||||||
|
from datetime import datetime
|
||||||
|
from flask.views import MethodView
|
||||||
|
from flask_classy import FlaskView, route
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from time import localtime, strftime
|
||||||
|
from functools import wraps, update_wrapper
|
||||||
|
from props import *
|
||||||
|
|
||||||
|
from hardware import *
|
||||||
|
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationAPI(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ActorAPI(object):
|
||||||
|
|
||||||
|
def init_actors(self):
|
||||||
|
self.app.logger.info("Init Actors")
|
||||||
|
t = self.cache.get("actor_types")
|
||||||
|
for key, value in t.iteritems():
|
||||||
|
value.get("class").init_global()
|
||||||
|
|
||||||
|
for key in self.cache.get("actors"):
|
||||||
|
self.init_actor(key)
|
||||||
|
|
||||||
|
def init_actor(self, id):
|
||||||
|
try:
|
||||||
|
value = self.cache.get("actors").get(int(id))
|
||||||
|
cfg = value.config.copy()
|
||||||
|
cfg.update(dict(api=self, id=id, name=value.name))
|
||||||
|
cfg.update(dict(api=self, id=id, name=value.name))
|
||||||
|
clazz = self.cache.get("actor_types").get(value.type).get("class")
|
||||||
|
value.instance = clazz(**cfg)
|
||||||
|
value.instance.init()
|
||||||
|
value.state = 0
|
||||||
|
value.power = 100
|
||||||
|
except Exception as e:
|
||||||
|
self.notify("Actor Error", "Failed to setup actor %s. Please check the configuraiton" % value.name,
|
||||||
|
type="danger", timeout=None)
|
||||||
|
self.app.logger.error("Initializing of Actor %s failed" % id)
|
||||||
|
|
||||||
|
def switch_actor_on(self, id, power=None):
|
||||||
|
actor = self.cache.get("actors").get(id)
|
||||||
|
|
||||||
|
if actor.state == 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
actor.instance.on(power=power)
|
||||||
|
actor.state = 1
|
||||||
|
if power is not None:
|
||||||
|
|
||||||
|
actor.power = power
|
||||||
|
self.emit("SWITCH_ACTOR", actor)
|
||||||
|
|
||||||
|
def actor_power(self, id, power=100):
|
||||||
|
actor = self.cache.get("actors").get(id)
|
||||||
|
actor.instance.set_power(power=power)
|
||||||
|
actor.power = power
|
||||||
|
self.emit("SWITCH_ACTOR", actor)
|
||||||
|
|
||||||
|
def switch_actor_off(self, id):
|
||||||
|
actor = self.cache.get("actors").get(id)
|
||||||
|
|
||||||
|
if actor.state == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
actor.instance.off()
|
||||||
|
actor.state = 0
|
||||||
|
|
||||||
|
|
||||||
|
self.emit("SWITCH_ACTOR", actor)
|
||||||
|
|
||||||
|
class SensorAPI(object):
|
||||||
|
|
||||||
|
def init_sensors(self):
|
||||||
|
'''
|
||||||
|
Initialize all sensors
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
|
||||||
|
self.app.logger.info("Init Sensors")
|
||||||
|
|
||||||
|
t = self.cache.get("sensor_types")
|
||||||
|
for key, value in t.iteritems():
|
||||||
|
value.get("class").init_global()
|
||||||
|
|
||||||
|
for key in self.cache.get("sensors"):
|
||||||
|
self.init_sensor(key)
|
||||||
|
|
||||||
|
def stop_sensor(self, id):
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cache.get("sensors").get(id).instance.stop()
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
self.app.logger.info("Stop Sensor Error")
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def init_sensor(self, id):
|
||||||
|
'''
|
||||||
|
initialize sensor by id
|
||||||
|
:param id:
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
|
||||||
|
def start_active_sensor(instance):
|
||||||
|
'''
|
||||||
|
start active sensors as background job
|
||||||
|
:param instance:
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
instance.execute()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if id in self.cache.get("sensor_instances"):
|
||||||
|
self.cache.get("sensor_instances").get(id).stop()
|
||||||
|
value = self.cache.get("sensors").get(id)
|
||||||
|
|
||||||
|
cfg = value.config.copy()
|
||||||
|
cfg.update(dict(api=self, id=id, name=value.name))
|
||||||
|
clazz = self.cache.get("sensor_types").get(value.type).get("class")
|
||||||
|
value.instance = clazz(**cfg)
|
||||||
|
value.instance.init()
|
||||||
|
if isinstance(value.instance, SensorPassive):
|
||||||
|
# Passive Sensors
|
||||||
|
value.mode = "P"
|
||||||
|
else:
|
||||||
|
# Active Sensors
|
||||||
|
value.mode = "A"
|
||||||
|
t = self.socketio.start_background_task(target=start_active_sensor, instance=value.instance)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
self.notify("Sensor Error", "Failed to setup Sensor %s. Please check the configuraiton" % value.name, type="danger", timeout=None)
|
||||||
|
self.app.logger.error("Initializing of Sensor %s failed" % id)
|
||||||
|
|
||||||
|
def receive_sensor_value(self, id, value):
|
||||||
|
self.emit("SENSOR_UPDATE", self.cache.get("sensors")[id])
|
||||||
|
self.save_to_file(id, value)
|
||||||
|
|
||||||
|
def save_to_file(self, id, value, prefix="sensor"):
|
||||||
|
filename = "./logs/%s_%s.log" % (prefix, str(id))
|
||||||
|
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
||||||
|
msg = str(formatted_time) + "," +str(value) + "\n"
|
||||||
|
|
||||||
|
with open(filename, "a") as file:
|
||||||
|
file.write(msg)
|
||||||
|
|
||||||
|
def log_action(self, text):
|
||||||
|
filename = "./logs/action.log"
|
||||||
|
formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime())
|
||||||
|
|
||||||
|
|
||||||
|
with open(filename, "a") as file:
|
||||||
|
file.write("%s,%s\n" % (formatted_time, text))
|
||||||
|
|
||||||
|
def shutdown_sensor(self, id):
|
||||||
|
self.cache.get("sensors")[id].stop()
|
||||||
|
|
||||||
|
|
||||||
|
def get_sensor_value(self, id):
|
||||||
|
try:
|
||||||
|
id = int(id)
|
||||||
|
return int(self.cache.get("sensors")[id].instance.last_value)
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
class CacheAPI(object):
|
||||||
|
|
||||||
|
def get_sensor(self, id):
|
||||||
|
try:
|
||||||
|
return self.cache["sensors"][id]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_actor(self, id):
|
||||||
|
try:
|
||||||
|
return self.cache["actors"][id]
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
class CraftBeerPi(ActorAPI, SensorAPI):
|
||||||
|
|
||||||
|
cache = {
|
||||||
|
"init": {},
|
||||||
|
"config": {},
|
||||||
|
"actor_types": {},
|
||||||
|
"sensor_types": {},
|
||||||
|
"sensors": {},
|
||||||
|
"sensor_instances": {},
|
||||||
|
"init": [],
|
||||||
|
"background":[],
|
||||||
|
"step_types": {},
|
||||||
|
"controller_types": {},
|
||||||
|
"messages": [],
|
||||||
|
"plugins": {},
|
||||||
|
"fermentation_controller_types": {},
|
||||||
|
"fermenter_task": {}
|
||||||
|
}
|
||||||
|
buzzer = None
|
||||||
|
eventbus = {}
|
||||||
|
|
||||||
|
|
||||||
|
# constructor
|
||||||
|
def __init__(self, app, socketio):
|
||||||
|
self.app = app
|
||||||
|
self.socketio = socketio
|
||||||
|
|
||||||
|
|
||||||
|
def emit(self, key, data):
|
||||||
|
self.socketio.emit(key, data, namespace='/brew')
|
||||||
|
|
||||||
|
def notify(self, headline, message, type="success", timeout=5000):
|
||||||
|
self.beep()
|
||||||
|
msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
|
||||||
|
if timeout is None:
|
||||||
|
self.cache["messages"].append(msg)
|
||||||
|
self.emit("NOTIFY", msg)
|
||||||
|
|
||||||
|
def beep(self):
|
||||||
|
|
||||||
|
if self.buzzer is not None:
|
||||||
|
self.buzzer.beep()
|
||||||
|
|
||||||
|
|
||||||
|
def add_cache_callback(self, key, method):
|
||||||
|
method.callback = True
|
||||||
|
self.cache[key] = method
|
||||||
|
|
||||||
|
def get_config_parameter(self, key, default):
|
||||||
|
cfg = self.cache.get("config").get(key)
|
||||||
|
|
||||||
|
if cfg is None:
|
||||||
|
return default
|
||||||
|
else:
|
||||||
|
return cfg.value
|
||||||
|
|
||||||
|
def clear_cache(self, key, is_array=False):
|
||||||
|
if is_array:
|
||||||
|
self.cache[key] = []
|
||||||
|
else:
|
||||||
|
self.cache[key] = {}
|
||||||
|
|
||||||
|
# helper method for parsing props
|
||||||
|
def __parseProps(self, key, cls):
|
||||||
|
name = cls.__name__
|
||||||
|
self.cache[key][name] = {"name": name, "class": cls, "properties": []}
|
||||||
|
tmpObj = cls()
|
||||||
|
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
|
||||||
|
for m in members:
|
||||||
|
if isinstance(tmpObj.__getattribute__(m), Property.Number):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append(
|
||||||
|
{"name": m, "label": t.label, "type": "number", "configurable": t.configurable})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), Property.Text):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append(
|
||||||
|
{"name": m, "label": t.label, "type": "text", "configurable": t.configurable})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), Property.Select):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append(
|
||||||
|
{"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options})
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
def actor(self, cls):
|
||||||
|
return self.__parseProps("actor_types", cls)
|
||||||
|
|
||||||
|
def sensor(self, cls):
|
||||||
|
return self.__parseProps("sensor_types", cls)
|
||||||
|
|
||||||
|
def controller(self, cls):
|
||||||
|
return self.__parseProps("controller_types", cls)
|
||||||
|
|
||||||
|
def fermentation_controller(self, cls):
|
||||||
|
return self.__parseProps("fermentation_controller_types", cls)
|
||||||
|
|
||||||
|
def get_controller(self, name):
|
||||||
|
return self.cache["controller_types"].get(name)
|
||||||
|
|
||||||
|
def get_fermentation_controller(self, name):
|
||||||
|
return self.cache["fermentation_controller_types"].get(name)
|
||||||
|
# Step action
|
||||||
|
def action(self,label):
|
||||||
|
def real_decorator(func):
|
||||||
|
func.action = True
|
||||||
|
func.label = label
|
||||||
|
return func
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
# step decorator
|
||||||
|
def step(self, cls):
|
||||||
|
|
||||||
|
key = "step_types"
|
||||||
|
name = cls.__name__
|
||||||
|
self.cache[key][name] = {"name": name, "class": cls, "properties": [], "actions": []}
|
||||||
|
|
||||||
|
tmpObj = cls()
|
||||||
|
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
|
||||||
|
for m in members:
|
||||||
|
if isinstance(tmpObj.__getattribute__(m), StepProperty.Number):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Text):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "text", "configurable": t.configurable})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "select", "options": t.options})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Actor):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Sensor):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable})
|
||||||
|
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Kettle):
|
||||||
|
t = tmpObj.__getattribute__(m)
|
||||||
|
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable})
|
||||||
|
|
||||||
|
for name, method in cls.__dict__.iteritems():
|
||||||
|
if hasattr(method, "action"):
|
||||||
|
label = method.__getattribute__("label")
|
||||||
|
self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
|
||||||
|
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
# Event Bus
|
||||||
|
def event(self, name, async=False):
|
||||||
|
|
||||||
|
def real_decorator(function):
|
||||||
|
if self.eventbus.get(name) is None:
|
||||||
|
self.eventbus[name] = []
|
||||||
|
self.eventbus[name].append({"function": function, "async": async})
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return function(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
def emit_message(self, message):
|
||||||
|
self.emit_event(name="MESSAGE", message=message)
|
||||||
|
|
||||||
|
def emit_event(self, name, **kwargs):
|
||||||
|
for i in self.eventbus.get(name, []):
|
||||||
|
if i["async"] is False:
|
||||||
|
i["function"](**kwargs)
|
||||||
|
else:
|
||||||
|
t = self.socketio.start_background_task(target=i["function"], **kwargs)
|
||||||
|
|
||||||
|
# initializer decorator
|
||||||
|
def initalizer(self, order=0):
|
||||||
|
def real_decorator(function):
|
||||||
|
self.cache["init"].append({"function": function, "order": order})
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return function(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def try_catch(self, errorResult="ERROR"):
|
||||||
|
def real_decorator(function):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return function(*args, **kwargs)
|
||||||
|
except:
|
||||||
|
self.app.logger.error("Exception in function %s. Return default %s" % (function.__name__, errorResult))
|
||||||
|
return errorResult
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
def nocache(self, view):
|
||||||
|
@wraps(view)
|
||||||
|
def no_cache(*args, **kwargs):
|
||||||
|
response = make_response(view(*args, **kwargs))
|
||||||
|
response.headers['Last-Modified'] = datetime.now()
|
||||||
|
response.headers[
|
||||||
|
'Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0, max-age=0'
|
||||||
|
response.headers['Pragma'] = 'no-cache'
|
||||||
|
response.headers['Expires'] = '-1'
|
||||||
|
return response
|
||||||
|
|
||||||
|
return update_wrapper(no_cache, view)
|
||||||
|
|
||||||
|
def init_kettle(self, id):
|
||||||
|
try:
|
||||||
|
value = self.cache.get("kettle").get(id)
|
||||||
|
value["state"] = False
|
||||||
|
except:
|
||||||
|
self.notify("Kettle Setup Faild", "Please check %s configuration" % value.name, type="danger", timeout=None)
|
||||||
|
self.app.logger.error("Initializing of Kettle %s failed" % id)
|
||||||
|
|
||||||
|
|
||||||
|
def run_init(self):
|
||||||
|
'''
|
||||||
|
call all initialziers after startup
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
self.app.logger.info("Invoke Init")
|
||||||
|
self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order'])
|
||||||
|
for i in self.cache.get("init"):
|
||||||
|
self.app.logger.info("-> %s " % i.get("function").__name__)
|
||||||
|
i.get("function")(self)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def backgroundtask(self, key, interval, config_parameter=None):
|
||||||
|
|
||||||
|
'''
|
||||||
|
Background Task Decorator
|
||||||
|
:param key:
|
||||||
|
:param interval:
|
||||||
|
:param config_parameter:
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
def real_decorator(function):
|
||||||
|
self.cache["background"].append({"function": function, "key": key, "interval": interval, "config_parameter": config_parameter})
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return function(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
return real_decorator
|
||||||
|
|
||||||
|
def run_background_processes(self):
|
||||||
|
'''
|
||||||
|
call all background task after startup
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
self.app.logger.info("Start Background")
|
||||||
|
|
||||||
|
def job(interval, method):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
method()
|
||||||
|
except Exception as e:
|
||||||
|
self.app.logger.error("Exception" + method.__name__ + ": " + str(e))
|
||||||
|
self.socketio.sleep(interval)
|
||||||
|
|
||||||
|
|
||||||
|
for value in self.cache.get("background"):
|
||||||
|
t = self.socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function"))
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from flask import json, g
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = getattr(g, '_database', None)
|
||||||
|
if db is None:
|
||||||
|
def dict_factory(cursor, row):
|
||||||
|
d = {}
|
||||||
|
for idx, col in enumerate(cursor.description):
|
||||||
|
d[col[0]] = row[idx]
|
||||||
|
return d
|
||||||
|
db = g._database = sqlite3.connect('craftbeerpi.db')
|
||||||
|
db.row_factory = dict_factory
|
||||||
|
return db
|
||||||
|
|
||||||
|
class DBModel(object):
|
||||||
|
|
||||||
|
__priamry_key__ = "id"
|
||||||
|
__as_array__ = False
|
||||||
|
__order_by__ = None
|
||||||
|
__json_fields__ = []
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
|
||||||
|
self.__setattr__(self.__priamry_key__, args.get(self.__priamry_key__))
|
||||||
|
for f in self.__fields__:
|
||||||
|
if f in self.__json_fields__:
|
||||||
|
if args.get(f) is not None:
|
||||||
|
|
||||||
|
if isinstance(args.get(f) , dict) or isinstance(args.get(f) , list) :
|
||||||
|
self.__setattr__(f, args.get(f))
|
||||||
|
else:
|
||||||
|
self.__setattr__(f, json.loads(args.get(f)))
|
||||||
|
else:
|
||||||
|
self.__setattr__(f, None)
|
||||||
|
else:
|
||||||
|
self.__setattr__(f, args.get(f))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all(cls):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
if cls.__order_by__ is not None:
|
||||||
|
cur.execute("SELECT * FROM %s ORDER BY '%s'" % (cls.__table_name__,cls.__order_by__))
|
||||||
|
else:
|
||||||
|
cur.execute("SELECT * FROM %s" % cls.__table_name__)
|
||||||
|
|
||||||
|
if cls.__as_array__ is True:
|
||||||
|
result = []
|
||||||
|
for r in cur.fetchall():
|
||||||
|
result.append( cls(r))
|
||||||
|
else:
|
||||||
|
result = {}
|
||||||
|
for r in cur.fetchall():
|
||||||
|
result[r.get(cls.__priamry_key__)] = cls(r)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_one(cls, id):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("SELECT * FROM %s WHERE %s = ?" % (cls.__table_name__, cls.__priamry_key__), (id,))
|
||||||
|
r = cur.fetchone()
|
||||||
|
if r is not None:
|
||||||
|
return cls(r)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete(cls, id):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("DELETE FROM %s WHERE %s = ? " % (cls.__table_name__, cls.__priamry_key__), (id,))
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def insert(cls, **kwargs):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
|
||||||
|
query = 'INSERT INTO %s (%s) VALUES (%s)' % (
|
||||||
|
cls.__table_name__,
|
||||||
|
', '.join("'%s'" % str(x) for x in cls.__fields__),
|
||||||
|
', '.join(['?'] * len(cls.__fields__)))
|
||||||
|
|
||||||
|
data = ()
|
||||||
|
for f in cls.__fields__:
|
||||||
|
if f in cls.__json_fields__:
|
||||||
|
data = data + (json.dumps(kwargs.get(f)),)
|
||||||
|
else:
|
||||||
|
data = data + (kwargs.get(f),)
|
||||||
|
|
||||||
|
cur.execute(query, data)
|
||||||
|
get_db().commit()
|
||||||
|
i = cur.lastrowid
|
||||||
|
kwargs["id"] = i
|
||||||
|
|
||||||
|
return cls(kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update(cls, **kwargs):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
query = 'UPDATE %s SET %s WHERE %s = ?' % (
|
||||||
|
cls.__table_name__,
|
||||||
|
', '.join("'%s' = ?" % str(x) for x in cls.__fields__),cls.__priamry_key__)
|
||||||
|
|
||||||
|
data = ()
|
||||||
|
for f in cls.__fields__:
|
||||||
|
if f in cls.__json_fields__:
|
||||||
|
data = data + (json.dumps(kwargs.get(f)),)
|
||||||
|
else:
|
||||||
|
data = data + (kwargs.get(f),)
|
||||||
|
|
||||||
|
data = data + (kwargs.get(cls.__priamry_key__),)
|
||||||
|
cur.execute(query, data)
|
||||||
|
get_db().commit()
|
||||||
|
return cls(kwargs)
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
class Base(object):
|
||||||
|
__dirty = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def init_global(cls):
|
||||||
|
print "GLOBAL ACTOR INIT"
|
||||||
|
|
||||||
|
def get_config_parameter(self, key, default_value):
|
||||||
|
return self.api.get_config_parameter(key, default_value)
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
print "INIT BASE"
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
print "STOP HARDWARE"
|
||||||
|
|
||||||
|
def update(self, **kwds):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
for a in kwds:
|
||||||
|
super(Base, self).__setattr__(a, kwds.get(a))
|
||||||
|
self.api = kwds.get("api")
|
||||||
|
self.id = kwds.get("id")
|
||||||
|
self.value = None
|
||||||
|
self.__dirty = False
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
|
||||||
|
if name != "_Base__dirty":
|
||||||
|
self.__dirty = True
|
||||||
|
super(Base, self).__setattr__(name, value)
|
||||||
|
else:
|
||||||
|
super(Base, self).__setattr__(name, value)
|
||||||
|
|
||||||
|
|
||||||
|
class SensorBase(Base):
|
||||||
|
|
||||||
|
last_value = 0
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
print "INIT Base SENSOR"
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
print "STOP SENSOR"
|
||||||
|
|
||||||
|
def data_received(self, data):
|
||||||
|
|
||||||
|
|
||||||
|
self.last_value = data
|
||||||
|
self.api.receive_sensor_value(self.id, data)
|
||||||
|
|
||||||
|
def get_unit(self):
|
||||||
|
if self.get_config_parameter("unit", "C") == "C":
|
||||||
|
return "°C"
|
||||||
|
else:
|
||||||
|
return "°F"
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
|
||||||
|
return {"value": self.last_value, "unit": self.get_unit()}
|
||||||
|
|
||||||
|
class SensorActive(SensorBase):
|
||||||
|
|
||||||
|
__running = False
|
||||||
|
|
||||||
|
def is_running(self):
|
||||||
|
return self.__running
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.__running = True
|
||||||
|
print "INIT ACTIVE SENSOR"
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.__running = False
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SensorPassive(SensorBase):
|
||||||
|
def init(self):
|
||||||
|
print "INIT PASSIV SENSOR"
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class ActorBase(Base):
|
||||||
|
|
||||||
|
def state(self):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def set_power(self, power):
|
||||||
|
print "SET POWER TO %s" % power
|
||||||
|
|
||||||
|
def on(self, power=0):
|
||||||
|
print "ON"
|
||||||
|
|
||||||
|
def off(self):
|
||||||
|
print "OFF"
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
class PropertyType(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Property(object):
|
||||||
|
class Select(PropertyType):
|
||||||
|
def __init__(self, label, options):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.options = options
|
||||||
|
|
||||||
|
class Number(PropertyType):
|
||||||
|
def __init__(self, label, configurable=False, default_value=0, unit=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = configurable
|
||||||
|
|
||||||
|
class Text(PropertyType):
|
||||||
|
def __init__(self, label, configurable=False, default_value=""):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = configurable
|
||||||
|
|
||||||
|
class StepProperty(Property):
|
||||||
|
class Actor(PropertyType):
|
||||||
|
def __init__(self, label):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = True
|
||||||
|
class Sensor(PropertyType):
|
||||||
|
def __init__(self, label):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = True
|
||||||
|
class Kettle(PropertyType):
|
||||||
|
def __init__(self, label):
|
||||||
|
PropertyType.__init__(self)
|
||||||
|
self.label = label
|
||||||
|
self.configurable = True
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
from modules import cbpi
|
||||||
|
from modules.core.props import StepProperty, Property
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationAPI(object):
|
||||||
|
def notify(self, headline, message, type="success", timeout=5000):
|
||||||
|
self.api.notify(headline, message, type, timeout)
|
||||||
|
|
||||||
|
class ActorAPI(NotificationAPI):
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def actor_on(self, id, power=100):
|
||||||
|
self.api.switch_actor_on(int(id), power=power)
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def actor_off(self, id):
|
||||||
|
self.api.switch_actor_off(int(id))
|
||||||
|
|
||||||
|
class SensorAPI(NotificationAPI):
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_sensor_value(self, id):
|
||||||
|
return cbpi.get_sensor_value(id)
|
||||||
|
|
||||||
|
class KettleAPI(NotificationAPI):
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_kettle_temp(self, id=None):
|
||||||
|
id = int(id)
|
||||||
|
if id is None:
|
||||||
|
id = self.kettle_id
|
||||||
|
return cbpi.get_sensor_value(int(self.api.cache.get("kettle").get(id).sensor))
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def get_target_temp(self, id=None):
|
||||||
|
id = int(id)
|
||||||
|
if id is None:
|
||||||
|
id = self.kettle_id
|
||||||
|
return self.api.cache.get("kettle").get(id).target_temp
|
||||||
|
|
||||||
|
def set_target_temp(self, temp, id=None):
|
||||||
|
temp = int(temp)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if id is None:
|
||||||
|
self.api.emit_event("SET_TARGET_TEMP", id=self.kettle_id, temp=temp)
|
||||||
|
else:
|
||||||
|
self.api.emit_event("SET_TARGET_TEMP", id=id, temp=temp)
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
self.notify("Faild to set Target Temp", "", type="warning")
|
||||||
|
|
||||||
|
class Timer(object):
|
||||||
|
timer_end = Property.Number("TIMER_END", configurable=False)
|
||||||
|
|
||||||
|
def start_timer(self, timer):
|
||||||
|
if self.timer_end is not None:
|
||||||
|
return
|
||||||
|
self.timer_end = int(time.time()) + timer
|
||||||
|
|
||||||
|
|
||||||
|
def stop_timer(self):
|
||||||
|
if self.timer_end is not None:
|
||||||
|
self.timer_end = None
|
||||||
|
|
||||||
|
def is_timer_running(self):
|
||||||
|
if self.timer_end is not None:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_timer_finished(self):
|
||||||
|
if self.timer_end is None:
|
||||||
|
return None
|
||||||
|
if self.timer_end <= int(time.time()):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI):
|
||||||
|
|
||||||
|
__dirty = False
|
||||||
|
managed_fields = []
|
||||||
|
n = False
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
self.n = True
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
print "INIT STEP"
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
print "FINSIH STEP"
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
print "REST STEP"
|
||||||
|
|
||||||
|
def execute(self):
|
||||||
|
print "-------------"
|
||||||
|
print "Step Info"
|
||||||
|
print "Kettle ID: %s" % self.kettle_id
|
||||||
|
print "ID: %s" % self.id
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
for a in kwds:
|
||||||
|
super(StepBase, self).__setattr__(a, kwds.get(a))
|
||||||
|
self.api = kwds.get("api")
|
||||||
|
self.id = kwds.get("id")
|
||||||
|
|
||||||
|
self.name = kwds.get("name")
|
||||||
|
self.kettle_id = kwds.get("kettleid")
|
||||||
|
self.value = None
|
||||||
|
self.__dirty = False
|
||||||
|
|
||||||
|
|
||||||
|
def is_dirty(self):
|
||||||
|
return self.__dirty
|
||||||
|
|
||||||
|
def reset_dirty(self):
|
||||||
|
self.__dirty = False
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
|
||||||
|
if name != "_StepBase__dirty" and name in self.managed_fields:
|
||||||
|
self.__dirty = True
|
||||||
|
super(StepBase, self).__setattr__(name, value)
|
||||||
|
else:
|
||||||
|
super(StepBase, self).__setattr__(name, value)
|
||||||
|
|
||||||
Executable
+306
@@ -0,0 +1,306 @@
|
|||||||
|
import time
|
||||||
|
from flask import request
|
||||||
|
from flask_classy import route
|
||||||
|
|
||||||
|
from modules import DBModel, cbpi, get_db
|
||||||
|
from modules.core.baseview import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class Fermenter(DBModel):
|
||||||
|
__fields__ = ["name", "brewname", "sensor", "sensor2", "sensor3", "heater", "cooler", "logic", "config", "target_temp"]
|
||||||
|
__table_name__ = "fermenter"
|
||||||
|
__json_fields__ = ["config"]
|
||||||
|
|
||||||
|
class FermenterStep(DBModel):
|
||||||
|
__fields__ = ["name", "days", "hours", "minutes", "temp", "direction", "order", "state", "start", "end", "timer_start", "fermenter_id"]
|
||||||
|
__table_name__ = "fermenter_step"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_fermenter_id(cls, id):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("SELECT * FROM %s WHERE fermenter_id = ?" % cls.__table_name__,(id,))
|
||||||
|
result = []
|
||||||
|
for r in cur.fetchall():
|
||||||
|
result.append(cls(r))
|
||||||
|
return result
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_max_order(cls,id):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("SELECT max(fermenter_step.'order') as 'order' FROM %s WHERE fermenter_id = ?" % cls.__table_name__, (id,))
|
||||||
|
r = cur.fetchone()
|
||||||
|
return r.get("order")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_state(cls, id, state):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id))
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_timer(cls, id, timer):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("UPDATE %s SET timer_start = ? WHERE id =?" % cls.__table_name__, (timer, id))
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_state(cls, state):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state)
|
||||||
|
r = cur.fetchone()
|
||||||
|
if r is not None:
|
||||||
|
return cls(r)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reset_all_steps(cls,id):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("UPDATE %s SET state = 'I', start = NULL, end = NULL, timer_start = NULL WHERE fermenter_id = ?" % cls.__table_name__, (id,))
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
class FermenterView(BaseView):
|
||||||
|
model = Fermenter
|
||||||
|
cache_key = "fermenter"
|
||||||
|
|
||||||
|
def post_post_callback(self, m):
|
||||||
|
m.state = False
|
||||||
|
m.steps = []
|
||||||
|
|
||||||
|
|
||||||
|
@route('/<int:id>/targettemp/<temp>', methods=['POST'])
|
||||||
|
def postTargetTemp(self, id, temp):
|
||||||
|
if temp is None or not temp:
|
||||||
|
return ('', 500)
|
||||||
|
id = int(id)
|
||||||
|
temp = float(temp)
|
||||||
|
cbpi.cache.get(self.cache_key)[id].target_temp = float(temp)
|
||||||
|
self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
|
||||||
|
cbpi.emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp})
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/<int:id>/brewname', methods=['POST'])
|
||||||
|
def postBrewName(self, id):
|
||||||
|
data = request.json
|
||||||
|
brewname = data.get("brewname")
|
||||||
|
cbpi.cache.get(self.cache_key)[id].brewname = brewname
|
||||||
|
self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
|
||||||
|
cbpi.emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname})
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_init_callback(cls, obj):
|
||||||
|
obj.steps = FermenterStep.get_by_fermenter_id(obj.id)
|
||||||
|
obj.state = False
|
||||||
|
|
||||||
|
@route('/<int:id>/step', methods=['POST'])
|
||||||
|
def postStep(self, id):
|
||||||
|
data = request.json
|
||||||
|
order_max = FermenterStep.get_max_order(id)
|
||||||
|
order = order_max + 1 if order_max is not None else 1
|
||||||
|
data["order"] = order
|
||||||
|
data["days"] = 0 if data["days"] == "" else data["days"]
|
||||||
|
data["hours"] = 0 if data["hours"] == "" else data["hours"]
|
||||||
|
data["minutes"] = 0 if data["minutes"] == "" else data["minutes"]
|
||||||
|
data["temp"] = 0 if data["temp"] == "" else data["temp"]
|
||||||
|
data["state"] = "I"
|
||||||
|
data["name"] = "NO NAME" if data["name"] == "" else data["name"]
|
||||||
|
f = FermenterStep.insert(**data)
|
||||||
|
|
||||||
|
cbpi.cache.get(self.cache_key)[id].steps.append(f)
|
||||||
|
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/<int:id>/step/<int:stepid>', methods=["PUT"])
|
||||||
|
def putStep(self, id, stepid):
|
||||||
|
data = request.json
|
||||||
|
# Select modal
|
||||||
|
|
||||||
|
data["id"] = stepid
|
||||||
|
data["fermenter_id"] = id
|
||||||
|
|
||||||
|
data["days"] = 0 if data["days"] == "" else data["days"]
|
||||||
|
data["hours"] = 0 if data["hours"] == "" else data["hours"]
|
||||||
|
data["minutes"] = 0 if data["minutes"] == "" else data["minutes"]
|
||||||
|
|
||||||
|
for s in cbpi.cache.get(self.cache_key)[id].steps:
|
||||||
|
if s.id == stepid:
|
||||||
|
s.__dict__.update(**data)
|
||||||
|
|
||||||
|
FermenterStep.update(**s.__dict__)
|
||||||
|
break
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/<int:id>/step/<int:stepid>', methods=["DELETE"])
|
||||||
|
def deleteStep(self, id, stepid):
|
||||||
|
|
||||||
|
for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
|
||||||
|
if s.id == stepid:
|
||||||
|
del cbpi.cache.get(self.cache_key)[id].steps[idx]
|
||||||
|
FermenterStep.delete(s.id)
|
||||||
|
break
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/<int:id>/start', methods=['POST'])
|
||||||
|
def start_fermentation(self, id):
|
||||||
|
active = None
|
||||||
|
for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
|
||||||
|
if s.state == 'A':
|
||||||
|
active = s
|
||||||
|
break
|
||||||
|
|
||||||
|
inactive = None
|
||||||
|
for idx, s in enumerate(cbpi.cache.get(self.cache_key)[id].steps):
|
||||||
|
if s.state == 'I':
|
||||||
|
inactive = s
|
||||||
|
break
|
||||||
|
|
||||||
|
if active is not None:
|
||||||
|
|
||||||
|
active.state = 'D'
|
||||||
|
active.end = time.time()
|
||||||
|
FermenterStep.update(**active.__dict__)
|
||||||
|
|
||||||
|
del cbpi.cache["fermenter_task"][id]
|
||||||
|
|
||||||
|
if inactive is not None:
|
||||||
|
fermenter = self.get_fermenter(inactive.fermenter_id)
|
||||||
|
current_temp = cbpi.get_sensor_value(int(fermenter.sensor))
|
||||||
|
|
||||||
|
inactive.state = 'A'
|
||||||
|
inactive.start = time.time()
|
||||||
|
inactive.direction = "C" if current_temp >= inactive.temp else "H"
|
||||||
|
FermenterStep.update(**inactive.__dict__)
|
||||||
|
|
||||||
|
self.postTargetTemp(id, inactive.temp)
|
||||||
|
|
||||||
|
cbpi.cache["fermenter_task"][id] = inactive
|
||||||
|
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/<int:id>/reset', methods=["POST"])
|
||||||
|
def reset(self, id):
|
||||||
|
FermenterStep.reset_all_steps(id)
|
||||||
|
|
||||||
|
cbpi.cache[self.cache_key][id].steps = FermenterStep.get_by_fermenter_id(id)
|
||||||
|
|
||||||
|
if id in cbpi.cache["fermenter_task"]:
|
||||||
|
del cbpi.cache["fermenter_task"][id]
|
||||||
|
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/<int:id>/automatic', methods=['POST'])
|
||||||
|
def toggle(self, id):
|
||||||
|
fermenter = cbpi.cache.get(self.cache_key)[id]
|
||||||
|
try:
|
||||||
|
|
||||||
|
if fermenter.state is False:
|
||||||
|
# Start controller
|
||||||
|
if fermenter.logic is not None:
|
||||||
|
cfg = fermenter.config.copy()
|
||||||
|
cfg.update(
|
||||||
|
dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor))
|
||||||
|
instance = cbpi.get_fermentation_controller(fermenter.logic).get("class")(**cfg)
|
||||||
|
instance.init()
|
||||||
|
fermenter.controller_instance = instance
|
||||||
|
|
||||||
|
def run(instance):
|
||||||
|
instance.run()
|
||||||
|
|
||||||
|
t = cbpi.socketio.start_background_task(target=run, instance=instance)
|
||||||
|
fermenter.state = not fermenter.state
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||||
|
else:
|
||||||
|
# Stop controller
|
||||||
|
fermenter.controller_instance.stop()
|
||||||
|
fermenter.state = not fermenter.state
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
|
||||||
|
cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name,
|
||||||
|
type="danger", timeout=None)
|
||||||
|
return ('', 500)
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def get_fermenter(self, id):
|
||||||
|
return cbpi.cache["fermenter"].get(id)
|
||||||
|
|
||||||
|
def target_temp_reached(self,id, step):
|
||||||
|
timestamp = time.time()
|
||||||
|
|
||||||
|
days = step.days * 24 * 60 * 60
|
||||||
|
hours = step.hours * 60 * 60
|
||||||
|
minutes = step.minutes * 60
|
||||||
|
target_time = days + hours + minutes + timestamp
|
||||||
|
|
||||||
|
FermenterStep.update_timer(step.id, target_time)
|
||||||
|
|
||||||
|
step.timer_start = target_time
|
||||||
|
|
||||||
|
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
|
||||||
|
|
||||||
|
def check_step(self):
|
||||||
|
for key, value in cbpi.cache["fermenter_task"].iteritems():
|
||||||
|
|
||||||
|
try:
|
||||||
|
fermenter = self.get_fermenter(key)
|
||||||
|
current_temp = current_temp = cbpi.get_sensor_value(int(fermenter.sensor))
|
||||||
|
|
||||||
|
if value.timer_start is None:
|
||||||
|
|
||||||
|
if value.direction == "H" :
|
||||||
|
|
||||||
|
if current_temp >= value.temp:
|
||||||
|
self.target_temp_reached(key,value)
|
||||||
|
else:
|
||||||
|
if current_temp <= value.temp:
|
||||||
|
self.target_temp_reached(key, value)
|
||||||
|
else:
|
||||||
|
if time.time() >= value.timer_start:
|
||||||
|
self.start_fermentation(key)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5)
|
||||||
|
def read_target_temps():
|
||||||
|
"""
|
||||||
|
background process that reads all passive sensors in interval of 1 second
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
for key, value in cbpi.cache.get("fermenter").iteritems():
|
||||||
|
cbpi.save_to_file(key, value.target_temp, prefix="fermenter")
|
||||||
|
|
||||||
|
|
||||||
|
instance = FermenterView()
|
||||||
|
|
||||||
|
@cbpi.backgroundtask(key="fermentation_task", interval=1)
|
||||||
|
def execute_fermentation_step():
|
||||||
|
with cbpi.app.app_context():
|
||||||
|
instance.check_step()
|
||||||
|
|
||||||
|
|
||||||
|
def init_active_steps():
|
||||||
|
'''
|
||||||
|
active_steps = FermenterStep.query.filter_by(state='A')
|
||||||
|
for a in active_steps:
|
||||||
|
db.session.expunge(a)
|
||||||
|
cbpi.cache["fermenter_task"][a.fermenter_id] = a
|
||||||
|
'''
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=1)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE CONFIG MODULE"
|
||||||
|
FermenterView.register(cbpi.app, route_base='/api/fermenter')
|
||||||
|
FermenterView.init_cache()
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
from flask import request
|
||||||
|
from flask_classy import FlaskView, route
|
||||||
|
from modules import cbpi, socketio
|
||||||
|
from modules.core.baseview import BaseView
|
||||||
|
from modules.core.db import DBModel
|
||||||
|
|
||||||
|
class Kettle(DBModel):
|
||||||
|
__fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"]
|
||||||
|
__table_name__ = "kettle"
|
||||||
|
__json_fields__ = ["config"]
|
||||||
|
|
||||||
|
|
||||||
|
class Kettle2View(BaseView):
|
||||||
|
model = Kettle
|
||||||
|
cache_key = "kettle"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def pre_post_callback(self, data):
|
||||||
|
data["target_temp"] = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def post_init_callback(cls, obj):
|
||||||
|
obj.state = False
|
||||||
|
|
||||||
|
|
||||||
|
def post_post_callback(self, m):
|
||||||
|
m.state = False
|
||||||
|
|
||||||
|
|
||||||
|
@route('/<int:id>/targettemp/<temp>', methods=['POST'])
|
||||||
|
def postTargetTemp(self, id, temp):
|
||||||
|
id = int(id)
|
||||||
|
temp = float(temp)
|
||||||
|
cbpi.cache.get("kettle")[id].target_temp = float(temp)
|
||||||
|
self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
|
||||||
|
cbpi.emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp})
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/<int:id>/automatic', methods=['POST'])
|
||||||
|
def toggle(self, id):
|
||||||
|
kettle = cbpi.cache.get("kettle")[id]
|
||||||
|
|
||||||
|
if kettle.state is False:
|
||||||
|
# Start controller
|
||||||
|
if kettle.logic is not None:
|
||||||
|
cfg = kettle.config.copy()
|
||||||
|
cfg.update(dict(api=cbpi, kettle_id=kettle.id, heater=kettle.heater, sensor=kettle.sensor))
|
||||||
|
instance = cbpi.get_controller(kettle.logic).get("class")(**cfg)
|
||||||
|
instance.init()
|
||||||
|
kettle.controller_instance = instance
|
||||||
|
def run(instance):
|
||||||
|
instance.run()
|
||||||
|
t = self.api.socketio.start_background_task(target=run, instance=instance)
|
||||||
|
kettle.state = not kettle.state
|
||||||
|
cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
|
||||||
|
else:
|
||||||
|
# Stop controller
|
||||||
|
kettle.controller_instance.stop()
|
||||||
|
kettle.state = not kettle.state
|
||||||
|
cbpi.emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@cbpi.event("SET_TARGET_TEMP")
|
||||||
|
def set_target_temp(id, temp):
|
||||||
|
'''
|
||||||
|
Change Taget Temp Event
|
||||||
|
:param id: kettle id
|
||||||
|
:param temp: target temp to set
|
||||||
|
:return: None
|
||||||
|
'''
|
||||||
|
print "GOT EVENT %s %s" % (id, temp)
|
||||||
|
Kettle2View().postTargetTemp(id,temp)
|
||||||
|
|
||||||
|
@cbpi.backgroundtask(key="read_target_temps", interval=5)
|
||||||
|
def read_target_temps():
|
||||||
|
"""
|
||||||
|
background process that reads all passive sensors in interval of 1 second
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
for key, value in cbpi.cache.get("kettle").iteritems():
|
||||||
|
cbpi.save_to_file(key, value.target_temp, prefix="kettle")
|
||||||
|
|
||||||
|
@cbpi.initalizer()
|
||||||
|
def init(cbpi):
|
||||||
|
Kettle2View.api = cbpi
|
||||||
|
Kettle2View.register(cbpi.app,route_base='/api/kettle')
|
||||||
|
Kettle2View.init_cache()
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
import endpoints
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
from flask import Blueprint, request, send_from_directory, json
|
||||||
|
from flask_classy import FlaskView, route
|
||||||
|
from modules import cbpi
|
||||||
|
|
||||||
|
|
||||||
|
class LogView(FlaskView):
|
||||||
|
|
||||||
|
@route('/', methods=['GET'])
|
||||||
|
def get_all_logfiles(self):
|
||||||
|
result = []
|
||||||
|
for filename in os.listdir("./logs"):
|
||||||
|
if filename.endswith(".log"):
|
||||||
|
result.append(filename)
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
@route('/actions')
|
||||||
|
def actions(self):
|
||||||
|
filename = "./logs/action.log"
|
||||||
|
if os.path.isfile(filename) == False:
|
||||||
|
return
|
||||||
|
import csv
|
||||||
|
array = []
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
for row in reader:
|
||||||
|
try:
|
||||||
|
array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, row[1]])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return json.dumps(array)
|
||||||
|
|
||||||
|
@route('/<file>', methods=["DELETE"])
|
||||||
|
def clearlog(self, file):
|
||||||
|
"""
|
||||||
|
Overload delete method to shutdown sensor before delete
|
||||||
|
:param id: sensor id
|
||||||
|
:return: HTTP 204
|
||||||
|
"""
|
||||||
|
if not self.check_filename(file):
|
||||||
|
return ('File Not Found', 404)
|
||||||
|
|
||||||
|
filename = "./logs/%s" % file
|
||||||
|
if os.path.isfile(filename) == True:
|
||||||
|
os.remove(filename)
|
||||||
|
cbpi.notify("log deleted succesfully", "")
|
||||||
|
else:
|
||||||
|
cbpi.notify("Failed to delete log", "", type="danger")
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def read_log_as_json(self, type, id):
|
||||||
|
filename = "./logs/%s_%s.log" % (type, id)
|
||||||
|
if os.path.isfile(filename) == False:
|
||||||
|
return
|
||||||
|
|
||||||
|
import csv
|
||||||
|
array = []
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
for row in reader:
|
||||||
|
try:
|
||||||
|
array.append([int((datetime.datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - datetime.datetime(1970, 1, 1)).total_seconds()) * 1000, float(row[1])])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return array
|
||||||
|
|
||||||
|
|
||||||
|
@route('/<t>/<int:id>', methods=["POST"])
|
||||||
|
def get_logs_as_json(self, t, id):
|
||||||
|
data = request.json
|
||||||
|
result = []
|
||||||
|
if t == "s":
|
||||||
|
name = cbpi.cache.get("sensors").get(id).name
|
||||||
|
result.append({"name": name, "data": self.read_log_as_json("sensor", id)})
|
||||||
|
|
||||||
|
if t == "k":
|
||||||
|
kettle = cbpi.cache.get("kettle").get(id)
|
||||||
|
result.append({"name": "Temp", "data": self.read_log_as_json("sensor", kettle.sensor)})
|
||||||
|
result.append({"name": "Target Temp", "data": self.read_log_as_json("kettle", kettle.id)})
|
||||||
|
|
||||||
|
if t == "f":
|
||||||
|
fermenter = cbpi.cache.get("fermenter").get(id)
|
||||||
|
result.append({"name": "Temp", "data": self.read_log_as_json("sensor", fermenter.sensor)})
|
||||||
|
result.append({"name": "Target Temp", "data": self.read_log_as_json("fermenter", fermenter.id)})
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
@route('/download/<file>')
|
||||||
|
@cbpi.nocache
|
||||||
|
def download(self, file):
|
||||||
|
if not self.check_filename(file):
|
||||||
|
return ('File Not Found', 404)
|
||||||
|
return send_from_directory('../logs', file, as_attachment=True, attachment_filename=file)
|
||||||
|
|
||||||
|
def check_filename(self, name):
|
||||||
|
import re
|
||||||
|
pattern = re.compile('^([A-Za-z0-9-_])+.log$')
|
||||||
|
|
||||||
|
return True if pattern.match(name) else False
|
||||||
|
|
||||||
|
@cbpi.initalizer()
|
||||||
|
def init(app):
|
||||||
|
"""
|
||||||
|
Initializer for the message module
|
||||||
|
:param app: the flask app
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
print "INITIALIZE LOG MODULE"
|
||||||
|
LogView.register(cbpi.app, route_base='/api/logs')
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import json
|
||||||
|
from flask_classy import FlaskView, route
|
||||||
|
from modules import cbpi
|
||||||
|
class NotificationView(FlaskView):
|
||||||
|
|
||||||
|
@route('/', methods=['GET'])
|
||||||
|
def getMessages(self):
|
||||||
|
"""
|
||||||
|
Get all messages
|
||||||
|
:return: current messages
|
||||||
|
"""
|
||||||
|
|
||||||
|
return json.dumps(cbpi.cache["messages"])
|
||||||
|
|
||||||
|
@route('/<id>', methods=['DELETE'])
|
||||||
|
def dismiss(self, id):
|
||||||
|
"""
|
||||||
|
Delete message from cache by id
|
||||||
|
:param id: message id to be deleted
|
||||||
|
:return: empty response HTTP 204
|
||||||
|
"""
|
||||||
|
for idx, m in enumerate(cbpi.cache.get("messages", [])):
|
||||||
|
if (m.get("id") == id):
|
||||||
|
cbpi.cache["messages"].pop(idx)
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@cbpi.event("MESSAGE", async=True)
|
||||||
|
def messageEvent(message, **kwargs):
|
||||||
|
"""
|
||||||
|
React on message event. add the message to the cache and push the message to the clients
|
||||||
|
:param message: the message
|
||||||
|
:param kwargs: other parameter
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
msg = {"id": len(cbpi.cache["messages"]), "type": "info", "message": message, "read": False}
|
||||||
|
cbpi.cache["messages"].append(msg)
|
||||||
|
cbpi.emit('MESSAGE', msg,)
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=2)
|
||||||
|
def init(cbpi):
|
||||||
|
"""
|
||||||
|
Initializer for the message module
|
||||||
|
:param app: the flask app
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
print "INITIALIZE MESSAGE MODULE"
|
||||||
|
|
||||||
|
msg = {"id": len(cbpi.cache["messages"]), "type": "info", "headline": "Support CraftBeerPi with your donation", "message": "You will find the PayPay Donation button in the system menu" , "read": False}
|
||||||
|
cbpi.cache["messages"].append(msg)
|
||||||
|
|
||||||
|
NotificationView.register(cbpi.app, route_base='/api/notification')
|
||||||
Executable
+43
@@ -0,0 +1,43 @@
|
|||||||
|
import time
|
||||||
|
from flask_classy import route
|
||||||
|
from modules import DBModel, cbpi
|
||||||
|
from modules.core.baseview import BaseView
|
||||||
|
|
||||||
|
class Sensor(DBModel):
|
||||||
|
__fields__ = ["name","type", "config", "hide"]
|
||||||
|
__table_name__ = "sensor"
|
||||||
|
__json_fields__ = ["config"]
|
||||||
|
|
||||||
|
class SensorView(BaseView):
|
||||||
|
model = Sensor
|
||||||
|
cache_key = "sensors"
|
||||||
|
|
||||||
|
|
||||||
|
def post_post_callback(self, m):
|
||||||
|
cbpi.init_sensor(m.id)
|
||||||
|
|
||||||
|
def post_put_callback(self, m):
|
||||||
|
cbpi.stop_sensor(m.id)
|
||||||
|
cbpi.init_sensor(m.id)
|
||||||
|
|
||||||
|
def pre_delete_callback(self, m):
|
||||||
|
cbpi.stop_sensor(m.id)
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=1000)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE SENSOR MODULE"
|
||||||
|
SensorView.register(cbpi.app, route_base='/api/sensor')
|
||||||
|
SensorView.init_cache()
|
||||||
|
cbpi.init_sensors()
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.backgroundtask(key="read_passiv_sensor", interval=5)
|
||||||
|
def read_passive_sensor():
|
||||||
|
"""
|
||||||
|
background process that reads all passive sensors in interval of 1 second
|
||||||
|
:return: None
|
||||||
|
|
||||||
|
"""
|
||||||
|
for key, value in cbpi.cache.get("sensors").iteritems():
|
||||||
|
if value.mode == "P":
|
||||||
|
value.instance.read()
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from modules import cbpi
|
||||||
|
|
||||||
|
|
||||||
|
def getserial():
|
||||||
|
cpuserial = "0000000000000000"
|
||||||
|
try:
|
||||||
|
f = open('/proc/cpuinfo','r')
|
||||||
|
for line in f:
|
||||||
|
if line[0:6]=='Serial':
|
||||||
|
cpuserial = line[10:26]
|
||||||
|
f.close()
|
||||||
|
except:
|
||||||
|
cpuserial = "0000000000000000"
|
||||||
|
return cpuserial
|
||||||
|
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=9999)
|
||||||
|
def sendStats(cbpi):
|
||||||
|
print "INITIALIZE STATS"
|
||||||
|
try:
|
||||||
|
serial = getserial()
|
||||||
|
|
||||||
|
info = {
|
||||||
|
"id": serial,
|
||||||
|
"version": "3.0",
|
||||||
|
"kettle": len(cbpi.cache.get("kettle")),
|
||||||
|
"hardware": len(cbpi.cache.get("actors")),
|
||||||
|
"thermometer": "CBP3.0",
|
||||||
|
"hardware_control": "CBP3.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
import requests
|
||||||
|
r = requests.post('http://statistics.craftbeerpi.com', json=info)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
Executable
+230
@@ -0,0 +1,230 @@
|
|||||||
|
import time
|
||||||
|
from flask import json
|
||||||
|
from flask_classy import route
|
||||||
|
|
||||||
|
from modules import DBModel, cbpi, get_db
|
||||||
|
from modules.core.baseview import BaseView
|
||||||
|
|
||||||
|
|
||||||
|
class Step(DBModel):
|
||||||
|
__fields__ = ["name","type", "stepstate", "state", "start", "end", "order", "config"]
|
||||||
|
__table_name__ = "step"
|
||||||
|
__json_fields__ = ["config", "stepstate"]
|
||||||
|
__order_by__ = "order"
|
||||||
|
__as_array__ = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_max_order(cls):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("SELECT max(step.'order') as 'order' FROM %s" % cls.__table_name__)
|
||||||
|
r = cur.fetchone()
|
||||||
|
return r.get("order")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_by_state(cls, state):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("SELECT * FROM %s WHERE state = ?" % cls.__table_name__, state)
|
||||||
|
r = cur.fetchone()
|
||||||
|
if r is not None:
|
||||||
|
return cls(r)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def delete_all(cls):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("DELETE FROM %s" % cls.__table_name__)
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def reset_all_steps(cls):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("UPDATE %s SET state = 'I', stepstate = NULL , start = NULL, end = NULL " % cls.__table_name__)
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_state(cls, id, state):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("UPDATE %s SET state = ? WHERE id =?" % cls.__table_name__, (state, id))
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def update_step_state(cls, id, state):
|
||||||
|
cur = get_db().cursor()
|
||||||
|
cur.execute("UPDATE %s SET stepstate = ? WHERE id =?" % cls.__table_name__, (json.dumps(state),id))
|
||||||
|
get_db().commit()
|
||||||
|
|
||||||
|
class StepView(BaseView):
|
||||||
|
model = Step
|
||||||
|
def pre_post_callback(self, data):
|
||||||
|
order = self.model.get_max_order()
|
||||||
|
data["order"] = 1 if order is None else order + 1
|
||||||
|
data["state"] = "I"
|
||||||
|
|
||||||
|
@route('/', methods=["DELETE"])
|
||||||
|
def deleteAll(self):
|
||||||
|
self.model.delete_all()
|
||||||
|
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/action/<method>', methods=["POST"])
|
||||||
|
def action(self, method):
|
||||||
|
|
||||||
|
cbpi.cache["active_step"].__getattribute__(method)()
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/reset', methods=["POST"])
|
||||||
|
def reset(self):
|
||||||
|
|
||||||
|
self.model.reset_all_steps()
|
||||||
|
#db.session.commit()
|
||||||
|
self.stop_step()
|
||||||
|
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def stop_step(self):
|
||||||
|
'''
|
||||||
|
stop active step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
step = cbpi.cache.get("active_step")
|
||||||
|
cbpi.cache["active_step"] = None
|
||||||
|
|
||||||
|
if step is not None:
|
||||||
|
step.finish()
|
||||||
|
|
||||||
|
@route('/reset/current', methods=['POST'])
|
||||||
|
def resetCurrentStep(self):
|
||||||
|
'''
|
||||||
|
Reset current step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
step = cbpi.cache.get("active_step")
|
||||||
|
|
||||||
|
if step is not None:
|
||||||
|
step.reset()
|
||||||
|
if step.is_dirty():
|
||||||
|
|
||||||
|
state = {}
|
||||||
|
for field in step.managed_fields:
|
||||||
|
state[field] = step.__getattribute__(field)
|
||||||
|
Step.update_step_state(step.id, state)
|
||||||
|
step.reset_dirty()
|
||||||
|
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def init_step(self, step):
|
||||||
|
|
||||||
|
cbpi.log_action("Start Step %s" % step.name)
|
||||||
|
type_cfg = cbpi.cache.get("step_types").get(step.type)
|
||||||
|
if type_cfg is None:
|
||||||
|
# if type not found
|
||||||
|
return
|
||||||
|
|
||||||
|
# copy config to stepstate
|
||||||
|
# init step
|
||||||
|
cfg = step.config.copy()
|
||||||
|
cfg.update(dict(name=step.name, api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg)))
|
||||||
|
instance = type_cfg.get("class")(**cfg)
|
||||||
|
instance.init()
|
||||||
|
|
||||||
|
# set step instance to ache
|
||||||
|
cbpi.cache["active_step"] = instance
|
||||||
|
|
||||||
|
@route('/next', methods=['POST'])
|
||||||
|
@route('/start', methods=['POST'])
|
||||||
|
def start(self):
|
||||||
|
active = Step.get_by_state("A")
|
||||||
|
inactive = Step.get_by_state('I')
|
||||||
|
|
||||||
|
if (active is not None):
|
||||||
|
active.state = 'D'
|
||||||
|
active.end = int(time.time())
|
||||||
|
self.stop_step()
|
||||||
|
Step.update(**active.__dict__)
|
||||||
|
|
||||||
|
if (inactive is not None):
|
||||||
|
self.init_step(inactive)
|
||||||
|
inactive.state = 'A'
|
||||||
|
inactive.stepstate = inactive.config
|
||||||
|
inactive.start = int(time.time())
|
||||||
|
Step.update(**inactive.__dict__)
|
||||||
|
else:
|
||||||
|
cbpi.log_action("Brewing Finished")
|
||||||
|
cbpi.notify("Brewing Finished", "You are done!", timeout=None)
|
||||||
|
|
||||||
|
cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def get_manged_fields_as_array(type_cfg):
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for f in type_cfg.get("properties"):
|
||||||
|
|
||||||
|
result.append(f.get("name"))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@cbpi.try_catch(None)
|
||||||
|
def init_after_startup():
|
||||||
|
'''
|
||||||
|
Restart after startup. Check is a step is in state A and reinitialize
|
||||||
|
:return: None
|
||||||
|
'''
|
||||||
|
|
||||||
|
step = Step.get_by_state('A')
|
||||||
|
# We have an active step
|
||||||
|
if step is not None:
|
||||||
|
|
||||||
|
# get the type
|
||||||
|
|
||||||
|
|
||||||
|
type_cfg = cbpi.cache.get("step_types").get(step.type)
|
||||||
|
|
||||||
|
if type_cfg is None:
|
||||||
|
# step type not found. cant restart step
|
||||||
|
return
|
||||||
|
cfg = step.stepstate.copy()
|
||||||
|
cfg.update(dict(api=cbpi, id=step.id, timer_end=None, managed_fields=get_manged_fields_as_array(type_cfg)))
|
||||||
|
instance = type_cfg.get("class")(**cfg)
|
||||||
|
instance.init()
|
||||||
|
cbpi.cache["active_step"] = instance
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=2000)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE STEPS MODULE"
|
||||||
|
StepView.register(cbpi.app, route_base='/api/step')
|
||||||
|
|
||||||
|
def get_all():
|
||||||
|
with cbpi.app.app_context():
|
||||||
|
return Step.get_all()
|
||||||
|
|
||||||
|
with cbpi.app.app_context():
|
||||||
|
init_after_startup()
|
||||||
|
cbpi.add_cache_callback("steps", get_all)
|
||||||
|
|
||||||
|
@cbpi.backgroundtask(key="step_task", interval=0.1)
|
||||||
|
def execute_step():
|
||||||
|
'''
|
||||||
|
Background job which executes the step
|
||||||
|
:return:
|
||||||
|
'''
|
||||||
|
with cbpi.app.app_context():
|
||||||
|
step = cbpi.cache.get("active_step")
|
||||||
|
if step is not None:
|
||||||
|
step.execute()
|
||||||
|
|
||||||
|
if step.is_dirty():
|
||||||
|
|
||||||
|
state = {}
|
||||||
|
for field in step.managed_fields:
|
||||||
|
state[field] = step.__getattribute__(field)
|
||||||
|
Step.update_step_state(step.id, state)
|
||||||
|
step.reset_dirty()
|
||||||
|
cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||||
|
|
||||||
|
if step.n is True:
|
||||||
|
|
||||||
|
StepView().start()
|
||||||
|
cbpi.emit("UPDATE_ALL_STEPS", Step.get_all())
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
import endpoints
|
||||||
Executable
+107
@@ -0,0 +1,107 @@
|
|||||||
|
from flask import json
|
||||||
|
from flask_classy import FlaskView, route
|
||||||
|
from git import Repo, Git
|
||||||
|
|
||||||
|
from modules.app_config import cbpi
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class SystemView(FlaskView):
|
||||||
|
def doShutdown(self):
|
||||||
|
time.sleep(5)
|
||||||
|
from subprocess import call
|
||||||
|
call("halt")
|
||||||
|
|
||||||
|
@route('/shutdown', methods=['POST'])
|
||||||
|
def shutdown(self):
|
||||||
|
"""
|
||||||
|
Shutdown hook
|
||||||
|
:return: HTTP 204
|
||||||
|
"""
|
||||||
|
self.doShutdown()
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
def doReboot(self):
|
||||||
|
time.sleep(5)
|
||||||
|
from subprocess import call
|
||||||
|
call("reboot")
|
||||||
|
|
||||||
|
@route('/reboot', methods=['POST'])
|
||||||
|
def reboot(self):
|
||||||
|
"""
|
||||||
|
Reboot hook
|
||||||
|
:return: HTTP 204
|
||||||
|
"""
|
||||||
|
self.doReboot()
|
||||||
|
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/tags/<name>', methods=['GET'])
|
||||||
|
def checkout_tag(self,name):
|
||||||
|
repo = Repo('./')
|
||||||
|
repo.git.reset('--hard')
|
||||||
|
o = repo.remotes.origin
|
||||||
|
o.fetch()
|
||||||
|
g = Git('./')
|
||||||
|
g.checkout(name)
|
||||||
|
cbpi.notify("Checkout successful", "Please restart the system")
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/git/status', methods=['GET'])
|
||||||
|
def git_status(self):
|
||||||
|
repo = Repo('./')
|
||||||
|
o = repo.remotes.origin
|
||||||
|
o.fetch()
|
||||||
|
# Tags
|
||||||
|
tags = []
|
||||||
|
for t in repo.tags:
|
||||||
|
tags.append({"name": t.name, "commit": str(t.commit), "date": t.commit.committed_date,
|
||||||
|
"committer": t.commit.committer.name, "message": t.commit.message})
|
||||||
|
try:
|
||||||
|
branch_name = repo.active_branch.name
|
||||||
|
# test1
|
||||||
|
except:
|
||||||
|
branch_name = None
|
||||||
|
|
||||||
|
changes = []
|
||||||
|
commits_behind = repo.iter_commits('master..origin/master')
|
||||||
|
|
||||||
|
for c in list(commits_behind):
|
||||||
|
changes.append({"committer": c.committer.name, "message": c.message})
|
||||||
|
|
||||||
|
return json.dumps({"tags": tags, "headcommit": str(repo.head.commit), "branchname": branch_name,
|
||||||
|
"master": {"changes": changes}})
|
||||||
|
|
||||||
|
@route('/check_update', methods=['GET'])
|
||||||
|
def check_update(self):
|
||||||
|
|
||||||
|
repo = Repo('./')
|
||||||
|
o = repo.remotes.origin
|
||||||
|
o.fetch()
|
||||||
|
changes = []
|
||||||
|
commits_behind = repo.iter_commits('master..origin/master')
|
||||||
|
|
||||||
|
for c in list(commits_behind):
|
||||||
|
changes.append({"committer": c.committer.name, "message": c.message})
|
||||||
|
|
||||||
|
return json.dumps(changes)
|
||||||
|
|
||||||
|
@route('/git/pull', methods=['POST'])
|
||||||
|
def update(self):
|
||||||
|
repo = Repo('./')
|
||||||
|
o = repo.remotes.origin
|
||||||
|
info = o.pull()
|
||||||
|
cbpi.notify("Pull successful", "The lasted updated was downloaded. Please restart the system")
|
||||||
|
return ('', 204)
|
||||||
|
|
||||||
|
@route('/dump', methods=['GET'])
|
||||||
|
def dump(self):
|
||||||
|
return json.dumps(cbpi.cache)
|
||||||
|
|
||||||
|
@cbpi.initalizer()
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE SYSTEM MODULE"
|
||||||
|
SystemView.register(cbpi.app, route_base='/api/system')
|
||||||
Executable
+1
@@ -0,0 +1 @@
|
|||||||
|
import endpoints
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from flask import Blueprint, render_template, request
|
||||||
|
from modules import cbpi
|
||||||
|
|
||||||
|
react = Blueprint('react', __name__, template_folder='templates', static_folder='static')
|
||||||
|
|
||||||
|
@cbpi.initalizer(order=10)
|
||||||
|
def init(cbpi):
|
||||||
|
print "INITIALIZE UI"
|
||||||
|
cbpi.app.register_blueprint(react, url_prefix='/ui')
|
||||||
|
|
||||||
|
|
||||||
|
@react.route('/')
|
||||||
|
def index():
|
||||||
|
return react.send_static_file("index.html")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Vendored
+11
File diff suppressed because one or more lines are too long
Vendored
+6
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2337
File diff suppressed because it is too large
Load Diff
+4
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,70 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="static/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="static/css/font-awesome.min.css">
|
||||||
|
<link rel="stylesheet" href="static/bootstrap.dark.css">
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
|
||||||
|
.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
|
||||||
|
position: relative;
|
||||||
|
min-height: 1px;
|
||||||
|
padding-right: 8px;
|
||||||
|
padding-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-heading {
|
||||||
|
padding: 5px 5px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-footer {
|
||||||
|
padding: 2px 2px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row > [class*='col-'] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
<title>CraftBeerPi 3.0</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="static/bundle.js" type="text/javascript"></script>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start`.
|
||||||
|
To create a production bundle, use `npm run build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Executable
+15
@@ -0,0 +1,15 @@
|
|||||||
|
Flask==0.11.1
|
||||||
|
Flask-SocketIO==2.6.2
|
||||||
|
eventlet==0.19.0
|
||||||
|
greenlet==0.4.10
|
||||||
|
python-dateutil==2.5.3
|
||||||
|
python-engineio==0.9.2
|
||||||
|
python-mimeparse==1.5.2
|
||||||
|
python-socketio==1.4.4
|
||||||
|
PyYAML==3.11
|
||||||
|
requests==2.11.0
|
||||||
|
Werkzeug==0.11.10
|
||||||
|
httplib2==0.9.2
|
||||||
|
flask-classy==0.6.10
|
||||||
|
GitPython==2.1.3
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from modules import socketio, app
|
||||||
|
|
||||||
|
socketio.run(app, host='0.0.0.0')
|
||||||
|
|
||||||
Executable
Reference in New Issue
Block a user