Juan Pablo Giménez GitHub pirms 5 gadiem
vecāks
revīzija
155da2fb88
Šim parakstam datu bāzē netika atrasta zināma atslēga GPG atslēgas ID: 4AEE18F83AFDEB23
36 mainītis faili ar 706 papildinājumiem un 418 dzēšanām
  1. +5
    -0
      .gitignore
  2. +25
    -0
      .pre-commit-config.yaml
  3. +2
    -2
      Dockerfile
  4. +69
    -0
      Vagrantfile
  5. +5
    -5
      install.sh
  6. +71
    -71
      modules/__init__.py
  7. +1
    -1
      modules/addon/__init__.py
  8. +28
    -8
      modules/addon/endpoints.py
  9. +6
    -6
      modules/base_plugins/brew_steps/__init__.py
  10. +52
    -27
      modules/base_plugins/dummy_temp/__init__.py
  11. +77
    -27
      modules/base_plugins/gpio_actor/__init__.py
  12. +1
    -1
      modules/base_plugins/one_wire/__init__.py
  13. +1
    -1
      modules/buzzer/__init__.py
  14. +1
    -1
      modules/config/__init__.py
  15. +1
    -1
      modules/core/baseview.py
  16. +1
    -1
      modules/core/controller.py
  17. +25
    -25
      modules/core/core.py
  18. +1
    -1
      modules/core/db.py
  19. +8
    -10
      modules/core/db_mirgrate.py
  20. +3
    -3
      modules/core/hardware.py
  21. +5
    -5
      modules/core/step.py
  22. +5
    -5
      modules/fermenter/__init__.py
  23. +1
    -1
      modules/kettle/__init__.py
  24. +1
    -1
      modules/logs/__init__.py
  25. +116
    -42
      modules/logs/endpoints.py
  26. +1
    -1
      modules/notification/__init__.py
  27. +3
    -3
      modules/recipe_import/__init__.py
  28. +1
    -1
      modules/recipe_import/kbh.py
  29. +1
    -1
      modules/sensors/__init__.py
  30. +1
    -1
      modules/system/__init__.py
  31. +145
    -145
      modules/system/endpoints.py
  32. +1
    -1
      modules/ui/__init__.py
  33. +1
    -1
      modules/ui/static/bundle.js
  34. +6
    -0
      requirements-dev.txt
  35. +27
    -14
      requirements.txt
  36. +8
    -5
      run.py

+ 5
- 0
.gitignore Parādīt failu

@@ -17,3 +17,8 @@ yarn.lock


modules/ui/package-lock.json

.python-version
upload/*
*.bak
.vscode

+ 25
- 0
.pre-commit-config.yaml Parādīt failu

@@ -0,0 +1,25 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files

# - repo: https://github.com/pycqa/pylint
# rev: pylint-2.5.3
# hooks:
# - id: pylint
# stages: [commit]
# additional_dependencies: [pylint-flask]

- repo: local
hooks:
- id: pylint
name: pylint
entry: pylint
language: system
types: [python]

+ 2
- 2
Dockerfile Parādīt failu

@@ -1,5 +1,5 @@
# Dockerfile for development on a pc/mac
FROM python:2
FROM python:3.8

EXPOSE 5000

@@ -11,4 +11,4 @@ RUN pip install --no-cache-dir -r requirements.txt

COPY . .

CMD ["python", "run.py"]
CMD ["python", "run.py"]

+ 69
- 0
Vagrantfile Parādīt failu

@@ -0,0 +1,69 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "debian/buster64"

# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
# config.vm.network "forwarded_port", guest: 80, host: 8080

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
config.vm.network "forwarded_port", guest: 5000, host: 5000, host_ip: "127.0.0.1"

# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"

# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"

# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.

# Enable provisioning with a shell script. Additional provisioners such as
# Ansible, Chef, Docker, Puppet and Salt are also available. Please see the
# documentation for more information about their specific syntax and use.
config.vm.provision "shell", inline: <<-SHELL
sudo /vagrant/install.sh
SHELL
end

+ 5
- 5
install.sh Parādīt failu

@@ -35,11 +35,11 @@ show_menu () {
apt-get -y update; apt-get -y upgrade;
fi

apt-get -y install python-setuptools
easy_install pip
apt-get -y install python-dev
apt-get -y install libpcre3-dev
pip install -r requirements.txt
# apt-get -y install python-setuptools
# easy_install pip
apt-get -y install python3-pip python3-dev python3-rpi.gpio
apt-get -y install libpcre3-dev git
pip3 install -r requirements.txt

confirmAnswer "Would you like to add active 1-wire support at your Raspberry PI now? IMPORTANT: The 1-wire thermometer must be conneted to GPIO 4!"
if [ $? = 0 ]; then


+ 71
- 71
modules/__init__.py Parādīt failu

@@ -1,72 +1,72 @@
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")
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 modules.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("##########################################")

+ 1
- 1
modules/addon/__init__.py Parādīt failu

@@ -1 +1 @@
import endpoints
import modules.addon.endpoints

+ 28
- 8
modules/addon/endpoints.py Parādīt failu

@@ -11,6 +11,7 @@ import os
import requests
import yaml
import shutil
import imp

blueprint = Blueprint('addon', __name__)

@@ -24,7 +25,7 @@ def merge(source, destination):
:param destination:
:return:
"""
for key, value in source.items():
for key, value in list(source.items()):
if isinstance(value, dict):
# get node or create one
node = destination.setdefault(key, {})
@@ -115,7 +116,7 @@ def reload(name):
"""
try:
if name in cache["modules"]:
reload(cache["modules"][name])
imp.reload(cache["modules"][name])
cbpi.emit_message("REALOD OF PLUGIN %s SUCCESSFUL" % (name))
return ('', 204)
else:
@@ -132,9 +133,9 @@ 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():
response = requests.get("https://raw.githubusercontent.com/jpgimenez/craftbeerpi-plugins/master/plugins.yaml")
cbpi.cache["plugins"] = merge(yaml.safe_load(response.text), cbpi.cache["plugins"])
for key, value in cbpi.cache["plugins"].items():
value["installed"] = os.path.isdir("./modules/plugins/%s/" % (key))

return json.dumps(cbpi.cache["plugins"])
@@ -144,9 +145,9 @@ def plugins():
def download_addon(name):

plugin = cbpi.cache["plugins"].get(name)
plugin["loading"] = True
if plugin is None:
return ('', 404)
plugin["loading"] = True
try:
Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name))
cbpi.notify("Download successful", "Plugin %s downloaded successfully" % name)
@@ -157,9 +158,28 @@ def download_addon(name):

@blueprint.route('/<name>/update', methods=['POST'])
def update_addon(name):
"""
Updates a addon

:param name: plugin name
:return: HTTP 204 if ok - HTTP 500 if plugin not exists
"""
plugin = cbpi.cache["plugins"].get(name)
if plugin is None:
return ('', 404)
plugin["loading"] = True

repo = Repo("./modules/plugins/%s/" % (name))
o = repo.remotes.origin
info = o.pull()
if repo.remotes.origin.url == plugin.get('repo_url'):
o = repo.remotes.origin
_info = o.pull()
else:
# url has changed the plugin needs to be re-downloaded
deletePlugin(name)
return download_addon(name)

reload(name)
plugin["loading"] = False
cbpi.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name)
return ('', 204)



+ 6
- 6
modules/base_plugins/brew_steps/__init__.py Parādīt failu

@@ -58,7 +58,7 @@ class MashStep(StepBase):
# 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()
next(self)


@cbpi.step
@@ -121,7 +121,7 @@ class ChilStep(StepBase):
self.start_timer(int(self.timer) * 60)

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

@cbpi.step
class PumpStep(StepBase):
@@ -149,7 +149,7 @@ class PumpStep(StepBase):
self.start_timer(int(self.timer) * 60)

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

@cbpi.step
class BoilStep(StepBase):
@@ -201,8 +201,8 @@ class BoilStep(StepBase):


def check_hop_timer(self, number, value):
if self.__getattribute__("hop_%s_added" % number) is not True and time.time() > (
if isinstance(value, int) and \
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)
@@ -226,4 +226,4 @@ class BoilStep(StepBase):
# 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()
next(self)

+ 52
- 27
modules/base_plugins/dummy_temp/__init__.py Parādīt failu

@@ -1,53 +1,78 @@
# -*- coding: utf-8 -*-
"""
Dummy sensors
"""
import subprocess
import time

from modules import cbpi, socketio
from modules.core.hardware import SensorActive
from modules import cbpi
from modules.core.hardware import SensorActive
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"
"""
Dummy temperature sensor
"""
temp = Property.Number("Temperature",
configurable=True,
default_value=5,
description="Dummy Temperature as decimal value")
inc = Property.Number(
"Auto increase",
configurable=True,
default_value=0.5,
description="Dummy Temperature increase as decimal value")
max_temp = Property.Number(
"Max temperature",
configurable=True,
default_value='100',
description="Dummy Max. Temperature as decimal value")
min_temp = Property.Number(
"Min temperature",
configurable=True,
default_value='0',
description="Dummy Min. Temperature as decimal value")
current_temp = None

@cbpi.action("Reset")
def reset(self):
"""
reset to default temp
"""
self.current_temp = None

@cbpi.action("Toogle Up/Down")
def toogle(self):
"""
toogle inc from up/down
"""
self.inc = float(self.inc) * -1

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

def execute(self):
'''
Active sensor has to handle his own loop
:return:
:return:
'''
while self.is_running() is True:
self.data_received(self.temp)
if not self.current_temp:
self.current_temp = float(self.temp)
self.data_received(self.current_temp)
new_temp = float(self.current_temp) + float(self.inc)
if float(self.min_temp) <= new_temp <= float(self.max_temp):
self.current_temp = '%.2f' % new_temp
self.sleep(5)

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










+ 77
- 27
modules/base_plugins/gpio_actor/__init__.py Parādīt failu

@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
""" base gpio actors """
import time

from modules import cbpi
@@ -6,46 +7,60 @@ from modules.core.hardware import ActorBase, SensorPassive, SensorActive
from modules.core.props import Property

try:
import RPi.GPIO as GPIO
import RPi.GPIO as GPIO # pylint: disable=import-error

GPIO.setmode(GPIO.BCM)
except Exception as e:
print e
pass

except ImportError as exp:
print(exp)


@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")
"""
Simple GPIO Actor
"""
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)
print(("GPIO ON %s" % str(self.gpio)))
GPIO.output(int(self.gpio), 1)

def off(self):
print "GPIO OFF"
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")
"""
GPIO Actor with PWM support
"""
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")
frequency = Property.Number("Frequency (Hz)", configurable=True)

p = None
gpio_inst = None
power = 100 # duty cycle

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)
@@ -53,28 +68,38 @@ class GPIOPWM(ActorBase):
if self.frequency is None:
self.frequency = 0.5 # 2 sec

self.p = GPIO.PWM(int(self.gpio), float(self.frequency))
self.p.start(int(self.power))
if self.gpio_inst is None:
self.gpio_inst = GPIO.PWM(int(self.gpio), float(self.frequency))
self.gpio_inst.start(int(self.power))

def set_power(self, power):
'''
Optional: Set the power of your actor
:param power: int value between 0 - 100
:return:
:return:
'''
if power is not None:
self.power = int(power)
self.p.ChangeDutyCycle(self.power)
if self.gpio_inst:
if power is not None:
self.power = int(power)
self.gpio_inst.ChangeDutyCycle(self.power)

def off(self):
print "GPIO OFF"
self.p.stop()
print("GPIO OFF")
self.gpio_inst.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")
"""
Relay board Actor
"""
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)
@@ -88,20 +113,45 @@ class RelayBoard(ActorBase):

GPIO.output(int(self.gpio), 1)


@cbpi.actor
class Dummy(ActorBase):

"""
Simple Dummy Actor
"""

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

def off(self):
print "OFF"
print("OFF")


@cbpi.actor
class DummyPWM(ActorBase):
"""
Dummy Actor with PWM support
"""
power = 100

def on(self, power=100):
'''
Code to switch on the actor
:param power: int value between 0 - 100
:return:
'''
self.power = int(power) if power is not None else 100
print("DummyPWM ON %s" % self.power)

def off(self):
self.power = 100
print("OFF")

def set_power(self, power):
self.power = int(power)
print("DummyPWM POWER %s" % self.power)

+ 1
- 1
modules/base_plugins/one_wire/__init__.py Parādīt failu

@@ -52,7 +52,7 @@ class myThread (threading.Thread):
## 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:
with open('/sys/bus/w1/devices/%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


+ 1
- 1
modules/buzzer/__init__.py Parādīt failu

@@ -1,5 +1,5 @@
import time
from thread import start_new_thread
from _thread import start_new_thread
from modules import cbpi

try:


+ 1
- 1
modules/config/__init__.py Parādīt failu

@@ -46,7 +46,7 @@ class ConfigView(BaseView):

with cls.api.app.app_context():
cls.api.cache[cls.cache_key] = {}
for key, value in cls.model.get_all().iteritems():
for key, value in cls.model.get_all().items():
cls.post_init_callback(value)
cls.api.cache[cls.cache_key][value.name] = value



+ 1
- 1
modules/core/baseview.py Parādīt failu

@@ -104,6 +104,6 @@ class BaseView(FlaskView):
cls.api.cache[cls.cache_key].append(value)
else:
cls.api.cache[cls.cache_key] = {}
for key, value in cls.model.get_all().iteritems():
for key, value in list(cls.model.get_all().items()):
cls.post_init_callback(value)
cls.api.cache[cls.cache_key][key] = value

+ 1
- 1
modules/core/controller.py Parādīt failu

@@ -42,7 +42,7 @@ class ControllerBase(object):

@staticmethod
def init_global():
print "GLOBAL CONTROLLER INIT"
print("GLOBAL CONTROLLER INIT")

def notify(self, headline, message, type="success", timeout=5000):
self.api.notify(headline, message, type, timeout)


+ 25
- 25
modules/core/core.py Parādīt failu

@@ -12,9 +12,9 @@ from time import localtime, strftime
from functools import wraps, update_wrapper


from props import *
from modules.core.props import *

from hardware import *
from modules.core.hardware import *

import time
import uuid
@@ -28,7 +28,7 @@ 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():
for key, value in list(t.items()):
value.get("class").api = self
value.get("class").init_global()

@@ -83,13 +83,13 @@ class SensorAPI(object):
def init_sensors(self):
'''
Initialize all sensors
:return:
:return:
'''

self.app.logger.info("Init Sensors")

t = self.cache.get("sensor_types")
for key, value in t.iteritems():
for key, value in list(t.items()):
value.get("class").init_global()

for key in self.cache.get("sensors"):
@@ -108,15 +108,15 @@ class SensorAPI(object):
def init_sensor(self, id):
'''
initialize sensor by id
:param id:
:return:
:param id:
:return:
'''

def start_active_sensor(instance):
'''
start active sensors as background job
:param instance:
:return:
:param instance:
:return:
'''
instance.execute()

@@ -292,7 +292,7 @@ class CraftBeerPi(ActorAPI, SensorAPI):
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():
for name, method in list(cls.__dict__.items()):
if hasattr(method, "action"):
label = method.__getattribute__("label")
self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
@@ -309,10 +309,10 @@ class CraftBeerPi(ActorAPI, SensorAPI):
def actor2(self, description="", power=True, **options):

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

@@ -369,7 +369,7 @@ class CraftBeerPi(ActorAPI, SensorAPI):
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():
for name, method in list(cls.__dict__.items()):
if hasattr(method, "action"):
label = method.__getattribute__("label")
self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label})
@@ -378,12 +378,12 @@ class CraftBeerPi(ActorAPI, SensorAPI):


# Event Bus
def event(self, name, async=False):
def event(self, name, use_async=False):

def real_decorator(function):
if self.eventbus.get(name) is None:
self.eventbus[name] = []
self.eventbus[name].append({"function": function, "async": async})
self.eventbus[name].append({"function": function, "async": use_async})
def wrapper(*args, **kwargs):
return function(*args, **kwargs)
return wrapper
@@ -447,12 +447,12 @@ class CraftBeerPi(ActorAPI, SensorAPI):
def run_init(self):
'''
call all initialziers after startup
:return:
: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__) ))
self.app.logger.info("INITIALIZER - METHOD %s PATH %s: " % (i.get("function").__name__, str(inspect.getmodule(i.get("function")).__file__) ))
i.get("function")(self)


@@ -461,10 +461,10 @@ class CraftBeerPi(ActorAPI, SensorAPI):

'''
Background Task Decorator
:param key:
:param interval:
:param config_parameter:
:return:
: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})
@@ -476,7 +476,7 @@ class CraftBeerPi(ActorAPI, SensorAPI):
def run_background_processes(self):
'''
call all background task after startup
:return:
:return:
'''
self.app.logger.info("Start Background")

@@ -485,7 +485,7 @@ class CraftBeerPi(ActorAPI, SensorAPI):
try:
method(self)
except Exception as e:
self.app.logger.error("Exception" + method.__name__ + ": " + str(e))
self.app.logger.error("Exception " + method.__name__ + ": " + str(e))
self.socketio.sleep(interval)




+ 1
- 1
modules/core/db.py Parādīt failu

@@ -79,7 +79,7 @@ class DBModel(object):
cur = get_db().cursor()


if cls.__priamry_key__ is not None and kwargs.has_key(cls.__priamry_key__):
if cls.__priamry_key__ is not None and cls.__priamry_key__ in kwargs:
query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % (
cls.__table_name__,
cls.__priamry_key__,


+ 8
- 10
modules/core/db_mirgrate.py Parādīt failu

@@ -1,10 +1,11 @@
import sqlite3
import os
from modules import cbpi
from db import get_db
from .db import get_db

def execute_file(curernt_version, data):
if curernt_version >= data["version"]:

def execute_file(current_version, data):
if current_version >= data["version"]:
cbpi.app.logger.info("SKIP DB FILE: %s" % data["file"])
return
try:
@@ -19,8 +20,9 @@ def execute_file(curernt_version, data):
conn.commit()

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


@cbpi.initalizer(order=-9999)
def init(app=None):
@@ -28,7 +30,7 @@ def init(app=None):
with cbpi.app.app_context():
conn = get_db()
cur = conn.cursor()
current_version = None
current_version = 0
try:
cur.execute("SELECT max(version) as m FROM schema_info")
m = cur.fetchone()
@@ -41,7 +43,3 @@ def init(app=None):
d = {"version": int(filename[:filename.index('_')]), "file": filename}
result.append(d)
execute_file(current_version, d)





+ 3
- 3
modules/core/hardware.py Parādīt failu

@@ -44,10 +44,10 @@ class SensorBase(Base):
last_value = 0

def init(self):
print "INIT Base SENSOR"
print("INIT Base SENSOR")

def stop(self):
print "STOP SENSOR"
print("STOP SENSOR")

def data_received(self, data):

@@ -86,7 +86,7 @@ class SensorActive(SensorBase):

class SensorPassive(SensorBase):
def init(self):
print "INIT PASSIV SENSOR"
print("INIT PASSIV SENSOR")
pass

def read(self):


+ 5
- 5
modules/core/step.py Parādīt failu

@@ -96,7 +96,7 @@ class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI):
managed_fields = []
n = False

def next(self):
def __next__(self):
self.n = True

def init(self):
@@ -109,10 +109,10 @@ class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI):
pass

def execute(self):
print "-------------"
print "Step Info"
print "Kettle ID: %s" % self.kettle_id
print "ID: %s" % self.id
print("-------------")
print("Step Info")
print(("Kettle ID: %s" % self.kettle_id))
print(("ID: %s" % self.id))


def __init__(self, *args, **kwds):


+ 5
- 5
modules/fermenter/__init__.py Parādīt failu

@@ -184,7 +184,7 @@ class FermenterView(BaseView):

inactive.state = 'A'
inactive.start = time.time()
inactive.direction = "C" if current_temp >= inactive.temp else "H"
inactive.direction = "C" if float(current_temp) >= float(inactive.temp) else "H"
FermenterStep.update(**inactive.__dict__)

self.postTargetTemp(id, inactive.temp)
@@ -210,7 +210,7 @@ class FermenterView(BaseView):
def toggle(self, id):
fermenter = cbpi.cache.get(self.cache_key)[id]
try:
print fermenter.state
print((fermenter.state))
if fermenter.state is False:
# Start controller
if fermenter.logic is not None:
@@ -236,7 +236,7 @@ class FermenterView(BaseView):
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id))

except Exception as e:
print e
print(e)
cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name,
type="danger", timeout=None)
return ('', 500)
@@ -261,7 +261,7 @@ class FermenterView(BaseView):
cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id])

def check_step(self):
for key, value in cbpi.cache["fermenter_task"].iteritems():
for key, value in cbpi.cache["fermenter_task"].items():

try:
fermenter = self.get_fermenter(key)
@@ -292,7 +292,7 @@ def read_target_temps(api):
:return: None
"""
result = {}
for key, value in cbpi.cache.get("fermenter").iteritems():
for key, value in cbpi.cache.get("fermenter").items():
cbpi.save_to_file(key, value.target_temp, prefix="fermenter")




+ 1
- 1
modules/kettle/__init__.py Parādīt failu

@@ -86,7 +86,7 @@ def read_target_temps(api):
:return: None
"""
result = {}
for key, value in cbpi.cache.get("kettle").iteritems():
for key, value in cbpi.cache.get("kettle").items():
cbpi.save_to_file(key, value.target_temp, prefix="kettle")

@cbpi.initalizer()


+ 1
- 1
modules/logs/__init__.py Parādīt failu

@@ -1 +1 @@
import endpoints
import modules.logs.endpoints

+ 116
- 42
modules/logs/endpoints.py Parādīt failu

@@ -1,14 +1,29 @@
# -*- coding: utf-8 -*-
"""
enpoints for logging
"""
import csv
import datetime
import os
from flask import Blueprint, request, send_from_directory, json
import re

from flask import send_from_directory, json
from flask_classy import FlaskView, route
from modules import cbpi


class LogView(FlaskView):
"""
View for logging
"""

@route('/', methods=['GET'])
def get_all_logfiles(self):
def get_all_logfiles(self): # pylint: disable=no-self-use
"""
get all log files

:return: json for all logs
"""
result = []
for filename in os.listdir("./logs"):
if filename.endswith(".log"):
@@ -16,91 +31,150 @@ class LogView(FlaskView):
return json.dumps(result)

@route('/actions')
def actions(self):
def actions(self): # pylint: disable=no-self-use
"""
get actions log

:return: json for actions log
"""
filename = "./logs/action.log"
if os.path.isfile(filename) == False:
return
import csv
if not os.path.isfile(filename):
return ('File not found', 404)
array = []
with open(filename, 'rb') as f:
reader = csv.reader(f)
with open(filename, 'r', encoding='utf-8') as csv_file:
reader = csv.reader(csv_file)
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:
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 IndexError:
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
log delete
:param file: log file name
:return: HTTP 204
"""
if not self.check_filename(file):
return ('File Not Found', 404)

filename = "./logs/%s" % file
if os.path.isfile(filename) == True:
if os.path.isfile(filename):
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
def read_log_as_json(self, log_type, log_id): # pylint: disable=no-self-use
"""
:param log_type: log type
:param log_id: log id

:return: log as array
"""
filename = "./logs/%s_%s.log" % (log_type, log_id)
if not os.path.isfile(filename):
return ('File not found', 404)

import csv
array = []
with open(filename, 'rb') as f:
reader = csv.reader(f)
with open(filename, 'r', encoding='utf-8') as csv_file:
reader = csv.reader(csv_file)
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:
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 IndexError:
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)})
"""
:param chart_data: data for a chart

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))
:return: json for chart data
"""
return {
"name":
chart_data["name"],
"data":
self.read_log_as_json(chart_data["data_type"],
chart_data["data_id"])
}

@route('/<log_type>/<int:log_id>', methods=["POST"])
def get_logs_as_json(self, log_type, log_id):
"""
:param log_type: log type
:param log_id: log id

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: log as array
"""
result = []
if log_type == "s":
name = cbpi.cache.get("sensors").get(log_id).name
result.append({
"name": name,
"data": self.read_log_as_json("sensor", log_id)
})

if log_type == "k":
kettle = cbpi.cache.get("kettle").get(log_id)
result = list(
map(
self.convert_chart_data_to_json,
cbpi.get_controller(
kettle.logic).get("class").chart(kettle)))

if log_type == "f":
fermenter = cbpi.cache.get("fermenter").get(log_id)
result = list(
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):
"""
:param file: log file name

:return: log data
"""
if not self.check_filename(file):
return ('File Not Found', 404)
return send_from_directory('../logs', file, as_attachment=True, attachment_filename=file)
return send_from_directory('../logs',
file,
as_attachment=True,
attachment_filename=file)

def check_filename(self, name):
import re
def check_filename(self, name): # pylint: disable=no-self-use
"""
:param name: log file name
:return: bool
"""
pattern = re.compile('^([A-Za-z0-9-_])+.log$')

return True if pattern.match(name) else False
return bool(pattern.match(name))


@cbpi.initalizer()
def init(app):
def init(app): # pylint: disable=unused-argument
"""
Initializer for the message module
:param app: the flask app


+ 1
- 1
modules/notification/__init__.py Parādīt failu

@@ -24,7 +24,7 @@ class NotificationView(FlaskView):
cbpi.cache["messages"].pop(idx)
return ('', 204)

@cbpi.event("MESSAGE", async=True)
@cbpi.event("MESSAGE", use_async=True)
def messageEvent(message, **kwargs):
"""
React on message event. add the message to the cache and push the message to the clients


+ 3
- 3
modules/recipe_import/__init__.py Parādīt failu

@@ -1,3 +1,3 @@
import beerxml
import kbh
import restapi
from . import beerxml
from . import kbh
from . import restapi

+ 1
- 1
modules/recipe_import/kbh.py Parādīt failu

@@ -29,7 +29,7 @@ class KBH(FlaskView):
result.append({"id": row[0], "name": row[1], "brewed": row[2]})
return json.dumps(result)
except Exception as e:
print e
print(e)
self.api.notify(headline="Failed to load KHB database", message="ERROR", type="danger")
return ('', 500)
finally:


+ 1
- 1
modules/sensors/__init__.py Parādīt failu

@@ -43,6 +43,6 @@ def read_passive_sensor(api):
:return: None

"""
for key, value in cbpi.cache.get("sensors").iteritems():
for key, value in cbpi.cache.get("sensors").items():
if value.mode == "P":
value.instance.read()

+ 1
- 1
modules/system/__init__.py Parādīt failu

@@ -1 +1 @@
import endpoints
import modules.system.endpoints

+ 145
- 145
modules/system/endpoints.py Parādīt failu

@@ -1,145 +1,145 @@
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')
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.request, urllib.parse, urllib.error
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.safe_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')

+ 1
- 1
modules/ui/__init__.py Parādīt failu

@@ -1 +1 @@
import endpoints
import modules.ui.endpoints

+ 1
- 1
modules/ui/static/bundle.js
Failā izmaiņas netiks attēlotas, jo tās ir par lielu
Parādīt failu


+ 6
- 0
requirements-dev.txt Parādīt failu

@@ -0,0 +1,6 @@
coverage==5.2.1
pre-commit
pylint
pylint-flask
black
pdbpp

+ 27
- 14
requirements.txt Parādīt failu

@@ -1,14 +1,27 @@
Flask==0.12.4
Flask-SocketIO==2.6.2
eventlet==0.19.0
greenlet==0.4.10
python-dateutil==2.5.3
python-engineio==0.9.2
python-mimeparse==1.5.2
python-socketio==1.4.4
PyYAML==4.2b1
requests==2.20.0
Werkzeug==0.11.10
httplib2==0.9.2
flask-classy==0.6.10
GitPython==2.1.3
Flask==1.1.2
Flask-Classy==0.6.10
Flask-SocketIO==2.6.2
flask-envconfig
greenlet==0.4.16
eventlet==0.26.1

python-engineio==3.13.1
python-socketio==1.4.4

attrs==19.3.0
certifi==2020.6.20
chardet==3.0.4
click==7.1.2
idna==2.10
itsdangerous==1.1.0
Jinja2==2.11.2
MarkupSafe==1.1.1
PyYAML==5.3.1
requests==2.24.0
six==1.15.0
smmap==3.0.4
urllib3==1.25.10
Werkzeug==1.0.1

gitdb==4.0.5
GitPython==3.1.7

+ 8
- 5
run.py Parādīt failu

@@ -1,11 +1,14 @@
#!/usr/bin/env python
#!/usr/bin/env python3
"""
cbpi runner
"""


from modules import socketio, app, cbpi

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

socketio.run(app, host='0.0.0.0', port=port)
PORT = 5000

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

Notiek ielāde…
Atcelt
Saglabāt