浏览代码

First Refactoring

- Major API Changes in Core Module
- Will be changed further. Still work in progress
tags/3.1_alpha
Manuel83 8 年前
父节点
当前提交
42ed9f02af
共有 50 个文件被更改,包括 3032 次插入3217 次删除
  1. +0
    -72
      modules/__init__.py
  2. +31
    -0
      modules/action/__init__.py
  3. +183
    -74
      modules/actor/__init__.py
  4. +0
    -1
      modules/addon/__init__.py
  5. +0
    -199
      modules/addon/endpoints.py
  6. +0
    -55
      modules/app_config.py
  7. +51
    -0
      modules/base_plugins/actor.py
  8. +0
    -223
      modules/base_plugins/brew_steps/__init__.py
  9. +0
    -53
      modules/base_plugins/dummy_temp/__init__.py
  10. +0
    -38
      modules/base_plugins/fermenter_hysteresis/__init__.py
  11. +0
    -107
      modules/base_plugins/gpio_actor/__init__.py
  12. +0
    -40
      modules/base_plugins/hysteresis/__init__.py
  13. +0
    -118
      modules/base_plugins/one_wire/__init__.py
  14. +36
    -0
      modules/base_plugins/sensor.py
  15. +14
    -0
      modules/base_plugins/steps.py
  16. +53
    -50
      modules/buzzer/__init__.py
  17. +89
    -57
      modules/config/__init__.py
  18. +0
    -0
      modules/core/__init__.py
  19. +216
    -0
      modules/core/baseapi.py
  20. +249
    -168
      modules/core/basetypes.py
  21. +110
    -109
      modules/core/baseview.py
  22. +337
    -494
      modules/core/core.py
  23. +133
    -134
      modules/core/db.py
  24. +0
    -47
      modules/core/db_mirgrate.py
  25. +0
    -108
      modules/core/hardware.py
  26. +36
    -0
      modules/core/login.py
  27. +48
    -68
      modules/core/proptypes.py
  28. +0
    -146
      modules/core/step.py
  29. +0
    -0
      modules/database/__init__.py
  30. +134
    -0
      modules/database/dbmodel.py
  31. +33
    -83
      modules/fermenter/__init__.py
  32. +264
    -96
      modules/kettle/__init__.py
  33. +175
    -1
      modules/logs/__init__.py
  34. +0
    -109
      modules/logs/endpoints.py
  35. +26
    -10
      modules/notification/__init__.py
  36. +138
    -0
      modules/plugin/__init__.py
  37. +41
    -4
      modules/recipe_import/beerxml.py
  38. +41
    -4
      modules/recipe_import/kbh.py
  39. +4
    -4
      modules/recipe_import/restapi.py
  40. +67
    -0
      modules/sensor/__init__.py
  41. +0
    -48
      modules/sensors/__init__.py
  42. +35
    -36
      modules/stats/__init__.py
  43. +241
    -239
      modules/step/__init__.py
  44. +152
    -1
      modules/system/__init__.py
  45. +0
    -145
      modules/system/endpoints.py
  46. +23
    -1
      modules/ui/__init__.py
  47. +0
    -25
      modules/ui/endpoints.py
  48. +48
    -44
      modules/ui/static/bundle.js
  49. +1
    -0
      modules/ui/static/index.html
  50. +23
    -6
      run.py

+ 0
- 72
modules/__init__.py 查看文件

@@ -1,72 +0,0 @@
import json
import pprint
import sys, os
from flask import Flask, render_template, redirect
from flask_socketio import SocketIO, emit
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
from modules.addon.endpoints import initPlugins
import modules.ui
import modules.system
import modules.buzzer
import modules.stats
import modules.kettle
import modules.recipe_import
import modules.core.db_mirgrate
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()
initPlugins()
cbpi.run_init()
cbpi.run_background_processes()
app.logger.info("##########################################")
app.logger.info("### STARTUP COMPLETE")
app.logger.info("##########################################")

+ 31
- 0
modules/action/__init__.py 查看文件

@@ -0,0 +1,31 @@
import json
from flask_classy import FlaskView, route
from modules.core.core import cbpi

class ActionView(FlaskView):

@route('/<action>', methods=['POST'])
def action(self, action):
"""
Call global action button
---
tags:
- action
responses:
200:
description: action invoked
"""
print self.cbpi.cache["actions"]
self.cbpi.cache["actions"][action]["function"](self.cbpi)

return ('',204)

@cbpi.addon.core.initializer()
def init(cbpi):
"""
Initializer for the message module
:param app: the flask app
:return: None
"""
ActionView.cbpi = cbpi
ActionView.register(cbpi._app, route_base='/api/action')

+ 183
- 74
modules/actor/__init__.py 查看文件

@@ -1,74 +1,183 @@
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)

@route('<int:id>/action/<method>', methods=["POST"])
def action(self, id, method):

cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
return ('', 204)


@cbpi.initalizer(order=1000)
def init(cbpi):
ActorView.register(cbpi.app, route_base='/api/actor')
ActorView.init_cache()
cbpi.init_actors()
import time
from flask_classy import route
from modules.core.db import DBModel
from modules.core.core import cbpi
from modules.core.baseview import BaseView
from modules.database.dbmodel import Actor
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.actor.init_one(m.id)
def _post_put_callback(self, m):
self.api.actor.init_one(m.id)
@route("<int:id>/switch/on", methods=["POST"])
def on(self, id):
"""
Switch actor on
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
responses:
200:
description: Actor switched on
"""
self.api.actor.on(id)
return ('', 204)
@route("<int:id>/switch/off", methods=["POST"])
def off(self, id):
"""
Switch actor off
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
responses:
200:
description: Actor switched off
"""
self.api.actor.off(id)
return ('', 204)
@route("<int:id>/power/<int:power>", methods=["POST"])
def power(self, id, power):
"""
Set Actor Power
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
- in: path
name: power
schema:
type: integer
required: true
description: Power value between 0 - 100
responses:
200:
description: Actor power set
"""
self.api.actor.power(id, power)
return ('', 204)
@route("<int:id>/toggle", methods=["POST"])
def toggle(self, id):
"""
Toggle Actor
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
responses:
200:
description: Actor toggled
"""
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):
"""
Toggle Actor for a defined time
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
- in: path
name: time
schema:
type: integer
required: true
description: time in seconds
responses:
200:
description: Actor toggled
"""
t = self.api._socketio.start_background_task(target=self.toggleTimeJob, id=id, t=t)
return ('', 204)
@route('<int:id>/action/<method>', methods=["POST"])
def action(self, id, method):
"""
Actor Action
---
tags:
- actor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the actor
- in: path
name: method
schema:
type: string
required: true
description: action method name
responses:
200:
description: Actor Action called
"""
cbpi.cache.get("actors").get(id).instance.__getattribute__(method)()
return ('', 204)
@cbpi.addon.core.initializer(order=1000)
def init(cbpi):
ActorView.register(cbpi._app, route_base='/api/actor')
ActorView.init_cache()
#cbpi.init_actors()

+ 0
- 1
modules/addon/__init__.py 查看文件

@@ -1 +0,0 @@
import endpoints

+ 0
- 199
modules/addon/endpoints.py 查看文件

@@ -1,199 +0,0 @@
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/Manuel83/craftbeerpi-plugins/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 os.path.isdir("./modules/base_plugins/"+filename) is False:
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 os.path.isdir("./modules/plugins/" + filename) is False:
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():
loadCorePlugins()
loadPlugins()

@cbpi.initalizer(order=2)
def init(cbpi):

cbpi.app.register_blueprint(blueprint, url_prefix='/api/editor')

+ 0
- 55
modules/app_config.py 查看文件

@@ -1,55 +0,0 @@

import json
import sys, os
from flask import Flask, render_template, redirect, json, g


from flask_socketio import SocketIO, emit

import logging



from modules.core.core import CraftBeerPi, ActorBase, SensorBase
from modules.core.db import DBModel

app = Flask(__name__)

FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s'

logging.basicConfig(filename='./logs/app.log',level=logging.INFO, format=FORMAT)
app.config['SECRET_KEY'] = 'craftbeerpi'
app.config['UPLOAD_FOLDER'] = './upload'


@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, logging=False)
cbpi = CraftBeerPi(app, socketio)

app.logger.info("##########################################")
app.logger.info("### NEW STARTUP Version 3.0")
app.logger.info("##########################################")

+ 51
- 0
modules/base_plugins/actor.py 查看文件

@@ -0,0 +1,51 @@
from modules.core.baseapi import Buzzer
from modules.core.basetypes import Actor, KettleController, FermenterController
from modules.core.core import cbpi

@cbpi.addon.actor.type("Dummy Actor")
class Dummy(Actor):


@cbpi.addon.actor.action("WOHOO")
def myaction(self):
print "HALLO!!!"

def on(self, power=100):
'''
Code to switch on the actor
:param power: int value between 0 - 100
:return:
'''
print "ON"

def off(self):
print "OFF"



@cbpi.addon.kettle.controller()
class MyController(KettleController):


def run(self):
while self.is_running():
print "HALLO"
self.sleep(1)

@cbpi.addon.fermenter.controller()
class MyController2(FermenterController):


def run(self):
while self.is_running():
print "HALLO"
self.sleep(1)

@cbpi.addon.core.initializer(order=200)
def init(cbpi):

class MyBuzzer(Buzzer):
def beep(self):
print "BEEEEEEP"

cbpi.buzzer = MyBuzzer()

+ 0
- 223
modules/base_plugins/brew_steps/__init__.py 查看文件

@@ -1,223 +0,0 @@
# -*- 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, description="Target Temperature of Mash Step")
kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place")
timer = Property.Number("Timer in Minutes", configurable=True, description="Timer is started when the target temperature is reached")

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) >= float(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.notify("Mash Step Completed!", "Starting the next step", timeout=None)
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, description="Target Temperature of Mash Step")
kettle = StepProperty.Kettle("Kettle", description="Kettle in which the mashing takes place")
s = False

@cbpi.action("Change Power")
def change_power(self):
self.actor_power(1, 50)

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) >= float(self.temp) and self.s is False:
self.s = True
self.notify("Step Temp Reached!", "Please press the next button to continue", timeout=None)



@cbpi.step
class ChilStep(StepBase):

timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")

@cbpi.action("Stat Timer")
def start(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)

def reset(self):
self.stop_timer()


def finish(self):
pass

def execute(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)

if self.is_timer_finished() == True:
self.next()

@cbpi.step
class PumpStep(StepBase):

pump = StepProperty.Actor("Pump", description="Pump actor gets toogled")
timer = Property.Number("Timer in Minutes", configurable=True, default_value=0, description="Timer is started immediately")

@cbpi.action("Stat Timer")
def start(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)

def reset(self):
self.stop_timer()


def finish(self):
self.actor_off(int(self.pump))

def init(self):
self.actor_on(int(self.pump))

def execute(self):
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)

if self.is_timer_finished() == True:
self.next()

@cbpi.step
class BoilStep(StepBase):
'''
Just put the decorator @cbpi.step on top of a method
'''
# Properties
temp = Property.Number("Temperature", configurable=True, default_value=100, description="Target temperature for boiling")
kettle = StepProperty.Kettle("Kettle", description="Kettle in which the boiling step takes place")
timer = Property.Number("Timer in Minutes", configurable=True, default_value=90, description="Timer is started when target temperature is reached")
hop_1 = Property.Number("Hop 1 Addition", configurable=True, description="Fist Hop alert")
hop_1_added = Property.Number("",default_value=None)
hop_2 = Property.Number("Hop 2 Addition", configurable=True, description="Second Hop alert")
hop_2_added = Property.Number("", default_value=None)
hop_3 = Property.Number("Hop 3 Addition", configurable=True)
hop_3_added = Property.Number("", default_value=None, description="Second Hop alert")

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 check_hop_timer(self, number, value):

if self.__getattribute__("hop_%s_added" % number) is not True and time.time() > (
self.timer_end - (int(self.timer) * 60 - int(value) * 60)):
self.__setattr__("hop_%s_added" % number, True)
self.notify("Hop Alert", "Please add Hop %s" % number, timeout=None)

def execute(self):
'''
This method is execute in an interval
:return:
'''
# Check if Target Temp is reached
if self.get_kettle_temp(self.kettle) >= float(self.temp):
# Check if Timer is Running
if self.is_timer_finished() is None:
self.start_timer(int(self.timer) * 60)
else:
self.check_hop_timer(1, self.hop_1)
self.check_hop_timer(2, self.hop_2)
self.check_hop_timer(3, self.hop_3)
# Check if timer finished and go to next step
if self.is_timer_finished() == True:
self.notify("Boil Step Completed!", "Starting the next step", timeout=None)
self.next()

+ 0
- 53
modules/base_plugins/dummy_temp/__init__.py 查看文件

@@ -1,53 +0,0 @@
# -*- 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, description="Dummy Temperature as decimal value")

@cbpi.action("My Custom Action")
def my_action(self):
print "HELLO WORLD"
pass

def get_unit(self):
'''
:return: Unit of the sensor as string. Should not be longer than 3 characters
'''
return "°C" if self.get_config_parameter("unit", "C") == "C" else "°F"

def stop(self):
SensorActive.stop(self)

def execute(self):
'''
Active sensor has to handle his own loop
:return:
'''
while self.is_running() is True:
self.data_received(self.temp)
self.sleep(5)

@classmethod
def init_global(cls):
'''
Called one at the startup for all sensors
:return:
'''










+ 0
- 38
modules/base_plugins/fermenter_hysteresis/__init__.py 查看文件

@@ -1,38 +0,0 @@
from modules import cbpi
from modules.core.controller import KettleController, FermenterController
from modules.core.props import Property


@cbpi.fermentation_controller
class Hysteresis(FermenterController):

heater_offset_min = Property.Number("Heater Offset ON", True, 0, description="Offset as decimal number when the heater is switched on. Should be greater then 'Heater Offset OFF'. For example a value of 2 switches on the heater if the current temperature is 2 degrees below the target temperature")
heater_offset_max = Property.Number("Heater Offset OFF", True, 0, description="Offset as decimal number when the heater is switched off. Should be smaller then 'Heater Offset ON'. For example a value of 1 switches off the heater if the current temperature is 1 degree below the target temperature")
cooler_offset_min = Property.Number("Cooler Offset ON", True, 0, description="Offset as decimal number when the cooler is switched on. Should be greater then 'Cooler Offset OFF'. For example a value of 2 switches on the cooler if the current temperature is 2 degrees above the target temperature")
cooler_offset_max = Property.Number("Cooler Offset OFF", True, 0, description="Offset as decimal number when the cooler is switched off. Should be less then 'Cooler Offset ON'. For example a value of 1 switches off the cooler if the current temperature is 1 degree above the target temperature")

def stop(self):
super(FermenterController, self).stop()

self.heater_off()
self.cooler_off()

def run(self):
while self.is_running():

target_temp = self.get_target_temp()
temp = self.get_temp()

if temp + float(self.heater_offset_min) <= target_temp:
self.heater_on(100)

if temp + float(self.heater_offset_max) >= target_temp:
self.heater_off()

if temp >= target_temp + float(self.cooler_offset_min):
self.cooler_on(100)

if temp <= target_temp + float(self.cooler_offset_max):
self.cooler_off()

self.sleep(1)

+ 0
- 107
modules/base_plugins/gpio_actor/__init__.py 查看文件

@@ -1,107 +0,0 @@
# -*- 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,27], description="GPIO to which the actor is connected")

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,27], description="GPIO to which the actor is connected")
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):
'''
Optional: Set the power of your actor
:param power: int value between 0 - 100
:return:
'''
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,27], description="GPIO to which the actor is connected")

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):
'''
Code to switch on the actor
:param power: int value between 0 - 100
:return:
'''
print "ON"

def off(self):
print "OFF"




+ 0
- 40
modules/base_plugins/hysteresis/__init__.py 查看文件

@@ -1,40 +0,0 @@
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, description="Offset below target temp when heater should switched on. Should be bigger then Offset Off")
off = Property.Number("Offset Off", True, 0, description="Offset below target temp when heater should switched off. Should be smaller then Offset Off")

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() - float(self.on):
self.heater_on(100)
elif self.get_temp() >= self.get_target_temp() - float(self.off):
self.heater_off()
else:
self.heater_off()
self.sleep(1)


+ 0
- 118
modules/base_plugins/one_wire/__init__.py 查看文件

@@ -1,118 +0,0 @@
# -*- 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")):
cbpi.app.logger.info("Device %s Found (Family: 28/10, Thermometer on GPIO4 (w1))" % dirname)
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(), description="The OneWire sensor address.")
offset = Property.Number("Offset", True, 0, description="Offset which is added to the received sensor data. Positive and negative values are both allowed.")

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(round(self.t.value + self.offset_value(), 2))
else:
self.data_received(round(9.0 / 5.0 * self.t.value + 32 + self.offset_value(), 2))

@cbpi.try_catch(0)
def offset_value(self):
return float(self.offset)
@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.register_blueprint(blueprint, url_prefix='/api/one_wire')

+ 36
- 0
modules/base_plugins/sensor.py 查看文件

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from modules.core.basetypes import Actor, Sensor
from modules.core.core import cbpi
from modules.core.proptypes import Property
import random
print "INit SENSOR"
@cbpi.addon.sensor.type("Dummy Sensor")
class Dummy(Sensor):

text = Property.Text(label="Text", required=True, description="This is a parameter", configurable=True)
p = Property.Select(label="hallo",options=[1,2,3])

def init(self):

if self.api.get_config_parameter("unit","C") == "C":
self.unit = "°C"
else:
self.unit = "°F"

@cbpi.addon.sensor.action("WOHOO")
def myaction(self):
print self.text
print "SENSOR ACTION HALLO!!!"

def execute(self):
while True:
try:
self.update_value(int(self.text))
except:
pass
self.api.sleep(1)

@cbpi.addon.core.action(key="clear", label="Clear all Logs")
def woohoo(cbpi):
print "COOL"
cbpi.notify(headline="HELLO WORLD",message="")

+ 14
- 0
modules/base_plugins/steps.py 查看文件

@@ -0,0 +1,14 @@
from modules.core.basetypes import Step
from modules.core.core import cbpi
from modules.core.proptypes import Property


@cbpi.addon.step.type("Dummy Step")
class Dummy(Step):


text = Property.Text(label="Text", configurable=True, description="WOHOOO")

def execute(self):
#print self.text
pass

+ 53
- 50
modules/buzzer/__init__.py 查看文件

@@ -1,50 +1,53 @@
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", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"]
def __init__(self, gpio):
try:
cbpi.app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
self.gpio = int(gpio)
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.gpio, GPIO.OUT)
self.state = True
cbpi.app.logger.info("BUZZER SETUP OK")
except Exception as e:
cbpi.app.logger.info("BUZZER EXCEPTION %s" % str(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=1)
def init(cbpi):
gpio = cbpi.get_config_parameter("buzzer", 16)
cbpi.buzzer = Buzzer(gpio)
cbpi.beep()
cbpi.app.logger.info("INIT OK")
import time
from thread import start_new_thread
from modules.core.baseapi import Buzzer
from modules.core.core import cbpi
try:
import RPi.GPIO as GPIO
except Exception as e:
pass
class GPIOBuzzer(Buzzer):
sound = ["H", 0.1, "L", 0.1, "H", 0.1, "L", 0.1, "H", 0.1, "L"]
def __init__(self, gpio):
try:
cbpi._app.logger.info("INIT BUZZER NOW GPIO%s" % gpio)
self.gpio = int(gpio)
GPIO.setmode(GPIO.BCM)
GPIO.setup(self.gpio, GPIO.OUT)
self.state = True
cbpi._app.logger.info("BUZZER SETUP OK")
except Exception as e:
cbpi._app.logger.info("BUZZER EXCEPTION %s" % str(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.addon.core.initializer(order=1)
def init(cbpi):
gpio = cbpi.get_config_parameter("buzzer", 16)
cbpi.buzzer = GPIOBuzzer(gpio)

+ 89
- 57
modules/config/__init__.py 查看文件

@@ -1,57 +1,89 @@
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.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=0)
def init(cbpi):

ConfigView.register(cbpi.app, route_base='/api/config')
ConfigView.init_cache()
import time
from flask import json, request
from flask_classy import route
from modules.core.core import cbpi
from modules.core.db import DBModel
from modules.core.baseview import BaseView
from modules.database.dbmodel import Config
class ConfigView(BaseView):
model = Config
cache_key = "config"
@route('/<name>', methods=["PUT"])
def put(self, name):
"""
Set new config value
---
tags:
- config
responses:
204:
description: New config value set
"""
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.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])
self.api.emit("CONFIG_UPDATE", name=name, data=data["value"])
return json.dumps(self.api.cache.get(self.cache_key)[name].__dict__)
@route('/<id>', methods=["GET"])
def getOne(self, id):
"""
Get config parameter
---
tags:
- config
responses:
400:
description: Get one config parameter via web api is not supported
"""
return ('NOT SUPPORTED', 400)
@route('/<id>', methods=["DELETE"])
def delete(self, id):
"""
Delete config parameter
---
tags:
- config
responses:
400:
description: Deleting config parameter via web api is not supported
"""
return ('NOT SUPPORTED', 400)
@route('/', methods=["POST"])
def post(self):
"""
Get config parameter
---
tags:
- config
responses:
400:
description: Adding new config parameter via web api is not supported
"""
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.addon.core.initializer(order=0)
def init(cbpi):
ConfigView.register(cbpi._app, route_base='/api/config')
ConfigView.init_cache()

+ 0
- 0
modules/core/__init__.py 查看文件


+ 216
- 0
modules/core/baseapi.py 查看文件

@@ -0,0 +1,216 @@
from proptypes import *
class BaseAPI(object):
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache[self.key] = {}
def init(self):
for name, value in self.cbpi.cache[self.key].iteritems():
value["class"].init_global()
def parseProps(self, key, cls, **options):
name = cls.__name__
tmpObj = cls()
try:
doc = tmpObj.__doc__.strip()
except:
doc = ""
self.cbpi.cache.get(key)[name] = {"name": name, "class": cls, "description":doc, "properties": [], "actions": []}
self.cbpi.cache.get(key)[name].update(options)
members = [attr for attr in dir(tmpObj) if not callable(getattr(tmpObj, attr)) and not attr.startswith("__")]
for m in members:
t = tmpObj.__getattribute__(m)
if isinstance(t, Property.Number):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
elif isinstance(t, Property.Text):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "text", "required": t.required, "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
elif isinstance(tmpObj.__getattribute__(m), Property.Select):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": True, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": True, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
self.cbpi.cache.get(key)[name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": True, "description": t.description})
for method_name, method in cls.__dict__.iteritems():
if hasattr(method, "action"):
label = method.__getattribute__("label")
self.cbpi.cache.get(key)[name]["actions"].append({"method": method_name, "label": label})
return cls
class SensorAPI(BaseAPI):
key = "sensor_types"
def type(self, description="Step", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f, description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class StepAPI(BaseAPI):
key = "step_types"
def init(self):
pass
def type(self, description="Step", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f, description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class ActorAPI(BaseAPI):
key = "actor_types"
def type(self, description="", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key, f, description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class KettleAPI(BaseAPI):
key = "controller_types"
def controller(self, description="", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f,description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class FermenterAPI(BaseAPI):
key = "fermentation_controller_types"
def controller(self, description="Step", **options):
def decorator(f):
BaseAPI.parseProps(self, self.key,f,description=description)
return f
return decorator
def action(self, label):
def real_decorator(func):
func.action = True
func.label = label
return func
return real_decorator
class CoreAPI(BaseAPI):
key = "core"
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache["actions"] = {}
self.cbpi.cache["init"] = []
self.cbpi.cache["js"] = {}
self.cbpi.cache["background"] = []
def init(self):
self.cbpi.cache["init"] = sorted(self.cbpi.cache["init"], key=lambda k: k['order'])
for value in self.cbpi.cache.get("init"):
print value
value["function"](self.cbpi)
def job(interval, method):
while True:
try:
method(self.cbpi)
except Exception as e:
print e
self.cbpi._socketio.sleep(interval)
for value in self.cbpi.cache.get("background"):
t = self.cbpi._socketio.start_background_task(target=job, interval=value.get("interval"), method=value.get("function"))
def add_js(self, name, file):
self.cbpi.cache["js"][name] = file
def initializer(self, order=0, **options):
def decorator(f):
self.cbpi.cache.get("init").append({"function": f, "order": order})
return f
return decorator
def action(self, key, label, **options):
def decorator(f):
self.cbpi.cache.get("actions")[key] = {"label": label, "function": f}
return f
return decorator
def backgroundjob(self, key, interval, **options):
def decorator(f):
self.cbpi.cache.get("background").append({"function": f, "key": key, "interval": interval})
return f
return decorator
def listen(self, name, method=None, async=False):
if method is not None:
if self.cbpi.eventbus.get(name) is None:
self.cbpi.eventbus[name] = []
self.cbpi.eventbus[name].append({"function": method, "async": async})
else:
def real_decorator(function):
if self.cbpi.eventbus.get(name) is None:
self.cbpi.eventbus[name] = []
self.cbpi.eventbus[name].append({"function": function, "async": async})
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
return real_decorator
class Buzzer(object):
def beep():
pass

modules/core/controller.py → modules/core/basetypes.py 查看文件

@@ -1,168 +1,249 @@
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))

@cbpi.try_catch(None)
def actor_power(self, power, id=None):
if id is None:
id = self.heater
self.api.actor_power(int(id), power)


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):

@staticmethod
def chart(kettle):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor})
result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id})

return result

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):

@staticmethod
def chart(fermenter):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor})
result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id})
return result

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 f.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 f.heater is not None:
self.actor_off(int(f.heater))

@cbpi.try_catch(None)
def cooler_on(self, power=100):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_on(power, int(f.cooler))

@cbpi.try_catch(None)
def cooler_off(self):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_off(int(f.cooler))

@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))

class Base(object):
def __init__(self, *args, **kwds):
for a in kwds:
super(Base, self).__setattr__(a, kwds.get(a))
self.api = kwds.get("cbpi")
self.id = kwds.get("id")
self.value = None
self.__dirty = False
class Actor(Base):
@classmethod
def init_global(cls):
print "GLOBAL INIT ACTOR"
pass
def init(self):
pass
def shutdown(self):
pass
def on(self, power=100):
print "SWITCH ON"
pass
def off(self):
print "SWITCH OFF"
pass
def power(self, power):
print "SET POWER", power
pass
def state(self):
pass
class Sensor(Base):
unit = ""
@classmethod
def init_global(cls):
pass
def init(self):
pass
def get_unit(self):
pass
def stop(self):
pass
def update_value(self, value):
self.value = value
self.cbpi.sensor.write_log(self.id, value)
self.cbpi.emit("SENSOR_UPDATE", id=self.id, value=value)
self.cbpi.ws_emit("SENSOR_UPDATE", self.cbpi.cache["sensors"][self.id])
def execute(self):
print "EXECUTE"
pass
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.sleep(seconds)
def stop(self):
self.__running = False
def get_sensor_value(self, id):
return self.api.sensor.get_sensor_value(id)
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 actor_on(self,id, power=100):
self.api.actor.on(id, power=power)
def actor_off(self, id):
self.api.actor.off(id)
def actor_power(self, power, id=None):
self.api.actor.power(id, power)
def run(self):
pass
class KettleController(ControllerBase):
@staticmethod
def chart(kettle):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": kettle.sensor})
result.append({"name": "Target Temp", "data_type": "kettle", "data_id": kettle.id})
return result
def __init__(self, *args, **kwds):
ControllerBase.__init__(self, *args, **kwds)
self.kettle_id = kwds.get("kettle_id")
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(k.heater, power)
def heater_off(self):
k = self.api.cache.get("kettle").get(self.kettle_id)
if k.heater is not None:
self.actor_off(k.heater)
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))
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):
@staticmethod
def chart(fermenter):
result = []
result.append({"name": "Temp", "data_type": "sensor", "data_id": fermenter.sensor})
result.append({"name": "Target Temp", "data_type": "fermenter", "data_id": fermenter.id})
return result
def __init__(self, *args, **kwds):
ControllerBase.__init__(self, *args, **kwds)
self.fermenter_id = kwds.get("fermenter_id")
self.cooler = kwds.get("cooler")
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
def heater_on(self, power=100):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.heater is not None:
self.actor_on(int(f.heater))
def heater_off(self):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.heater is not None:
self.actor_off(int(f.heater))
def cooler_on(self, power=100):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_on(power, int(f.cooler))
def cooler_off(self):
f = self.api.cache.get("fermenter").get(self.fermenter_id)
if f.cooler is not None:
self.actor_off(int(f.cooler))
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))
class Step(Base):
@classmethod
def init_global(cls):
pass
__dirty = False
managed_fields = []
n = False
def next(self):
self.n = True
def init(self):
pass
def finish(self):
pass
def reset(self):
pass
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(Step, 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(Step, self).__setattr__(name, value)
else:
super(Step, self).__setattr__(name, value)

+ 110
- 109
modules/core/baseview.py 查看文件

@@ -1,109 +1,110 @@
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>', methods=["GET"])
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('/', methods=["GET"])
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
from flask import request, json
from flask_classy import route, FlaskView
from modules.core.core import cbpi
class BaseView(FlaskView):
as_array = False
cache_key = None
api = cbpi
@route('/<int:id>', methods=["GET"])
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('/', methods=["GET"])
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.api._app.logger.info("INSERT Model %s", self.model.__name__)
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

+ 337
- 494
modules/core/core.py 查看文件

@@ -1,494 +1,337 @@
import inspect
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 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").api = self
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:
text = text.encode("utf-8")
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 float(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}
self.emit_message(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 set_config_parameter(self, name, value):
from modules.config import Config
with self.app.app_context():
update_data = {"name": name, "value": value}
self.cache.get("config")[name].__dict__.update(**update_data)
c = Config.update(**update_data)
self.emit("UPDATE_CONFIG", c)


def add_config_parameter(self, name, value, type, description, options=None):
from modules.config import Config
with self.app.app_context():
c = Config.insert(**{"name":name, "value": value, "type": type, "description": description, "options": options})
if self.cache.get("config") is not None:
self.cache.get("config")[c.name] = c

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": [], "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), Property.Number):
t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append(
{"name": m, "label": t.label, "type": "number", "configurable": t.configurable, "description": t.description, "default_value": t.default_value})
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, "default_value": t.default_value, "description": t.description})
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, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Actor):
t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "actor", "configurable": t.configurable, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Sensor):
t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "sensor", "configurable": t.configurable, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), Property.Kettle):
t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description})

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


def actor(self, cls):
return self.__parseProps("actor_types", cls)



def actor2(self, description="", power=True, **options):

def decorator(f):
print f()
print f
print options
print description
return f
return decorator

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, "default_value": t.default_value, "description": t.description})
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, "default_value": t.default_value, "description": t.description})
elif isinstance(tmpObj.__getattribute__(m), StepProperty.Select):
t = tmpObj.__getattribute__(m)
self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "select", "configurable": True, "options": t.options, "description": t.description})
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, "description": t.description})
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, "description": t.description})
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, "description": t.description})

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("INITIALIZER - METHOD %s PAHT %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) ))
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(self)
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"))
import json
import logging
import os
import sqlite3
import uuid
from datetime import datetime
from functools import wraps, update_wrapper
from importlib import import_module
from time import localtime, strftime
from flask import Flask, redirect, json, g, make_response
from flask_socketio import SocketIO
from baseapi import *
from db import DBModel
from modules.core.basetypes import Sensor, Actor
from modules.database.dbmodel import Kettle
class ComplexEncoder(json.JSONEncoder):
def default(self, obj):
try:
if isinstance(obj, DBModel):
return obj.__dict__
elif isinstance(obj, Actor):
return {"state": obj.value}
elif isinstance(obj, Sensor):
return {"value": obj.value, "unit": obj.unit}
elif hasattr(obj, "callback"):
return obj()
else:
return None
return None
except TypeError as e:
pass
return None
class Addon(object):
def __init__(self, cbpi):
self.step = StepAPI(cbpi)
self.actor = ActorAPI(cbpi)
self.sensor = SensorAPI(cbpi)
self.kettle = KettleAPI(cbpi)
self.fermenter = FermenterAPI(cbpi)
self.core = CoreAPI(cbpi)
def init(self):
self.core.init()
self.step.init()
self.actor.init()
self.sensor.init()
# self.kettle.init()
# self.fermenter.init()
class ActorCore(object):
key = "actor_types"
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache["actors"] = {}
self.cbpi.cache[self.key] = {}
def init(self):
for key, value in self.cbpi.cache["actors"].iteritems():
self.init_one(key)
def init_one(self, id):
try:
actor = self.cbpi.cache["actors"][id]
clazz = self.cbpi.cache[self.key].get(actor.type)["class"]
cfg = actor.config.copy()
cfg.update(dict(cbpi=self.cbpi, id=id))
self.cbpi.cache["actors"][id].instance = clazz(**cfg)
self.cbpi.emit("INIT_ACTOR", id=id)
except Exception as e:
print e
self.cbpi._app.logger.error(e)
def stop_one(self, id):
self.cbpi.cache["actors"][id]["instance"].stop()
self.cbpi.emit("STOP_ACTOR", id=id)
def on(self, id, power=100):
try:
actor = self.cbpi.cache["actors"].get(int(id))
actor.instance.on()
actor.state = 1
actor.power = power
self.cbpi.ws_emit("SWITCH_ACTOR", actor)
self.cbpi.emit("SWITCH_ACTOR_ON", id=id, power=power)
return True
except Exception as e:
print e
return False
def off(self, id):
try:
actor = self.cbpi.cache["actors"].get(int(id))
actor.instance.off()
actor.state = 0
self.cbpi.ws_emit("SWITCH_ACTOR", actor)
self.cbpi.emit("SWITCH_ACTOR_OFF", id=id)
return True
except Exception as e:
print e
return False
def power(self, id, power):
try:
actor = self.cbpi.cache["actors"].get(int(id))
actor.instance.power(power)
actor.power = power
self.cbpi.ws_emit("SWITCH_ACTOR", actor)
self.cbpi.emit("SWITCH_ACTOR_POWER_CHANGE", id=id, power=power)
return True
except Exception as e:
print e
return False
def get_state(self, actor_id):
print actor_id
print self.cbpi
class SensorCore(object):
key = "sensor_types"
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache["sensors"] = {}
self.cbpi.cache["sensor_instances"] = {}
self.cbpi.cache["sensor_types"] = {}
def init(self):
for key, value in self.cbpi.cache["sensors"].iteritems():
self.init_one(key)
def init_one(self, id):
try:
sensor = self.cbpi.cache["sensors"][id]
clazz = self.cbpi.cache[self.key].get(sensor.type)["class"]
cfg = sensor.config.copy()
cfg.update(dict(cbpi=self.cbpi, id=id))
self.cbpi.cache["sensors"][id].instance = clazz(**cfg)
self.cbpi.cache["sensors"][id].instance.init()
print self.cbpi.cache["sensors"][id].instance
self.cbpi.emit("INIT_SENSOR", id=id)
def job(obj):
obj.execute()
t = self.cbpi._socketio.start_background_task(target=job, obj=self.cbpi.cache["sensors"][id].instance)
self.cbpi.emit("INIT_SENSOR", id=id)
except Exception as e:
print "ERROR"
self.cbpi._app.logger.error(e)
def stop_one(self, id):
print "OBJ", self.cbpi.cache["sensors"][id]
self.cbpi.cache["sensors"][id].instance.stop()
self.cbpi.emit("STOP_SENSOR", id=id)
def get_value(self, sensorid):
try:
return self.cbpi.cache["sensors"][sensorid].instance.value
except:
return None
def get_state(self, actor_id):
print actor_id
print self.cbpi
def write_log(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)
class BrewingCore(object):
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache["step_types"] = {}
self.cbpi.cache["controller_types"] = {}
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:
text = text.encode("utf-8")
file.write("%s,%s\n" % (formatted_time, text))
def get_controller(self, name):
return self.cbpi.cache["controller_types"].get(name)
def set_target_temp(self, id, temp):
self.cbpi.cache.get("kettle")[id].target_temp = float(temp)
Kettle.update(**self.cbpi.cache.get("kettle")[id].__dict__)
self.cbpi.ws_emit("UPDATE_KETTLE_TARGET_TEMP", {"id": id, "target_temp": temp})
self.cbpi.emit("SET_KETTLE_TARGET_TEMP", id=id, temp=temp)
class FermentationCore(object):
def __init__(self, cbpi):
self.cbpi = cbpi
self.cbpi.cache["fermenter"] = {}
self.cbpi.cache["fermentation_controller_types"] = {}
def get_controller(self, name):
return self.cbpi.cache["fermentation_controller_types"].get(name)
class CraftBeerPI(object):
cache = {}
eventbus = {}
def __init__(self):
FORMAT = '%(asctime)-15s - %(levelname)s - %(message)s'
logging.basicConfig(filename='./logs/app.log', level=logging.INFO, format=FORMAT)
self.cache["messages"] = []
self.modules = {}
self.cache["users"] = {'manuel': {'pw': 'secret'}}
self.addon = Addon(self)
self.actor = ActorCore(self)
self.sensor = SensorCore(self)
self.brewing = BrewingCore(self)
self.fermentation = FermentationCore(self)
self._app = Flask(__name__)
self._app.secret_key = 'Cr4ftB33rP1'
self._app.json_encoder = ComplexEncoder
self._socketio = SocketIO(self._app, json=json, logging=False)
@self._app.route('/')
def index():
return redirect('ui')
def run(self):
self.__init_db()
self.loadPlugins()
self.addon.init()
self.sensor.init()
self.actor.init()
self.beep()
self._socketio.run(self._app, host='0.0.0.0', port=5000)
def beep(self):
self.buzzer.beep()
def sleep(self, seconds):
self._socketio.sleep(seconds)
def notify(self, headline, message, type="success", timeout=5000):
msg = {"id": str(uuid.uuid1()), "type": type, "headline": headline, "message": message, "timeout": timeout}
self.ws_emit("NOTIFY", msg)
def ws_emit(self, key, data):
self._socketio.emit(key, data, namespace='/brew')
def __init_db(self, ):
print "INIT DB"
with self._app.app_context():
db = self.get_db()
try:
with self._app.open_resource('../../config/schema.sql', mode='r') as f:
db.cursor().executescript(f.read())
db.commit()
except Exception as e:
print e
pass
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 get_db(self):
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
def add_cache_callback(self, key, method):
method.callback = True
self.cache[key] = method
def get_config_parameter(self, key, default):
cfg = self.cache["config"].get(key)
if cfg is None:
return default
else:
return cfg.value
def emit(self, key, **kwargs):
print key, kwargs
if self.eventbus.get(key) is not None:
for value in self.eventbus[key]:
if value["async"] is False:
value["function"](**kwargs)
else:
t = self.cbpi._socketio.start_background_task(target=value["function"], **kwargs)
def loadPlugins(self):
for filename in os.listdir("./modules/plugins"):
print filename
if os.path.isdir("./modules/plugins/" + filename) is False:
continue
try:
self.modules[filename] = import_module("modules.plugins.%s" % (filename))
except Exception as e:
print e
self.notify("Failed to load plugin %s " % filename, str(e), type="danger", timeout=None)
cbpi = CraftBeerPI()
addon = cbpi.addon

+ 133
- 134
modules/core/db.py 查看文件

@@ -1,134 +1,133 @@
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.'%s'" % (cls.__table_name__,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()


if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__):
query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % (
cls.__table_name__,
cls.__priamry_key__,
', '.join("'%s'" % str(x) for x in cls.__fields__),
', '.join(['?'] * len(cls.__fields__)))
data = ()
data = data + (kwargs.get(cls.__priamry_key__),)
for f in cls.__fields__:
if f in cls.__json_fields__:
data = data + (json.dumps(kwargs.get(f)),)
else:
data = data + (kwargs.get(f),)
else:

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)
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.'%s'" % (cls.__table_name__,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()
if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__):
query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % (
cls.__table_name__,
cls.__priamry_key__,
', '.join("'%s'" % str(x) for x in cls.__fields__),
', '.join(['?'] * len(cls.__fields__)))
data = ()
data = data + (kwargs.get(cls.__priamry_key__),)
for f in cls.__fields__:
if f in cls.__json_fields__:
data = data + (json.dumps(kwargs.get(f)),)
else:
data = data + (kwargs.get(f),)
else:
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
- 47
modules/core/db_mirgrate.py 查看文件

@@ -1,47 +0,0 @@
import sqlite3
import os
from modules import cbpi
from db import get_db

def execute_file(curernt_version, data):
if curernt_version >= data["version"]:
cbpi.app.logger.info("SKIP DB FILE: %s" % data["file"])
return
try:
with sqlite3.connect("craftbeerpi.db") as conn:
with open('./update/%s' % data["file"], 'r') as f:
d = f.read()
sqlCommands = d.split(";")
cur = conn.cursor()
for s in sqlCommands:
cur.execute(s)
cur.execute("INSERT INTO schema_info (version,filename) values (?,?)", (data["version"], data["file"]))
conn.commit()

except sqlite3.OperationalError as err:
print "EXCEPT"
print err

@cbpi.initalizer(order=-9999)
def init(app=None):

with cbpi.app.app_context():
conn = get_db()
cur = conn.cursor()
current_version = None
try:
cur.execute("SELECT max(version) as m FROM schema_info")
m = cur.fetchone()
current_version = m["m"]
except:
pass
result = []
for filename in os.listdir("./update"):
if filename.endswith(".sql"):
d = {"version": int(filename[:filename.index('_')]), "file": filename}
result.append(d)
execute_file(current_version, d)





+ 0
- 108
modules/core/hardware.py 查看文件

@@ -1,108 +0,0 @@
# -*- coding: utf-8 -*-

class Base(object):
__dirty = False

@classmethod
def init_global(cls):
pass

def get_config_parameter(self, key, default_value):
return self.api.get_config_parameter(key, default_value)

def sleep(self, seconds):
self.api.socketio.sleep(seconds)

def init(self):
pass

def stop(self):
pass

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

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):
pass

def on(self, power=0):
pass

def off(self):
pass

+ 36
- 0
modules/core/login.py 查看文件

@@ -0,0 +1,36 @@
import flask_login
from modules.core.core import cbpi, addon
class User(flask_login.UserMixin):
pass
@addon.core.initializer(order=0)
def log(cbpi):
cbpi._login_manager = flask_login.LoginManager()
cbpi._login_manager.init_app(cbpi._app)
@cbpi._app.route('/login', methods=['GET', 'POST'])
def login():
user = User()
user.id = "manuel"
flask_login.login_user(user)
return "OK"
@cbpi._app.route('/logout')
def logout():
flask_login.logout_user()
return 'Logged out'
@cbpi._login_manager.user_loader
def user_loader(email):
if email not in cbpi.cache["users"]:
return
user = User()
user.id = email
return user
@cbpi._login_manager.unauthorized_handler
def unauthorized_handler():
return 'Unauthorized :-('

modules/core/props.py → modules/core/proptypes.py 查看文件

@@ -1,68 +1,48 @@
class PropertyType(object):
pass

class Property(object):
class Select(PropertyType):
def __init__(self, label, options, description=""):
PropertyType.__init__(self)
self.label = label
self.options = options
self.description = description

class Number(PropertyType):
def __init__(self, label, configurable=False, default_value=None, unit="", description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = configurable
self.default_value = default_value
self.description = description

class Text(PropertyType):
def __init__(self, label, configurable=False, default_value="", description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = configurable
self.default_value = default_value
self.description = description

class Actor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description

class Sensor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description

class Kettle(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description


class StepProperty(Property):
class Actor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Sensor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Kettle(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class PropertyType(object):
pass
class Property(object):
class Select(PropertyType):
def __init__(self, label, options, description=""):
PropertyType.__init__(self)
self.label = label
self.options = options
self.description = description
class Number(PropertyType):
def __init__(self, label, configurable=False, default_value=None, unit="", description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = configurable
self.default_value = default_value
self.description = description
class Text(PropertyType):
def __init__(self, label, configurable=False, required=False, default_value="", description=""):
PropertyType.__init__(self)
self.label = label
self.required = required
self.configurable = configurable
self.default_value = default_value
self.description = description
class Actor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Sensor(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description
class Kettle(PropertyType):
def __init__(self, label, description=""):
PropertyType.__init__(self)
self.label = label
self.configurable = True
self.description = description

+ 0
- 146
modules/core/step.py 查看文件

@@ -1,146 +0,0 @@
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))

@cbpi.try_catch(None)
def actor_power(self, id, power):
self.api.actor_power(int(id), power)

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 = float(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 timer_remaining(self):
if self.timer_end is not None:
return self.timer_end - int(time.time())
else:
return None

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):
pass

def finish(self):
pass

def reset(self):
pass

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)


+ 0
- 0
modules/database/__init__.py 查看文件


+ 134
- 0
modules/database/dbmodel.py 查看文件

@@ -0,0 +1,134 @@
from modules.core.db import DBModel, get_db


class Kettle(DBModel):
__fields__ = ["name","sensor", "heater", "automatic", "logic", "config", "agitator", "target_temp"]
__table_name__ = "kettle"
__json_fields__ = ["config"]

class Sensor(DBModel):
__fields__ = ["name","type", "config", "hide"]
__table_name__ = "sensor"
__json_fields__ = ["config"]

class Config(DBModel):
__fields__ = ["type", "value", "description", "options"]
__table_name__ = "config"
__json_fields__ = ["options"]
__priamry_key__ = "name"

class Actor(DBModel):
__fields__ = ["name","type", "config", "hide"]
__table_name__ = "actor"
__json_fields__ = ["config"]

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, order=True):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,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()

@classmethod
def sort(cls, new_order):
cur = get_db().cursor()

for e in new_order:

cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (e[1], e[0]))
get_db().commit()


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()

+ 33
- 83
modules/fermenter/__init__.py 查看文件

@@ -2,63 +2,12 @@ import time
from flask import request from flask import request
from flask_classy import route from flask_classy import route


from modules import DBModel, cbpi, get_db
from modules.core.core import cbpi
from modules.core.db import get_db, DBModel
from modules.core.baseview import BaseView from modules.core.baseview import BaseView
from modules.database.dbmodel import Fermenter, FermenterStep




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): class FermenterView(BaseView):
model = Fermenter model = Fermenter
cache_key = "fermenter" cache_key = "fermenter"
@@ -86,7 +35,7 @@ class FermenterView(BaseView):
temp = float(temp) temp = float(temp)
cbpi.cache.get(self.cache_key)[id].target_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__) self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
cbpi.emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp})
cbpi.ws_emit("UPDATE_FERMENTER_TARGET_TEMP", {"id": id, "target_temp": temp})
return ('', 204) return ('', 204)


@route('/<int:id>/brewname', methods=['POST']) @route('/<int:id>/brewname', methods=['POST'])
@@ -95,7 +44,7 @@ class FermenterView(BaseView):
brewname = data.get("brewname") brewname = data.get("brewname")
cbpi.cache.get(self.cache_key)[id].brewname = brewname cbpi.cache.get(self.cache_key)[id].brewname = brewname
self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__) self.model.update(**self.api.cache.get(self.cache_key)[id].__dict__)
cbpi.emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname})
cbpi.ws_emit("UPDATE_FERMENTER_BREWNAME", {"id": id, "brewname": brewname})
return ('', 204) return ('', 204)


@classmethod @classmethod
@@ -119,7 +68,7 @@ class FermenterView(BaseView):


cbpi.cache.get(self.cache_key)[id].steps.append(f) cbpi.cache.get(self.cache_key)[id].steps.append(f)


cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])


return ('', 204) return ('', 204)


@@ -141,7 +90,7 @@ class FermenterView(BaseView):


FermenterStep.update(**s.__dict__) FermenterStep.update(**s.__dict__)
break break
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)


@route('/<int:id>/step/<int:stepid>', methods=["DELETE"]) @route('/<int:id>/step/<int:stepid>', methods=["DELETE"])
@@ -152,7 +101,7 @@ class FermenterView(BaseView):
del cbpi.cache.get(self.cache_key)[id].steps[idx] del cbpi.cache.get(self.cache_key)[id].steps[idx]
FermenterStep.delete(s.id) FermenterStep.delete(s.id)
break break
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)


@route('/<int:id>/start', methods=['POST']) @route('/<int:id>/start', methods=['POST'])
@@ -179,7 +128,8 @@ class FermenterView(BaseView):


if inactive is not None: if inactive is not None:
fermenter = self.get_fermenter(inactive.fermenter_id) fermenter = self.get_fermenter(inactive.fermenter_id)
current_temp = cbpi.get_sensor_value(int(fermenter.sensor))

current_temp = cbpi.sensor.get_value(int(fermenter.sensor))


inactive.state = 'A' inactive.state = 'A'
inactive.start = time.time() inactive.start = time.time()
@@ -190,7 +140,7 @@ class FermenterView(BaseView):


cbpi.cache["fermenter_task"][id] = inactive cbpi.cache["fermenter_task"][id] = inactive


cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)


@route('/<int:id>/reset', methods=["POST"]) @route('/<int:id>/reset', methods=["POST"])
@@ -202,7 +152,7 @@ class FermenterView(BaseView):
if id in cbpi.cache["fermenter_task"]: if id in cbpi.cache["fermenter_task"]:
del cbpi.cache["fermenter_task"][id] del cbpi.cache["fermenter_task"][id]


cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
return ('', 204) return ('', 204)


@route('/<int:id>/automatic', methods=['POST']) @route('/<int:id>/automatic', methods=['POST'])
@@ -216,21 +166,23 @@ class FermenterView(BaseView):
cfg = fermenter.config.copy() cfg = fermenter.config.copy()
cfg.update( cfg.update(
dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor)) dict(api=cbpi, fermenter_id=fermenter.id, heater=fermenter.heater, sensor=fermenter.sensor))
instance = cbpi.get_fermentation_controller(fermenter.logic).get("class")(**cfg)
instance = cbpi.fermentation.get_controller(fermenter.logic).get("class")(**cfg)
instance.init() instance.init()
fermenter.instance = instance fermenter.instance = instance


def run(instance): def run(instance):
instance.run() instance.run()


t = cbpi.socketio.start_background_task(target=run, instance=instance)
t = cbpi._socketio.start_background_task(target=run, instance=instance)
fermenter.state = not fermenter.state fermenter.state = not fermenter.state
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
cbpi.emit("FERMENTER_CONTROLLER_STARTED", id=id)
else: else:
# Stop controller # Stop controller
fermenter.instance.stop() fermenter.instance.stop()
fermenter.state = not fermenter.state fermenter.state = not fermenter.state
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))
cbpi.emit("FERMENTER_CONTROLLER_STOPPED", id=id)


except Exception as e: except Exception as e:
print e print e
@@ -255,7 +207,7 @@ class FermenterView(BaseView):


step.timer_start = target_time step.timer_start = target_time


cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])
cbpi.ws_emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])


def check_step(self): def check_step(self):
for key, value in cbpi.cache["fermenter_task"].iteritems(): for key, value in cbpi.cache["fermenter_task"].iteritems():
@@ -282,35 +234,33 @@ class FermenterView(BaseView):
pass pass




@cbpi.backgroundtask(key="read_target_temps_fermenter", interval=5)
def read_target_temps(api):
@cbpi.addon.core.backgroundjob(key="read_target_temps_fermenter", interval=5)
def read_target_temps(cbpi):
""" """
background process that reads all passive sensors in interval of 1 second background process that reads all passive sensors in interval of 1 second
:return: None :return: None
""" """
result = {}
for key, value in cbpi.cache.get("fermenter").iteritems(): for key, value in cbpi.cache.get("fermenter").iteritems():
cbpi.save_to_file(key, value.target_temp, prefix="fermenter")
cbpi.sensor.write_log(key, value.target_temp, prefix="fermenter")





instance = FermenterView() instance = FermenterView()


@cbpi.backgroundtask(key="fermentation_task", interval=1)
def execute_fermentation_step(api):
with cbpi.app.app_context():
@cbpi.addon.core.backgroundjob(key="fermentation_task", interval=1)
def execute_fermentation_step(cbpi):
with cbpi._app.app_context():
instance.check_step() instance.check_step()




def init_active_steps(): 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)
pass



@cbpi.addon.core.initializer(order=1)
def init(cbpi): def init(cbpi):


FermenterView.register(cbpi.app, route_base='/api/fermenter')
cbpi.cache["fermenter_task"] = {}
FermenterView.register(cbpi._app, route_base='/api/fermenter')
FermenterView.init_cache() FermenterView.init_cache()

+ 264
- 96
modules/kettle/__init__.py 查看文件

@@ -1,96 +1,264 @@
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

def _pre_put_callback(self, m):
try:
m.instance.stop()
except:
pass

def _post_put_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.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.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
'''

Kettle2View().postTargetTemp(id,temp)

@cbpi.backgroundtask(key="read_target_temps", interval=5)
def read_target_temps(api):
"""
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()
from flask import request
from flask_classy import FlaskView, route
from modules.core.core import cbpi
from modules.core.baseview import BaseView
from modules.core.db import DBModel
from modules.database.dbmodel import Kettle
class KettleView(BaseView):
model = Kettle
cache_key = "kettle"
@route('/', methods=["GET"])
def getAll(self):
"""
Get all Kettles
---
tags:
- kettle
responses:
200:
description: List auf all Kettles
"""
return super(KettleView, self).getAll()
@route('/', methods=["POST"])
def post(self):
"""
Create a new kettle
---
tags:
- kettle
parameters:
- in: body
name: body
schema:
id: Kettle
required:
- name
properties:
name:
type: string
description: name for user
sensor:
type: string
description: name for user
heater:
type: string
description: name for user
automatic:
type: string
description: name for user
logic:
type: string
description: name for user
config:
type: string
description: name for user
agitator:
type: string
description: name for user
target_temp:
type: string
description: name for user
responses:
200:
description: User created
"""
return super(KettleView, self).post()
@route('/<int:id>', methods=["PUT"])
def put(self, id):
"""
Update a kettle
---
tags:
- kettle
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the Kettle
- in: body
name: body
schema:
id: Kettle
required:
- name
properties:
name:
type: string
description: name for user
sensor:
type: string
description: name for user
heater:
type: string
description: name for user
automatic:
type: string
description: name for user
logic:
type: string
description: name for user
config:
type: string
description: name for user
agitator:
type: string
description: name for user
target_temp:
type: string
description: name for user
responses:
200:
description: User created
"""
return super(KettleView, self).put(id)
@route('/<int:id>', methods=["DELETE"])
def delete(self, id):
"""
Delete a kettle
---
tags:
- kettle
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the Kettle
responses:
200:
description: User created
"""
return super(KettleView, self).delete(id)
@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
def _pre_put_callback(self, m):
try:
m.instance.stop()
except:
pass
def _post_put_callback(self, m):
m.state = False
@route('/<int:id>/targettemp/<temp>', methods=['POST'])
def postTargetTemp(self, id, temp):
"""
Set Target Temp
---
tags:
- kettle
parameters:
- required: true
type: string
description: ID of pet to return
in: path
name: id
- required: true
type: string
description: Temperature you like to set
in: path
name: temp
responses:
201:
description: User created
"""
id = int(id)
temp = float(temp)
cbpi.brewing.set_target_temp(id, temp)
return ('', 204)
@route('/<int:id>/automatic', methods=['POST'])
def toggle(self, id):
"""
Set Target Temp
---
tags:
- kettle
parameters:
- required: true
type: string
description: ID of pet to return
in: path
name: id
- required: true
type: string
description: Temperature you like to set
in: path
name: temp
responses:
201:
description: User created
"""
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.brewing.get_controller(kettle.logic).get("class")(**cfg)
instance.init()
kettle.instance = instance
def run(instance):
instance.run()
t = self.api._socketio.start_background_task(target=run, instance=instance)
kettle.state = not kettle.state
cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
cbpi.emit("KETTLE_CONTROLLER_STARTED", id=id)
else:
# Stop controller
kettle.instance.stop()
kettle.state = not kettle.state
cbpi.ws_emit("UPDATE_KETTLE", cbpi.cache.get("kettle").get(id))
cbpi.emit("KETTLE_CONTROLLER_STOPPED", id=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
'''
KettleView().postTargetTemp(id, temp)
@cbpi.addon.core.backgroundjob(key="read_target_temps", interval=5)
def read_target_temps(api):
"""
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.sensor.write_log(key, value.target_temp, prefix="kettle")
@cbpi.addon.core.initializer()
def init(cbpi):
KettleView.api = cbpi
KettleView.register(cbpi._app, route_base='/api/kettle')
KettleView.init_cache()

+ 175
- 1
modules/logs/__init__.py 查看文件

@@ -1 +1,175 @@
import endpoints
import datetime
import os
from flask import Blueprint, request, send_from_directory, json
from flask_classy import FlaskView, route
from modules.core.core import cbpi
class LogView(FlaskView):
@route('/', methods=['GET'])
def get_all_logfiles(self):
"""
Get a list of all Log Files
---
tags:
- logs
responses:
200:
description: List of all log files
"""
result = []
for filename in os.listdir("./logs"):
if filename.endswith(".log"):
result.append(filename)
return json.dumps(result)
@route('/actions')
def actions(self):
"""
Get a list of all brewing actions
---
tags:
- logs
responses:
200:
description: List of all log files
"""
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):
"""
Delete a log file by name
---
tags:
- logs
parameters:
- in: path
name: file
schema:
type: string
required: true
description: File name
responses:
204:
description: Log deleted
"""
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", "")
return ('', 204)
else:
cbpi.notify("Failed to delete log", "", type="danger")
return ('', 404)
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
def convert_chart_data_to_json(self, chart_data):
return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])}
@route('/<t>/<int:id>', methods=["POST"])
def get_logs_as_json(self, t, id):
"""
Get Log as json
---
tags:
- logs
parameters:
- in: path
name: id
schema:
type: string
required: true
description: id of the file
responses:
200:
description: Log File Data
"""
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 = map(self.convert_chart_data_to_json, cbpi.brewing.get_controller(kettle.logic).get("class").chart(kettle))
if t == "f":
fermenter = cbpi.cache.get("fermenter").get(id)
result = map(self.convert_chart_data_to_json, cbpi.fermentation.get_controller(fermenter.logic).get("class").chart(fermenter))
return json.dumps(result)
@route('/download/<file>')
@cbpi.nocache
def download(self, file):
"""
Download a log file by name
---
tags:
- logs
parameters:
- in: path
name: file
schema:
type: string
required: true
description: filename
responses:
200:
description: Log file downloaded
"""
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.addon.core.initializer()
def init(cbpi):
"""
Initializer for the message module
:param app: the flask app
:return: None
"""
LogView.register(cbpi._app, route_base='/api/logs')

+ 0
- 109
modules/logs/endpoints.py 查看文件

@@ -1,109 +0,0 @@
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

def convert_chart_data_to_json(self, chart_data):
return {"name": chart_data["name"], "data": self.read_log_as_json(chart_data["data_type"], chart_data["data_id"])}

@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 = map(self.convert_chart_data_to_json, cbpi.get_controller(kettle.logic).get("class").chart(kettle))

if t == "f":
fermenter = cbpi.cache.get("fermenter").get(id)
result = map(self.convert_chart_data_to_json, cbpi.get_fermentation_controller(fermenter.logic).get("class").chart(fermenter))

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
"""
LogView.register(cbpi.app, route_base='/api/logs')

+ 26
- 10
modules/notification/__init__.py 查看文件

@@ -1,30 +1,46 @@
import json import json
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from modules import cbpi
from modules.core.core import cbpi

class NotificationView(FlaskView): class NotificationView(FlaskView):


@route('/', methods=['GET']) @route('/', methods=['GET'])
def getMessages(self): def getMessages(self):
""" """
Get all messages
:return: current messages
Get all Messages
---
tags:
- notification
responses:
200:
description: All messages
""" """

return json.dumps(cbpi.cache["messages"]) return json.dumps(cbpi.cache["messages"])


@route('/<id>', methods=['DELETE']) @route('/<id>', methods=['DELETE'])
def dismiss(self, id): def dismiss(self, id):
""" """
Delete message from cache by id
:param id: message id to be deleted
:return: empty response HTTP 204
Delete Message
---
tags:
- notification
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the message
responses:
200:
description: Message deleted
""" """
for idx, m in enumerate(cbpi.cache.get("messages", [])): for idx, m in enumerate(cbpi.cache.get("messages", [])):
if (m.get("id") == id): if (m.get("id") == id):
cbpi.cache["messages"].pop(idx) cbpi.cache["messages"].pop(idx)
return ('', 204) return ('', 204)


@cbpi.event("MESSAGE", async=True)
#@cbpi.event("MESSAGE", async=True)
def messageEvent(message, **kwargs): def messageEvent(message, **kwargs):
""" """
React on message event. add the message to the cache and push the message to the clients React on message event. add the message to the cache and push the message to the clients
@@ -36,7 +52,7 @@ def messageEvent(message, **kwargs):
cbpi.cache["messages"].append(message) cbpi.cache["messages"].append(message)
cbpi.emit("NOTIFY", message) cbpi.emit("NOTIFY", message)


@cbpi.initalizer(order=2)
@cbpi.addon.core.initializer(order=2)
def init(cbpi): def init(cbpi):
""" """
Initializer for the message module Initializer for the message module
@@ -47,4 +63,4 @@ def init(cbpi):
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} 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) cbpi.cache["messages"].append(msg)


NotificationView.register(cbpi.app, route_base='/api/notification')
NotificationView.register(cbpi._app, route_base='/api/notification')

+ 138
- 0
modules/plugin/__init__.py 查看文件

@@ -0,0 +1,138 @@
import sys
from flask import request, send_from_directory, json
from importlib import import_module
from modules.core.core import cbpi
from git import Repo
import os
import requests
import yaml
import shutil
from flask_classy import FlaskView, route
modules = {}
class PluginView(FlaskView):
def merge(self, 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, {})
self.merge(value, node)
else:
destination[key] = value
return destination
@route('/', methods=['GET'])
def get(self):
"""
Get Plugin List
---
tags:
- plugin
responses:
200:
description: List of all plugins
"""
response = requests.get("https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins.yaml")
self.api.cache["plugins"] = self.merge(yaml.load(response.text), self.api.cache["plugins"])
for key, value in cbpi.cache["plugins"].iteritems():
value["installed"] = os.path.isdir("./plugins/%s/" % (key))
return json.dumps(cbpi.cache["plugins"])
@route('/<name>', methods=['DELETE'])
def delete(self,name):
"""
Delete Plugin
---
tags:
- plugin
parameters:
- in: path
name: name
schema:
type: string
required: true
description: Plugin name
responses:
200:
description: Plugin deleted
"""
if os.path.isdir("./plugins/"+name) is False:
return ('Dir not found', 500)
shutil.rmtree("./plugins/"+name)
cbpi.notify("Plugin deleted", "Plugin %s deleted successfully" % name)
return ('', 204)
@route('/<name>/download', methods=['POST'])
def download(self, name):
"""
Download Plugin
---
tags:
- plugin
parameters:
- in: path
name: name
schema:
type: string
required: true
description: Plugin name
responses:
200:
description: Plugin downloaded
"""
plugin = self.api.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))
self.api.notify("Download successful", "Plugin %s downloaded successfully" % name)
finally:
plugin["loading"] = False
return ('', 204)
@route('/<name>/update', methods=['POST'])
def update(self, name):
"""
Pull Plugin Update
---
tags:
- plugin
parameters:
- in: path
name: name
schema:
type: string
required: true
description: Plugin name
responses:
200:
description: Plugin updated
"""
repo = Repo("./modules/plugins/%s/" % (name))
o = repo.remotes.origin
info = o.pull()
self.api.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name)
return ('', 204)
@cbpi.addon.core.initializer()
def init(cbpi):
cbpi.cache["plugins"] = {}
PluginView.api = cbpi
PluginView.register(cbpi._app, route_base='/api/plugin')

+ 41
- 4
modules/recipe_import/beerxml.py 查看文件

@@ -2,18 +2,28 @@ from flask import json, request
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from git import Repo, Git from git import Repo, Git
import sqlite3 import sqlite3
from modules.app_config import cbpi
from modules.core.core import cbpi
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import pprint import pprint
import time import time
import os import os
from modules.steps import Step,StepView
from modules.step import Step,StepView
import xml.etree.ElementTree import xml.etree.ElementTree


class BeerXMLImport(FlaskView): class BeerXMLImport(FlaskView):
BEER_XML_FILE = "./upload/beer.xml" BEER_XML_FILE = "./upload/beer.xml"
@route('/', methods=['GET']) @route('/', methods=['GET'])
def get(self): def get(self):

"""
Get BeerXML
---
tags:
- beerxml
responses:
200:
description: BeerXML file stored in CraftBeerPI
"""
if not os.path.exists(self.BEER_XML_FILE): if not os.path.exists(self.BEER_XML_FILE):
self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File", self.api.notify(headline="File Not Found", message="Please upload a Beer.xml File",
type="danger") type="danger")
@@ -31,6 +41,16 @@ class BeerXMLImport(FlaskView):


@route('/upload', methods=['POST']) @route('/upload', methods=['POST'])
def upload_file(self): def upload_file(self):
"""
Upload BeerXML File
---
tags:
- beerxml
responses:
200:
description: BeerXML File Uploaded
"""
try: try:
if request.method == 'POST': if request.method == 'POST':
file = request.files['file'] file = request.files['file']
@@ -46,6 +66,23 @@ class BeerXMLImport(FlaskView):
@route('/<int:id>', methods=['POST']) @route('/<int:id>', methods=['POST'])
def load(self, id): def load(self, id):


"""
Load Recipe from BeerXML
---
tags:
- beerxml
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Recipe ID from BeerXML

responses:
200:
description: Recipe loaed
"""


steps = self.getSteps(id) steps = self.getSteps(id)
name = self.getRecipeName(id) name = self.getRecipeName(id)
@@ -103,8 +140,8 @@ class BeerXMLImport(FlaskView):


return steps return steps


@cbpi.initalizer()
@cbpi.addon.core.initializer()
def init(cbpi): def init(cbpi):


BeerXMLImport.api = cbpi BeerXMLImport.api = cbpi
BeerXMLImport.register(cbpi.app, route_base='/api/beerxml')
BeerXMLImport.register(cbpi._app, route_base='/api/beerxml')

+ 41
- 4
modules/recipe_import/kbh.py 查看文件

@@ -2,18 +2,28 @@ from flask import json, request
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from git import Repo, Git from git import Repo, Git
import sqlite3 import sqlite3
from modules.app_config import cbpi
from modules.core.core import cbpi
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import pprint import pprint
import time import time
import os import os
from modules.steps import Step, StepView
from modules.step import Step, StepView




class KBH(FlaskView): class KBH(FlaskView):


@route('/', methods=['GET']) @route('/', methods=['GET'])
def get(self): def get(self):

"""
Get all recipes from uploaded kleinerbrauhelfer database
---
tags:
- kleinerbrauhelfer
responses:
200:
description: Recipes from kleinerbrauhelfer database
"""
conn = None conn = None
try: try:
if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'): if not os.path.exists(self.api.app.config['UPLOAD_FOLDER'] + '/kbh.db'):
@@ -41,6 +51,16 @@ class KBH(FlaskView):


@route('/upload', methods=['POST']) @route('/upload', methods=['POST'])
def upload_file(self): def upload_file(self):
"""
Upload KleinerBrauhelfer Database File
---
tags:
- kleinerbrauhelfer
responses:
200:
description: File uploaed
"""
try: try:
if request.method == 'POST': if request.method == 'POST':
file = request.files['file'] file = request.files['file']
@@ -57,6 +77,23 @@ class KBH(FlaskView):


@route('/<int:id>', methods=['POST']) @route('/<int:id>', methods=['POST'])
def load(self, id): def load(self, id):
"""
Load Recipe from Kleinerbrauhelfer Database
---
tags:
- kleinerbrauhelfer
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of recipe

responses:
200:
description: Recipe loaded
"""
mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep") mashstep_type = cbpi.get_config_parameter("step_mash", "MashStep")
mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep") mashinstep_type = cbpi.get_config_parameter("step_mashin", "MashInStep")
chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep") chilstep_type = cbpi.get_config_parameter("step_chil", "ChilStep")
@@ -102,8 +139,8 @@ class KBH(FlaskView):






@cbpi.initalizer()
@cbpi.addon.core.initializer()
def init(cbpi): def init(cbpi):


KBH.api = cbpi KBH.api = cbpi
KBH.register(cbpi.app, route_base='/api/kbh')
KBH.register(cbpi._app, route_base='/api/kbh')

+ 4
- 4
modules/recipe_import/restapi.py 查看文件

@@ -2,12 +2,12 @@ from flask import json, request
from flask_classy import FlaskView, route from flask_classy import FlaskView, route
from git import Repo, Git from git import Repo, Git
import sqlite3 import sqlite3
from modules.app_config import cbpi
from modules.core.core import cbpi
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import pprint import pprint
import time import time
import os import os
from modules.steps import Step,StepView
from modules.step import Step,StepView
import xml.etree.ElementTree import xml.etree.ElementTree




@@ -56,7 +56,7 @@ class RESTImport(FlaskView):
return ('', 204) return ('', 204)




@cbpi.initalizer()
@cbpi.addon.core.initializer()
def init(cbpi): def init(cbpi):
RESTImport.api = cbpi RESTImport.api = cbpi
RESTImport.register(cbpi.app, route_base='/api/recipe/import/v1')
RESTImport.register(cbpi._app, route_base='/api/recipe/import/v1')

+ 67
- 0
modules/sensor/__init__.py 查看文件

@@ -0,0 +1,67 @@
import time
from flask_classy import route
from modules.core.core import cbpi
from modules.core.db import DBModel
from modules.core.baseview import BaseView
from modules.database.dbmodel import Sensor


class SensorView(BaseView):
model = Sensor
cache_key = "sensors"

@route('<int:id>/action/<method>', methods=["POST"])
def action(self, id, method):
"""
Sensor Action
---
tags:
- sensor
parameters:
- in: path
name: id
schema:
type: integer
required: true
description: Numeric ID of the sensor
- in: path
name: method
schema:
type: string
required: true
description: action method name
responses:
200:
description: Sensor Action called
"""
cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)()
return ('', 204)

def _post_post_callback(self, m):

cbpi.sensor.init_one(m.id)

def _post_put_callback(self, m):
cbpi.sensor.stop_one(m.id)
cbpi.sensor.init_one(m.id)

def _pre_delete_callback(self, m):
cbpi.sensor.stop_one(m.id)

@cbpi.addon.core.initializer(order=1000)
def init(cbpi):

SensorView.register(cbpi._app, route_base='/api/sensor')
SensorView.init_cache()


#@cbpi.backgroundtask(key="read_passiv_sensor", interval=5)
def read_passive_sensor(api):
"""
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
- 48
modules/sensors/__init__.py 查看文件

@@ -1,48 +0,0 @@
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"

@route('<int:id>/action/<method>', methods=["POST"])
def action(self, id, method):

cbpi.cache.get("sensors").get(id).instance.__getattribute__(method)()
return ('', 204)

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):

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(api):
"""
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()

+ 35
- 36
modules/stats/__init__.py 查看文件

@@ -1,36 +1,35 @@
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):

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
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):
try:
serial = getserial()
info = {
"id": serial,
"version": "3.1",
"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

modules/steps/__init__.py → modules/step/__init__.py 查看文件

@@ -1,239 +1,241 @@
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 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, order=True):
cur = get_db().cursor()
cur.execute("SELECT * FROM %s WHERE state = ? ORDER BY %s.'order'" % (cls.__table_name__,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()

@classmethod
def sort(cls, new_order):
cur = get_db().cursor()

for e in new_order:

cur.execute("UPDATE %s SET '%s' = ? WHERE id = ?" % (cls.__table_name__, "order"), (e[1], e[0]))
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('/sort', methods=["POST"])
def sort_steps(self):
Step.sort(request.json)
cbpi.emit("UPDATE_ALL_STEPS", self.model.get_all())
return ('', 204)

@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()
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, 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):

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(api):
'''
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())
import time
from flask import json, request
from flask_classy import route
from modules.core.db import DBModel
from modules.core.baseview import BaseView
from modules.core.core import cbpi
from modules.database.dbmodel import Step
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('/sort', methods=["POST"])
def sort_steps(self):
"""
Sort all steps
---
tags:
- steps
responses:
204:
description: Steps sorted. Update delivered via web socket
"""
Step.sort(request.json)
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
return ('', 204)
@route('/', methods=["DELETE"])
def deleteAll(self):
"""
Delete all Steps
---
tags:
- steps
responses:
204:
description: All steps deleted
"""
self.model.delete_all()
self.api.emit("ALL_BREWING_STEPS_DELETED")
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
return ('', 204)
@route('/action/<method>', methods=["POST"])
def action(self, method):
"""
Call Step Action
---
tags:
- steps
responses:
204:
description: Step action called
"""
self.api.emit("BREWING_STEP_ACTION_INVOKED", method=method)
cbpi.cache["active_step"].__getattribute__(method)()
return ('', 204)
@route('/reset', methods=["POST"])
def reset(self):
"""
Reset All Steps
---
tags:
- steps
responses:
200:
description: Steps reseted
"""
self.model.reset_all_steps()
self.stop_step()
self.api.emit("ALL_BREWING_STEPS_RESET")
cbpi.ws_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
self.api.emit("BREWING_STEPS_STOP")
if step is not None:
step.finish()
@route('/reset/current', methods=['POST'])
def resetCurrentStep(self):
"""
Reset current Steps
---
tags:
- steps
responses:
200:
description: Current Steps reseted
"""
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()
self.api.emit("BREWING_STEPS_RESET_CURRENT")
cbpi.ws_emit("UPDATE_ALL_STEPS", self.model.get_all())
return ('', 204)
def init_step(self, step):
cbpi.brewing.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):
"""
Next Step
---
tags:
- steps
responses:
200:
description: Next Step
"""
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__)
self.api.emit("BREWING_STEP_DONE")
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__)
self.api.emit("BREWING_STEP_STARTED")
else:
cbpi.brewing.log_action("Brewing Finished")
self.api.emit("BREWING_FINISHED")
cbpi.notify("Brewing Finished", "You are done!", timeout=None)
cbpi.ws_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
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, managed_fields=get_manged_fields_as_array(type_cfg)))
instance = type_cfg.get("class")(**cfg)
instance.init()
cbpi.cache["active_step"] = instance
@cbpi.addon.core.initializer(order=2000)
def init(cbpi):
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.addon.core.backgroundjob(key="step_task", interval=0.1)
def execute_step(api):
'''
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.ws_emit("UPDATE_ALL_STEPS", Step.get_all())
if step.n is True:
StepView().start()
cbpi.ws_emit("UPDATE_ALL_STEPS", Step.get_all())

+ 152
- 1
modules/system/__init__.py 查看文件

@@ -1 +1,152 @@
import endpoints
import yaml
from flask import json, url_for, Response
from flask_classy import FlaskView, route
from git import Repo, Git
from modules.core.core 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):
"""
System Shutdown
---
tags:
- system
responses:
200:
description: Shutdown triggered
"""
self.doShutdown()
return ('', 204)
def doReboot(self):
time.sleep(5)
from subprocess import call
call("reboot")
@route('/reboot', methods=['POST'])
def reboot(self):
"""
System Reboot
---
tags:
- system
responses:
200:
description: Reboot triggered
"""
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):
"""
Check for GIT status
---
tags:
- system
responses:
200:
description: Git Status
"""
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):
"""
Check for GIT update
---
tags:
- system
responses:
200:
description: Git Changes
"""
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):
"""
System Update
---
tags:
- system
responses:
200:
description: Git Pull Triggered
"""
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):
"""
Dump Cache
---
tags:
- system
responses:
200:
description: CraftBeerPi System Cache
"""
return json.dumps(cbpi.cache)
@cbpi.addon.core.initializer()
def init(cbpi):
SystemView.api = cbpi
SystemView.register(cbpi._app, route_base='/api/system')

+ 0
- 145
modules/system/endpoints.py 查看文件

@@ -1,145 +0,0 @@
import yaml
from flask import json, url_for, Response
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)
@route('/endpoints', methods=['GET'])
def endpoints(self):
import urllib
output = []
vf = self.api.app.view_functions
for f in self.api.app.view_functions:
print f
endpoints = {}
re = {
"swagger": "2.0",
"host": "",
"info": {
"description":"",
"version": "",
"title": "CraftBeerPi"
},
"schemes": ["http"],
"paths": endpoints}
for rule in self.api.app.url_map.iter_rules():
r = rule
endpoints[rule.rule] = {}
if "HEAD" in r.methods: r.methods.remove("HEAD")
if "OPTIONS" in r.methods: r.methods.remove("OPTIONS")
for m in rule.methods:
endpoints[rule.rule][m] = dict(summary="", description="", consumes=["application/json"],produces=["application/json"])
with open("config/version.yaml", 'r') as stream:
y = yaml.load(stream)
pprint.pprint(y)
pprint.pprint(re)
return Response(yaml.dump(re), mimetype='text/yaml')
@cbpi.initalizer()
def init(cbpi):
SystemView.api = cbpi
SystemView.register(cbpi.app, route_base='/api/system')

+ 23
- 1
modules/ui/__init__.py 查看文件

@@ -1 +1,23 @@
import endpoints
from flask import Blueprint

from modules.core.core import cbpi

react = Blueprint('ui', __name__, template_folder='templates', static_folder='static')

@cbpi.addon.core.initializer(order=10)
def init(cbpi):
cbpi._app.register_blueprint(react, url_prefix='/ui')


@react.route('/', methods=["GET"])
def index():
return react.send_static_file("index.html")










+ 0
- 25
modules/ui/endpoints.py 查看文件

@@ -1,25 +0,0 @@
from flask import Blueprint

from modules import cbpi

react = Blueprint('react', __name__, template_folder='templates', static_folder='static')

@cbpi.initalizer(order=10)
def init(cbpi):
cbpi.app.register_blueprint(react, url_prefix='/ui')




@react.route('/', methods=["GET"])
def index():
return react.send_static_file("index.html")










+ 48
- 44
modules/ui/static/bundle.js
文件差异内容过多而无法显示
查看文件


+ 1
- 0
modules/ui/static/index.html 查看文件

@@ -17,5 +17,6 @@
<div id="root" ></div> <div id="root" ></div>
<script src="static/bundle.js" type="text/javascript"></script> <script src="static/bundle.js" type="text/javascript"></script>



</body> </body>
</html> </html>

+ 23
- 6
run.py 查看文件

@@ -1,11 +1,28 @@
#!/usr/bin/env python #!/usr/bin/env python


from modules import socketio, app, cbpi
from modules.core.core import *


try:
port = int(cbpi.get_config_parameter('port', '5000'))
except ValueError:
port = 5000
cbpi = CraftBeerPI()


socketio.run(app, host='0.0.0.0', port=port)
addon = cbpi.addon


from modules.buzzer import *
from modules.config import *
from modules.core.login import *
from modules.system import *
from modules.ui import *
from modules.step import *
from modules.kettle import *
from modules.actor import *
from modules.plugin import *
from modules.logs import *
from modules.notification import *
from modules.sensor import *
from modules.recipe_import import *
from modules.fermenter import *
from modules.action import *
from modules.base_plugins.actor import *
from modules.base_plugins.sensor import *
from modules.base_plugins.steps import *

cbpi.run()

正在加载...
取消
保存