| @@ -17,3 +17,8 @@ yarn.lock | |||||
| modules/ui/package-lock.json | modules/ui/package-lock.json | ||||
| .python-version | |||||
| upload/* | |||||
| *.bak | |||||
| .vscode | |||||
| @@ -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] | |||||
| @@ -1,5 +1,5 @@ | |||||
| # Dockerfile for development on a pc/mac | # Dockerfile for development on a pc/mac | ||||
| FROM python:2 | |||||
| FROM python:3.8 | |||||
| EXPOSE 5000 | EXPOSE 5000 | ||||
| @@ -11,4 +11,4 @@ RUN pip install --no-cache-dir -r requirements.txt | |||||
| COPY . . | COPY . . | ||||
| CMD ["python", "run.py"] | |||||
| CMD ["python", "run.py"] | |||||
| @@ -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 | |||||
| @@ -35,11 +35,11 @@ show_menu () { | |||||
| apt-get -y update; apt-get -y upgrade; | apt-get -y update; apt-get -y upgrade; | ||||
| fi | 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!" | 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 | if [ $? = 0 ]; then | ||||
| @@ -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("##########################################") | app.logger.info("##########################################") | ||||
| @@ -1 +1 @@ | |||||
| import endpoints | |||||
| import modules.addon.endpoints | |||||
| @@ -11,6 +11,7 @@ import os | |||||
| import requests | import requests | ||||
| import yaml | import yaml | ||||
| import shutil | import shutil | ||||
| import imp | |||||
| blueprint = Blueprint('addon', __name__) | blueprint = Blueprint('addon', __name__) | ||||
| @@ -24,7 +25,7 @@ def merge(source, destination): | |||||
| :param destination: | :param destination: | ||||
| :return: | :return: | ||||
| """ | """ | ||||
| for key, value in source.items(): | |||||
| for key, value in list(source.items()): | |||||
| if isinstance(value, dict): | if isinstance(value, dict): | ||||
| # get node or create one | # get node or create one | ||||
| node = destination.setdefault(key, {}) | node = destination.setdefault(key, {}) | ||||
| @@ -115,7 +116,7 @@ def reload(name): | |||||
| """ | """ | ||||
| try: | try: | ||||
| if name in cache["modules"]: | if name in cache["modules"]: | ||||
| reload(cache["modules"][name]) | |||||
| imp.reload(cache["modules"][name]) | |||||
| cbpi.emit_message("REALOD OF PLUGIN %s SUCCESSFUL" % (name)) | cbpi.emit_message("REALOD OF PLUGIN %s SUCCESSFUL" % (name)) | ||||
| return ('', 204) | return ('', 204) | ||||
| else: | else: | ||||
| @@ -132,9 +133,9 @@ def plugins(): | |||||
| Read the central plugin yaml to get a list of all official plugins | Read the central plugin yaml to get a list of all official plugins | ||||
| :return: | :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)) | value["installed"] = os.path.isdir("./modules/plugins/%s/" % (key)) | ||||
| return json.dumps(cbpi.cache["plugins"]) | return json.dumps(cbpi.cache["plugins"]) | ||||
| @@ -144,9 +145,9 @@ def plugins(): | |||||
| def download_addon(name): | def download_addon(name): | ||||
| plugin = cbpi.cache["plugins"].get(name) | plugin = cbpi.cache["plugins"].get(name) | ||||
| plugin["loading"] = True | |||||
| if plugin is None: | if plugin is None: | ||||
| return ('', 404) | return ('', 404) | ||||
| plugin["loading"] = True | |||||
| try: | try: | ||||
| Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name)) | Repo.clone_from(plugin.get("repo_url"), "./modules/plugins/%s/" % (name)) | ||||
| cbpi.notify("Download successful", "Plugin %s downloaded successfully" % name) | cbpi.notify("Download successful", "Plugin %s downloaded successfully" % name) | ||||
| @@ -157,9 +158,28 @@ def download_addon(name): | |||||
| @blueprint.route('/<name>/update', methods=['POST']) | @blueprint.route('/<name>/update', methods=['POST']) | ||||
| def update_addon(name): | 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)) | 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) | cbpi.notify("Plugin Updated", "Plugin %s updated successfully. Please restart the system" % name) | ||||
| return ('', 204) | return ('', 204) | ||||
| @@ -58,7 +58,7 @@ class MashStep(StepBase): | |||||
| # Check if timer finished and go to next step | # Check if timer finished and go to next step | ||||
| if self.is_timer_finished() == True: | if self.is_timer_finished() == True: | ||||
| self.notify("Mash Step Completed!", "Starting the next step", timeout=None) | self.notify("Mash Step Completed!", "Starting the next step", timeout=None) | ||||
| self.next() | |||||
| next(self) | |||||
| @cbpi.step | @cbpi.step | ||||
| @@ -121,7 +121,7 @@ class ChilStep(StepBase): | |||||
| self.start_timer(int(self.timer) * 60) | self.start_timer(int(self.timer) * 60) | ||||
| if self.is_timer_finished() == True: | if self.is_timer_finished() == True: | ||||
| self.next() | |||||
| next(self) | |||||
| @cbpi.step | @cbpi.step | ||||
| class PumpStep(StepBase): | class PumpStep(StepBase): | ||||
| @@ -149,7 +149,7 @@ class PumpStep(StepBase): | |||||
| self.start_timer(int(self.timer) * 60) | self.start_timer(int(self.timer) * 60) | ||||
| if self.is_timer_finished() == True: | if self.is_timer_finished() == True: | ||||
| self.next() | |||||
| next(self) | |||||
| @cbpi.step | @cbpi.step | ||||
| class BoilStep(StepBase): | class BoilStep(StepBase): | ||||
| @@ -201,8 +201,8 @@ class BoilStep(StepBase): | |||||
| def check_hop_timer(self, number, value): | 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.timer_end - (int(self.timer) * 60 - int(value) * 60)): | ||||
| self.__setattr__("hop_%s_added" % number, True) | self.__setattr__("hop_%s_added" % number, True) | ||||
| self.notify("Hop Alert", "Please add Hop %s" % number, timeout=None) | 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 | # Check if timer finished and go to next step | ||||
| if self.is_timer_finished() == True: | if self.is_timer_finished() == True: | ||||
| self.notify("Boil Step Completed!", "Starting the next step", timeout=None) | self.notify("Boil Step Completed!", "Starting the next step", timeout=None) | ||||
| self.next() | |||||
| next(self) | |||||
| @@ -1,53 +1,78 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| """ | |||||
| Dummy sensors | |||||
| """ | |||||
| import subprocess | import subprocess | ||||
| import time | import time | ||||
| from modules import cbpi, socketio | 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 | from modules.core.props import Property | ||||
| @cbpi.sensor | @cbpi.sensor | ||||
| class DummyTempSensor(SensorActive): | 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): | def stop(self): | ||||
| """ | |||||
| stop sensor | |||||
| """ | |||||
| SensorActive.stop(self) | SensorActive.stop(self) | ||||
| def execute(self): | def execute(self): | ||||
| ''' | ''' | ||||
| Active sensor has to handle his own loop | Active sensor has to handle his own loop | ||||
| :return: | |||||
| :return: | |||||
| ''' | ''' | ||||
| while self.is_running() is True: | 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) | self.sleep(5) | ||||
| @classmethod | @classmethod | ||||
| def init_global(cls): | def init_global(cls): | ||||
| ''' | ''' | ||||
| Called one at the startup for all sensors | Called one at the startup for all sensors | ||||
| :return: | |||||
| :return: | |||||
| ''' | ''' | ||||
| @@ -1,4 +1,5 @@ | |||||
| # -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
| """ base gpio actors """ | |||||
| import time | import time | ||||
| from modules import cbpi | from modules import cbpi | ||||
| @@ -6,46 +7,60 @@ from modules.core.hardware import ActorBase, SensorPassive, SensorActive | |||||
| from modules.core.props import Property | from modules.core.props import Property | ||||
| try: | try: | ||||
| import RPi.GPIO as GPIO | |||||
| import RPi.GPIO as GPIO # pylint: disable=import-error | |||||
| GPIO.setmode(GPIO.BCM) | GPIO.setmode(GPIO.BCM) | ||||
| except Exception as e: | |||||
| print e | |||||
| pass | |||||
| except ImportError as exp: | |||||
| print(exp) | |||||
| @cbpi.actor | @cbpi.actor | ||||
| class GPIOSimple(ActorBase): | 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): | def init(self): | ||||
| GPIO.setup(int(self.gpio), GPIO.OUT) | GPIO.setup(int(self.gpio), GPIO.OUT) | ||||
| GPIO.output(int(self.gpio), 0) | GPIO.output(int(self.gpio), 0) | ||||
| def on(self, power=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) | GPIO.output(int(self.gpio), 1) | ||||
| def off(self): | def off(self): | ||||
| print "GPIO OFF" | |||||
| print("GPIO OFF") | |||||
| GPIO.output(int(self.gpio), 0) | GPIO.output(int(self.gpio), 0) | ||||
| @cbpi.actor | @cbpi.actor | ||||
| class GPIOPWM(ActorBase): | 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) | frequency = Property.Number("Frequency (Hz)", configurable=True) | ||||
| p = None | |||||
| gpio_inst = None | |||||
| power = 100 # duty cycle | power = 100 # duty cycle | ||||
| def init(self): | def init(self): | ||||
| GPIO.setup(int(self.gpio), GPIO.OUT) | GPIO.setup(int(self.gpio), GPIO.OUT) | ||||
| GPIO.output(int(self.gpio), 0) | GPIO.output(int(self.gpio), 0) | ||||
| def on(self, power=None): | def on(self, power=None): | ||||
| if power is not None: | if power is not None: | ||||
| self.power = int(power) | self.power = int(power) | ||||
| @@ -53,28 +68,38 @@ class GPIOPWM(ActorBase): | |||||
| if self.frequency is None: | if self.frequency is None: | ||||
| self.frequency = 0.5 # 2 sec | 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): | def set_power(self, power): | ||||
| ''' | ''' | ||||
| Optional: Set the power of your actor | Optional: Set the power of your actor | ||||
| :param power: int value between 0 - 100 | :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): | def off(self): | ||||
| print "GPIO OFF" | |||||
| self.p.stop() | |||||
| print("GPIO OFF") | |||||
| self.gpio_inst.stop() | |||||
| @cbpi.actor | @cbpi.actor | ||||
| class RelayBoard(ActorBase): | 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): | def init(self): | ||||
| GPIO.setup(int(self.gpio), GPIO.OUT) | GPIO.setup(int(self.gpio), GPIO.OUT) | ||||
| @@ -88,20 +113,45 @@ class RelayBoard(ActorBase): | |||||
| GPIO.output(int(self.gpio), 1) | GPIO.output(int(self.gpio), 1) | ||||
| @cbpi.actor | @cbpi.actor | ||||
| class Dummy(ActorBase): | class Dummy(ActorBase): | ||||
| """ | |||||
| Simple Dummy Actor | |||||
| """ | |||||
| def on(self, power=100): | def on(self, power=100): | ||||
| ''' | ''' | ||||
| Code to switch on the actor | Code to switch on the actor | ||||
| :param power: int value between 0 - 100 | :param power: int value between 0 - 100 | ||||
| :return: | |||||
| :return: | |||||
| ''' | ''' | ||||
| print "ON" | |||||
| print("ON") | |||||
| def off(self): | 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) | |||||
| @@ -52,7 +52,7 @@ class myThread (threading.Thread): | |||||
| ## Test Mode | ## Test Mode | ||||
| if self.sensor_name is None: | if self.sensor_name is None: | ||||
| return | 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() | content = content_file.read() | ||||
| if (content.split('\n')[0].split(' ')[11] == "YES"): | if (content.split('\n')[0].split(' ')[11] == "YES"): | ||||
| temp = float(content.split("=")[-1]) / 1000 # temp in Celcius | temp = float(content.split("=")[-1]) / 1000 # temp in Celcius | ||||
| @@ -1,5 +1,5 @@ | |||||
| import time | import time | ||||
| from thread import start_new_thread | |||||
| from _thread import start_new_thread | |||||
| from modules import cbpi | from modules import cbpi | ||||
| try: | try: | ||||
| @@ -46,7 +46,7 @@ class ConfigView(BaseView): | |||||
| with cls.api.app.app_context(): | with cls.api.app.app_context(): | ||||
| cls.api.cache[cls.cache_key] = {} | 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.post_init_callback(value) | ||||
| cls.api.cache[cls.cache_key][value.name] = value | cls.api.cache[cls.cache_key][value.name] = value | ||||
| @@ -104,6 +104,6 @@ class BaseView(FlaskView): | |||||
| cls.api.cache[cls.cache_key].append(value) | cls.api.cache[cls.cache_key].append(value) | ||||
| else: | else: | ||||
| cls.api.cache[cls.cache_key] = {} | 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.post_init_callback(value) | ||||
| cls.api.cache[cls.cache_key][key] = value | cls.api.cache[cls.cache_key][key] = value | ||||
| @@ -42,7 +42,7 @@ class ControllerBase(object): | |||||
| @staticmethod | @staticmethod | ||||
| def init_global(): | def init_global(): | ||||
| print "GLOBAL CONTROLLER INIT" | |||||
| print("GLOBAL CONTROLLER INIT") | |||||
| def notify(self, headline, message, type="success", timeout=5000): | def notify(self, headline, message, type="success", timeout=5000): | ||||
| self.api.notify(headline, message, type, timeout) | self.api.notify(headline, message, type, timeout) | ||||
| @@ -12,9 +12,9 @@ from time import localtime, strftime | |||||
| from functools import wraps, update_wrapper | 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 time | ||||
| import uuid | import uuid | ||||
| @@ -28,7 +28,7 @@ class ActorAPI(object): | |||||
| def init_actors(self): | def init_actors(self): | ||||
| self.app.logger.info("Init Actors") | self.app.logger.info("Init Actors") | ||||
| t = self.cache.get("actor_types") | 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").api = self | ||||
| value.get("class").init_global() | value.get("class").init_global() | ||||
| @@ -83,13 +83,13 @@ class SensorAPI(object): | |||||
| def init_sensors(self): | def init_sensors(self): | ||||
| ''' | ''' | ||||
| Initialize all sensors | Initialize all sensors | ||||
| :return: | |||||
| :return: | |||||
| ''' | ''' | ||||
| self.app.logger.info("Init Sensors") | self.app.logger.info("Init Sensors") | ||||
| t = self.cache.get("sensor_types") | t = self.cache.get("sensor_types") | ||||
| for key, value in t.iteritems(): | |||||
| for key, value in list(t.items()): | |||||
| value.get("class").init_global() | value.get("class").init_global() | ||||
| for key in self.cache.get("sensors"): | for key in self.cache.get("sensors"): | ||||
| @@ -108,15 +108,15 @@ class SensorAPI(object): | |||||
| def init_sensor(self, id): | def init_sensor(self, id): | ||||
| ''' | ''' | ||||
| initialize sensor by id | initialize sensor by id | ||||
| :param id: | |||||
| :return: | |||||
| :param id: | |||||
| :return: | |||||
| ''' | ''' | ||||
| def start_active_sensor(instance): | def start_active_sensor(instance): | ||||
| ''' | ''' | ||||
| start active sensors as background job | start active sensors as background job | ||||
| :param instance: | |||||
| :return: | |||||
| :param instance: | |||||
| :return: | |||||
| ''' | ''' | ||||
| instance.execute() | instance.execute() | ||||
| @@ -292,7 +292,7 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description}) | 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"): | if hasattr(method, "action"): | ||||
| label = method.__getattribute__("label") | label = method.__getattribute__("label") | ||||
| self.cache[key][cls.__name__]["actions"].append({"method": name, "label": 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 actor2(self, description="", power=True, **options): | ||||
| def decorator(f): | def decorator(f): | ||||
| print f() | |||||
| print f | |||||
| print options | |||||
| print description | |||||
| print((f())) | |||||
| print(f) | |||||
| print(options) | |||||
| print(description) | |||||
| return f | return f | ||||
| return decorator | return decorator | ||||
| @@ -369,7 +369,7 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| t = tmpObj.__getattribute__(m) | t = tmpObj.__getattribute__(m) | ||||
| self.cache[key][name]["properties"].append({"name": m, "label": t.label, "type": "kettle", "configurable": t.configurable, "description": t.description}) | 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"): | if hasattr(method, "action"): | ||||
| label = method.__getattribute__("label") | label = method.__getattribute__("label") | ||||
| self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label}) | self.cache[key][cls.__name__]["actions"].append({"method": name, "label": label}) | ||||
| @@ -378,12 +378,12 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| # Event Bus | # Event Bus | ||||
| def event(self, name, async=False): | |||||
| def event(self, name, use_async=False): | |||||
| def real_decorator(function): | def real_decorator(function): | ||||
| if self.eventbus.get(name) is None: | if self.eventbus.get(name) is None: | ||||
| self.eventbus[name] = [] | self.eventbus[name] = [] | ||||
| self.eventbus[name].append({"function": function, "async": async}) | |||||
| self.eventbus[name].append({"function": function, "async": use_async}) | |||||
| def wrapper(*args, **kwargs): | def wrapper(*args, **kwargs): | ||||
| return function(*args, **kwargs) | return function(*args, **kwargs) | ||||
| return wrapper | return wrapper | ||||
| @@ -447,12 +447,12 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| def run_init(self): | def run_init(self): | ||||
| ''' | ''' | ||||
| call all initialziers after startup | call all initialziers after startup | ||||
| :return: | |||||
| :return: | |||||
| ''' | ''' | ||||
| self.app.logger.info("Invoke Init") | self.app.logger.info("Invoke Init") | ||||
| self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order']) | self.cache["init"] = sorted(self.cache["init"], key=lambda k: k['order']) | ||||
| for i in self.cache.get("init"): | 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) | i.get("function")(self) | ||||
| @@ -461,10 +461,10 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| ''' | ''' | ||||
| Background Task Decorator | Background Task Decorator | ||||
| :param key: | |||||
| :param interval: | |||||
| :param config_parameter: | |||||
| :return: | |||||
| :param key: | |||||
| :param interval: | |||||
| :param config_parameter: | |||||
| :return: | |||||
| ''' | ''' | ||||
| def real_decorator(function): | def real_decorator(function): | ||||
| self.cache["background"].append({"function": function, "key": key, "interval": interval, "config_parameter": config_parameter}) | 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): | def run_background_processes(self): | ||||
| ''' | ''' | ||||
| call all background task after startup | call all background task after startup | ||||
| :return: | |||||
| :return: | |||||
| ''' | ''' | ||||
| self.app.logger.info("Start Background") | self.app.logger.info("Start Background") | ||||
| @@ -485,7 +485,7 @@ class CraftBeerPi(ActorAPI, SensorAPI): | |||||
| try: | try: | ||||
| method(self) | method(self) | ||||
| except Exception as e: | 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) | self.socketio.sleep(interval) | ||||
| @@ -79,7 +79,7 @@ class DBModel(object): | |||||
| cur = get_db().cursor() | 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)" % ( | query = "INSERT INTO %s (%s, %s) VALUES (?, %s)" % ( | ||||
| cls.__table_name__, | cls.__table_name__, | ||||
| cls.__priamry_key__, | cls.__priamry_key__, | ||||
| @@ -1,10 +1,11 @@ | |||||
| import sqlite3 | import sqlite3 | ||||
| import os | import os | ||||
| from modules import cbpi | 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"]) | cbpi.app.logger.info("SKIP DB FILE: %s" % data["file"]) | ||||
| return | return | ||||
| try: | try: | ||||
| @@ -19,8 +20,9 @@ def execute_file(curernt_version, data): | |||||
| conn.commit() | conn.commit() | ||||
| except sqlite3.OperationalError as err: | except sqlite3.OperationalError as err: | ||||
| print "EXCEPT" | |||||
| print err | |||||
| print("EXCEPT") | |||||
| print(err) | |||||
| @cbpi.initalizer(order=-9999) | @cbpi.initalizer(order=-9999) | ||||
| def init(app=None): | def init(app=None): | ||||
| @@ -28,7 +30,7 @@ def init(app=None): | |||||
| with cbpi.app.app_context(): | with cbpi.app.app_context(): | ||||
| conn = get_db() | conn = get_db() | ||||
| cur = conn.cursor() | cur = conn.cursor() | ||||
| current_version = None | |||||
| current_version = 0 | |||||
| try: | try: | ||||
| cur.execute("SELECT max(version) as m FROM schema_info") | cur.execute("SELECT max(version) as m FROM schema_info") | ||||
| m = cur.fetchone() | m = cur.fetchone() | ||||
| @@ -41,7 +43,3 @@ def init(app=None): | |||||
| d = {"version": int(filename[:filename.index('_')]), "file": filename} | d = {"version": int(filename[:filename.index('_')]), "file": filename} | ||||
| result.append(d) | result.append(d) | ||||
| execute_file(current_version, d) | execute_file(current_version, d) | ||||
| @@ -44,10 +44,10 @@ class SensorBase(Base): | |||||
| last_value = 0 | last_value = 0 | ||||
| def init(self): | def init(self): | ||||
| print "INIT Base SENSOR" | |||||
| print("INIT Base SENSOR") | |||||
| def stop(self): | def stop(self): | ||||
| print "STOP SENSOR" | |||||
| print("STOP SENSOR") | |||||
| def data_received(self, data): | def data_received(self, data): | ||||
| @@ -86,7 +86,7 @@ class SensorActive(SensorBase): | |||||
| class SensorPassive(SensorBase): | class SensorPassive(SensorBase): | ||||
| def init(self): | def init(self): | ||||
| print "INIT PASSIV SENSOR" | |||||
| print("INIT PASSIV SENSOR") | |||||
| pass | pass | ||||
| def read(self): | def read(self): | ||||
| @@ -96,7 +96,7 @@ class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI): | |||||
| managed_fields = [] | managed_fields = [] | ||||
| n = False | n = False | ||||
| def next(self): | |||||
| def __next__(self): | |||||
| self.n = True | self.n = True | ||||
| def init(self): | def init(self): | ||||
| @@ -109,10 +109,10 @@ class StepBase(Timer, ActorAPI, SensorAPI, KettleAPI): | |||||
| pass | pass | ||||
| def execute(self): | 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): | def __init__(self, *args, **kwds): | ||||
| @@ -184,7 +184,7 @@ class FermenterView(BaseView): | |||||
| inactive.state = 'A' | inactive.state = 'A' | ||||
| inactive.start = time.time() | 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__) | FermenterStep.update(**inactive.__dict__) | ||||
| self.postTargetTemp(id, inactive.temp) | self.postTargetTemp(id, inactive.temp) | ||||
| @@ -210,7 +210,7 @@ class FermenterView(BaseView): | |||||
| def toggle(self, id): | def toggle(self, id): | ||||
| fermenter = cbpi.cache.get(self.cache_key)[id] | fermenter = cbpi.cache.get(self.cache_key)[id] | ||||
| try: | try: | ||||
| print fermenter.state | |||||
| print((fermenter.state)) | |||||
| if fermenter.state is False: | if fermenter.state is False: | ||||
| # Start controller | # Start controller | ||||
| if fermenter.logic is not None: | 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)) | cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key).get(id)) | ||||
| except Exception as e: | except Exception as e: | ||||
| print e | |||||
| print(e) | |||||
| cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name, | cbpi.notify("Toogle Fementer Controller failed", "Pleae check the %s configuration" % fermenter.name, | ||||
| type="danger", timeout=None) | type="danger", timeout=None) | ||||
| return ('', 500) | return ('', 500) | ||||
| @@ -261,7 +261,7 @@ class FermenterView(BaseView): | |||||
| cbpi.emit("UPDATE_FERMENTER", cbpi.cache.get(self.cache_key)[id]) | cbpi.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"].items(): | |||||
| try: | try: | ||||
| fermenter = self.get_fermenter(key) | fermenter = self.get_fermenter(key) | ||||
| @@ -292,7 +292,7 @@ def read_target_temps(api): | |||||
| :return: None | :return: None | ||||
| """ | """ | ||||
| result = {} | 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") | cbpi.save_to_file(key, value.target_temp, prefix="fermenter") | ||||
| @@ -86,7 +86,7 @@ def read_target_temps(api): | |||||
| :return: None | :return: None | ||||
| """ | """ | ||||
| result = {} | 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.save_to_file(key, value.target_temp, prefix="kettle") | ||||
| @cbpi.initalizer() | @cbpi.initalizer() | ||||
| @@ -1 +1 @@ | |||||
| import endpoints | |||||
| import modules.logs.endpoints | |||||
| @@ -1,14 +1,29 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| """ | |||||
| enpoints for logging | |||||
| """ | |||||
| import csv | |||||
| import datetime | import datetime | ||||
| import os | 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 flask_classy import FlaskView, route | ||||
| from modules import cbpi | from modules import cbpi | ||||
| class LogView(FlaskView): | class LogView(FlaskView): | ||||
| """ | |||||
| View for logging | |||||
| """ | |||||
| @route('/', methods=['GET']) | @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 = [] | result = [] | ||||
| for filename in os.listdir("./logs"): | for filename in os.listdir("./logs"): | ||||
| if filename.endswith(".log"): | if filename.endswith(".log"): | ||||
| @@ -16,91 +31,150 @@ class LogView(FlaskView): | |||||
| return json.dumps(result) | return json.dumps(result) | ||||
| @route('/actions') | @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" | 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 = [] | 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: | for row in reader: | ||||
| try: | 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 | pass | ||||
| return json.dumps(array) | return json.dumps(array) | ||||
| @route('/<file>', methods=["DELETE"]) | @route('/<file>', methods=["DELETE"]) | ||||
| def clearlog(self, file): | 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 | :return: HTTP 204 | ||||
| """ | """ | ||||
| if not self.check_filename(file): | if not self.check_filename(file): | ||||
| return ('File Not Found', 404) | return ('File Not Found', 404) | ||||
| filename = "./logs/%s" % file | filename = "./logs/%s" % file | ||||
| if os.path.isfile(filename) == True: | |||||
| if os.path.isfile(filename): | |||||
| os.remove(filename) | os.remove(filename) | ||||
| cbpi.notify("log deleted succesfully", "") | cbpi.notify("log deleted succesfully", "") | ||||
| else: | else: | ||||
| cbpi.notify("Failed to delete log", "", type="danger") | cbpi.notify("Failed to delete log", "", type="danger") | ||||
| return ('', 204) | 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 = [] | 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: | for row in reader: | ||||
| try: | 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 | pass | ||||
| return array | return array | ||||
| def convert_chart_data_to_json(self, chart_data): | 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) | return json.dumps(result) | ||||
| @route('/download/<file>') | @route('/download/<file>') | ||||
| @cbpi.nocache | @cbpi.nocache | ||||
| def download(self, file): | def download(self, file): | ||||
| """ | |||||
| :param file: log file name | |||||
| :return: log data | |||||
| """ | |||||
| if not self.check_filename(file): | if not self.check_filename(file): | ||||
| return ('File Not Found', 404) | 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$') | pattern = re.compile('^([A-Za-z0-9-_])+.log$') | ||||
| return True if pattern.match(name) else False | |||||
| return bool(pattern.match(name)) | |||||
| @cbpi.initalizer() | @cbpi.initalizer() | ||||
| def init(app): | |||||
| def init(app): # pylint: disable=unused-argument | |||||
| """ | """ | ||||
| Initializer for the message module | Initializer for the message module | ||||
| :param app: the flask app | :param app: the flask app | ||||
| @@ -24,7 +24,7 @@ class NotificationView(FlaskView): | |||||
| cbpi.cache["messages"].pop(idx) | cbpi.cache["messages"].pop(idx) | ||||
| return ('', 204) | return ('', 204) | ||||
| @cbpi.event("MESSAGE", async=True) | |||||
| @cbpi.event("MESSAGE", use_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 | ||||
| @@ -1,3 +1,3 @@ | |||||
| import beerxml | |||||
| import kbh | |||||
| import restapi | |||||
| from . import beerxml | |||||
| from . import kbh | |||||
| from . import restapi | |||||
| @@ -29,7 +29,7 @@ class KBH(FlaskView): | |||||
| result.append({"id": row[0], "name": row[1], "brewed": row[2]}) | result.append({"id": row[0], "name": row[1], "brewed": row[2]}) | ||||
| return json.dumps(result) | return json.dumps(result) | ||||
| except Exception as e: | except Exception as e: | ||||
| print e | |||||
| print(e) | |||||
| self.api.notify(headline="Failed to load KHB database", message="ERROR", type="danger") | self.api.notify(headline="Failed to load KHB database", message="ERROR", type="danger") | ||||
| return ('', 500) | return ('', 500) | ||||
| finally: | finally: | ||||
| @@ -43,6 +43,6 @@ def read_passive_sensor(api): | |||||
| :return: None | :return: None | ||||
| """ | """ | ||||
| for key, value in cbpi.cache.get("sensors").iteritems(): | |||||
| for key, value in cbpi.cache.get("sensors").items(): | |||||
| if value.mode == "P": | if value.mode == "P": | ||||
| value.instance.read() | value.instance.read() | ||||
| @@ -1 +1 @@ | |||||
| import endpoints | |||||
| import modules.system.endpoints | |||||
| @@ -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 @@ | |||||
| import endpoints | |||||
| import modules.ui.endpoints | |||||
| @@ -0,0 +1,6 @@ | |||||
| coverage==5.2.1 | |||||
| pre-commit | |||||
| pylint | |||||
| pylint-flask | |||||
| black | |||||
| pdbpp | |||||
| @@ -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 | |||||
| @@ -1,11 +1,14 @@ | |||||
| #!/usr/bin/env python | |||||
| #!/usr/bin/env python3 | |||||
| """ | |||||
| cbpi runner | |||||
| """ | |||||
| from modules import socketio, app, cbpi | from modules import socketio, app, cbpi | ||||
| try: | try: | ||||
| port = int(cbpi.get_config_parameter('port', '5000')) | |||||
| PORT = int(cbpi.get_config_parameter('port', '5000')) | |||||
| except ValueError: | 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) | |||||