mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-12-03 12:14:18 +01:00
plugin installer added
This commit is contained in:
parent
85b6549640
commit
eb2ba8fdfe
34 changed files with 990 additions and 1107 deletions
|
@ -32,5 +32,12 @@
|
|||
<auth-required>false</auth-required>
|
||||
<introspection-schemas>*:@</introspection-schemas>
|
||||
</data-source>
|
||||
<data-source name="craftbeerpi.db test" uuid="2513ed7e-a63e-406f-b4b6-435eaa982255">
|
||||
<database-info product="SQLite" version="3.16.1" jdbc-version="2.1" driver-name="SQLiteJDBC" driver-version="native" dbms="SQLITE" exact-version="3.16.1" />
|
||||
<case-sensitivity plain-identifiers="mixed" quoted-identifiers="mixed" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<auth-required>false</auth-required>
|
||||
<introspection-schemas>*:@</introspection-schemas>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
|
@ -78,5 +78,14 @@
|
|||
</library>
|
||||
</libraries>
|
||||
</data-source>
|
||||
<data-source source="LOCAL" name="craftbeerpi.db test" uuid="2513ed7e-a63e-406f-b4b6-435eaa982255">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$USER_HOME$/cbpi4_test/2019-08-08-001/craftbeerpi.db</jdbc-url>
|
||||
<driver-properties>
|
||||
<property name="enable_load_extension" value="true" />
|
||||
</driver-properties>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
|
@ -4,4 +4,7 @@
|
|||
<option name="languageLevel" value="JSX" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7.1 virtualenv at ~/cbp42" project-jdk-type="Python SDK" />
|
||||
<component name="PyPackaging">
|
||||
<option name="earlyReleasesAsUpgrades" value="true" />
|
||||
</component>
|
||||
</project>
|
1341
.idea/workspace.xml
1341
.idea/workspace.xml
File diff suppressed because it is too large
Load diff
|
@ -2,8 +2,6 @@ __all__ = ["CBPiActor",
|
|||
"CBPiExtension",
|
||||
"Property",
|
||||
"PropertyType",
|
||||
"on_websocket_message",
|
||||
"on_mqtt_message",
|
||||
"on_event",
|
||||
"on_startup",
|
||||
"request_mapping",
|
||||
|
|
|
@ -2,7 +2,7 @@ from functools import wraps
|
|||
|
||||
from voluptuous import Schema
|
||||
|
||||
__all__ = ["request_mapping", "on_startup", "on_event", "on_mqtt_message", "on_websocket_message", "action", "background_task"]
|
||||
__all__ = ["request_mapping", "on_startup", "on_event", "action", "background_task"]
|
||||
|
||||
from aiohttp_auth import auth
|
||||
|
||||
|
@ -55,15 +55,6 @@ def request_mapping(path, name=None, method="GET", auth_required=True, json_sche
|
|||
validate_json_body
|
||||
)
|
||||
|
||||
def on_websocket_message(path, name=None):
|
||||
def real_decorator(func):
|
||||
func.ws = True
|
||||
func.key = path
|
||||
func.name = name
|
||||
return func
|
||||
|
||||
return real_decorator
|
||||
|
||||
def on_event(topic):
|
||||
def real_decorator(func):
|
||||
func.eventbus = True
|
||||
|
@ -76,29 +67,18 @@ def on_event(topic):
|
|||
def action(key, parameters):
|
||||
def real_decorator(func):
|
||||
func.action = True
|
||||
|
||||
func.key = key
|
||||
func.parameters = parameters
|
||||
return func
|
||||
|
||||
return real_decorator
|
||||
|
||||
def on_mqtt_message(topic):
|
||||
def real_decorator(func):
|
||||
func.mqtt = True
|
||||
func.topic = topic
|
||||
return func
|
||||
|
||||
return real_decorator
|
||||
|
||||
|
||||
def background_task(name, interval):
|
||||
def real_decorator(func):
|
||||
func.background_task = True
|
||||
func.name = name
|
||||
func.interval = interval
|
||||
return func
|
||||
|
||||
return real_decorator
|
||||
|
||||
|
||||
|
@ -108,7 +88,6 @@ def on_startup(name, order=0):
|
|||
func.name = name
|
||||
func.order = order
|
||||
return func
|
||||
|
||||
return real_decorator
|
||||
|
||||
|
||||
|
|
|
@ -11,4 +11,4 @@ class SensorException(CBPiException):
|
|||
pass
|
||||
|
||||
class ActorException(CBPiException):
|
||||
pass
|
||||
pass
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
from logging.handlers import RotatingFileHandler
|
||||
from time import localtime, strftime
|
||||
import logging
|
||||
from abc import ABCMeta
|
||||
|
||||
from cbpi.api.extension import CBPiExtension
|
||||
import logging
|
||||
|
||||
|
||||
class CBPiSensor(CBPiExtension):
|
||||
class CBPiSensor(CBPiExtension, metaclass=ABCMeta):
|
||||
def __init__(self, *args, **kwds):
|
||||
CBPiExtension.__init__(self, *args, **kwds)
|
||||
self.logger = logging.getLogger(__file__)
|
||||
|
|
|
@ -2,7 +2,7 @@ import json
|
|||
import time
|
||||
import asyncio
|
||||
import logging
|
||||
from abc import abstractmethod,ABCMeta
|
||||
from abc import abstractmethod, ABCMeta
|
||||
|
||||
|
||||
class CBPiSimpleStep(metaclass=ABCMeta):
|
||||
|
|
90
cbpi/cli.py
90
cbpi/cli.py
|
@ -1,7 +1,12 @@
|
|||
import argparse
|
||||
import datetime
|
||||
import logging
|
||||
import subprocess
|
||||
import sys
|
||||
import re
|
||||
import requests
|
||||
import yaml
|
||||
from cbpi.utils.utils import load_config
|
||||
|
||||
from cbpi.craftbeerpi import CraftBeerPi
|
||||
import os
|
||||
|
@ -59,22 +64,87 @@ def list_plugins():
|
|||
print("***************************************************")
|
||||
print("CraftBeerPi 4.x Plugin List")
|
||||
print("***************************************************")
|
||||
print("")
|
||||
plugins_yaml = "https://raw.githubusercontent.com/Manuel83/craftbeerpi-plugins/master/plugins_v4.yaml"
|
||||
r = requests.get(plugins_yaml)
|
||||
|
||||
data = yaml.load(r.content, Loader=yaml.FullLoader)
|
||||
|
||||
|
||||
for name, value in data.items():
|
||||
print(name)
|
||||
print("")
|
||||
print("***************************************************")
|
||||
|
||||
def add(package_name):
|
||||
|
||||
if package_name is None:
|
||||
print("Missing Plugin Name: cbpi add --name=")
|
||||
return
|
||||
|
||||
data = subprocess.check_output([sys.executable, "-m", "pip", "install", package_name])
|
||||
data = data.decode('UTF-8')
|
||||
|
||||
patter_already_installed = "Requirement already satisfied: %s" % package_name
|
||||
pattern = "Successfully installed %s-([-0-9a-zA-Z._]*)" % package_name
|
||||
|
||||
match_already_installed = re.search(patter_already_installed, data)
|
||||
match_installed = re.search(pattern, data)
|
||||
|
||||
if match_already_installed is not None:
|
||||
print("Plugin already installed")
|
||||
return False
|
||||
|
||||
if match_installed is None:
|
||||
print(data)
|
||||
print("Faild to install plugin")
|
||||
return False
|
||||
|
||||
version = match_installed.groups()[0]
|
||||
plugins = load_config("./config/plugin_list.txt")
|
||||
if plugins is None:
|
||||
plugins = {}
|
||||
now = datetime.datetime.now()
|
||||
plugins[package_name] = dict(version=version, installation_date=now.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
with open('./config/plugin_list.txt', 'w') as outfile:
|
||||
yaml.dump(plugins, outfile, default_flow_style=False)
|
||||
|
||||
print("Plugin %s added" % package_name)
|
||||
return True
|
||||
|
||||
|
||||
def remove(package_name):
|
||||
if package_name is None:
|
||||
print("Missing Plugin Name: cbpi add --name=")
|
||||
return
|
||||
data = subprocess.check_output([sys.executable, "-m", "pip", "uninstall", "-y", package_name])
|
||||
data = data.decode('UTF-8')
|
||||
|
||||
pattern = "Successfully uninstalled %s-([-0-9a-zA-Z._]*)" % package_name
|
||||
match_uninstalled = re.search(pattern, data)
|
||||
|
||||
if match_uninstalled is None:
|
||||
print(data)
|
||||
print("Faild to uninstall plugin")
|
||||
return False
|
||||
|
||||
plugins = load_config("./config/plugin_list.txt")
|
||||
if plugins is None:
|
||||
plugins = {}
|
||||
|
||||
if package_name not in plugins:
|
||||
return False
|
||||
|
||||
del plugins[package_name]
|
||||
with open('./config/plugin_list.txt', 'w') as outfile:
|
||||
yaml.dump(plugins, outfile, default_flow_style=False)
|
||||
|
||||
print("Plugin %s removed" % package_name)
|
||||
return True
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Welcome to CraftBeerPi 4')
|
||||
parser.add_argument("action", type=str, help="start,stop,restart,setup,plugins")
|
||||
|
||||
parser.add_argument("--name", type=str, help="Plugin name")
|
||||
args = parser.parse_args()
|
||||
|
||||
#logging.basicConfig(level=logging.INFO, filename='./logs/app.log', filemode='a', format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
|
||||
|
@ -94,6 +164,16 @@ def main():
|
|||
list_plugins()
|
||||
return
|
||||
|
||||
|
||||
if args.action == "add":
|
||||
|
||||
add(args.name)
|
||||
return
|
||||
|
||||
if args.action == "remove":
|
||||
remove(args.name)
|
||||
return
|
||||
|
||||
if args.action == "start":
|
||||
if check_for_setup() is False:
|
||||
return
|
||||
|
|
|
@ -108,4 +108,9 @@ CREATE TABLE IF NOT EXISTS dummy
|
|||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name VARCHAR(80)
|
||||
|
||||
);
|
||||
);
|
||||
|
||||
|
||||
INSERT OR IGNORE INTO config (name, value, type, description, options) VALUES ('TEMP_UNIT', 'F', 'select', 'Temperature Unit', '[{"value": "C", "label": "C"}, {"value": "F", "label": "F"}]');
|
||||
INSERT OR IGNORE INTO config (name, value, type, description, options) VALUES ('NAME', 'India Pale Ale1', 'string', 'Brew Name', 'null');
|
||||
INSERT OR IGNORE INTO config (name, value, type, description, options) VALUES ('BREWERY_NAME', 'CraftBeerPI', 'string', 'Brewery Name', 'null');
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
cbpi-actor
|
|
@ -149,8 +149,7 @@ class ActorController(CRUDController):
|
|||
pass
|
||||
|
||||
async def _pre_delete_callback(self, actor_id):
|
||||
#if int(actor_id) not in self.cache:
|
||||
# return
|
||||
|
||||
if self.cache[int(actor_id)].instance is not None:
|
||||
await self._stop_actor(self.cache[int(actor_id)])
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ class DashboardController(CRUDController):
|
|||
name = "Dashboard"
|
||||
|
||||
def __init__(self, cbpi):
|
||||
self.caching = False
|
||||
super(DashboardController, self).__init__(cbpi)
|
||||
self.cbpi = cbpi
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
@ -29,3 +30,7 @@ class DashboardController(CRUDController):
|
|||
|
||||
async def move_content(self,content_id, x, y):
|
||||
await DashboardContentModel.update_coordinates(content_id, x, y)
|
||||
|
||||
async def delete_dashboard(self, dashboard_id):
|
||||
await DashboardContentModel.delete_by_dashboard_id(dashboard_id)
|
||||
await self.model.delete(dashboard_id)
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
from os import urandom
|
||||
import os
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp_auth import auth
|
||||
|
@ -231,7 +232,8 @@ class CraftBeerPi():
|
|||
else:
|
||||
return web.Response(text="Hello from CraftbeerPi!")
|
||||
|
||||
self.app.add_routes([web.get('/', http_index)])
|
||||
|
||||
self.app.add_routes([web.get('/', http_index), web.static('/static', os.path.join(os.path.dirname(__file__),"static"), show_index=True)])
|
||||
|
||||
async def init_serivces(self):
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import os
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from cbpi.api import *
|
||||
from cbpi.controller.crud_controller import CRUDController
|
||||
from cbpi.database.orm_framework import DBModel
|
||||
|
@ -22,11 +19,8 @@ class MyComp(CBPiExtension, CRUDController, HttpCrudEndpoints):
|
|||
def __init__(self, cbpi):
|
||||
'''
|
||||
Initializer
|
||||
|
||||
:param cbpi:
|
||||
'''
|
||||
|
||||
|
||||
self.cbpi = cbpi
|
||||
# register component for http, events
|
||||
# In addtion the sub folder static is exposed to access static content via http
|
||||
|
@ -35,14 +29,14 @@ class MyComp(CBPiExtension, CRUDController, HttpCrudEndpoints):
|
|||
|
||||
@on_event(topic="actor/#")
|
||||
async def listen(self, **kwargs):
|
||||
# Listen for all actor events
|
||||
pass
|
||||
|
||||
|
||||
@on_event(topic="kettle/+/automatic")
|
||||
async def listen2(self, **kwargs):
|
||||
|
||||
# listen for all kettle events which are switching the automatic logic
|
||||
pass
|
||||
#await self.cbpi.bus.fire(topic="actor/%s/toggle" % 1, id=1)
|
||||
|
||||
|
||||
def setup(cbpi):
|
||||
|
@ -54,4 +48,4 @@ def setup(cbpi):
|
|||
'''
|
||||
# regsiter the component to the core
|
||||
cbpi.plugin.register("MyComp", MyComp)
|
||||
pass
|
||||
pass
|
||||
|
|
120
cbpi/extension/ds18b20/__init__.py
Normal file
120
cbpi/extension/ds18b20/__init__.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
import threading
|
||||
import time
|
||||
|
||||
from aiohttp import web
|
||||
from cbpi.api import *
|
||||
|
||||
import re
|
||||
import random
|
||||
|
||||
|
||||
def getSensors():
|
||||
try:
|
||||
arr = []
|
||||
for dirname in os.listdir('/sys/bus/w1/devices'):
|
||||
if (dirname.startswith("28") or dirname.startswith("10")):
|
||||
cbpi.app.logger.info("Device %s Found (Family: 28/10, Thermometer on GPIO4 (w1))" % dirname)
|
||||
arr.append(dirname)
|
||||
return arr
|
||||
except:
|
||||
return []
|
||||
|
||||
|
||||
class myThread (threading.Thread):
|
||||
|
||||
value = 0
|
||||
|
||||
|
||||
def __init__(self, sensor_name):
|
||||
threading.Thread.__init__(self)
|
||||
self.value = 0
|
||||
self.sensor_name = sensor_name
|
||||
self.runnig = True
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def stop(self):
|
||||
self.runnig = False
|
||||
|
||||
def run(self):
|
||||
|
||||
while self.runnig:
|
||||
|
||||
try:
|
||||
app.logger.info("READ TEMP")
|
||||
## Test Mode
|
||||
if self.sensor_name is None:
|
||||
return
|
||||
with open('/sys/bus/w1/devices/w1_bus_master1/%s/w1_slave' % self.sensor_name, 'r') as content_file:
|
||||
content = content_file.read()
|
||||
if (content.split('\n')[0].split(' ')[11] == "YES"):
|
||||
temp = float(content.split("=")[-1]) / 1000 # temp in Celcius
|
||||
self.value = temp
|
||||
except:
|
||||
pass
|
||||
|
||||
self.value = random.randint(1,100)
|
||||
time.sleep(4)
|
||||
|
||||
class DS18B20(CBPiSensor):
|
||||
|
||||
|
||||
sensor_name = Property.Select("Sensor", getSensors(), description="The OneWire sensor address.")
|
||||
offset = Property.Number("Offset", True, 0, description="Offset which is added to the received sensor data. Positive and negative values are both allowed.")
|
||||
interval = Property.Number(label="interval", configurable=True)
|
||||
|
||||
# Internal runtime variable
|
||||
value = 0
|
||||
|
||||
def init(self):
|
||||
super().init()
|
||||
self.state = True
|
||||
self.t = myThread(self.sensor_name)
|
||||
def shudown():
|
||||
shudown.cb.shutdown()
|
||||
|
||||
shudown.cb = self.t
|
||||
|
||||
self.t.start()
|
||||
|
||||
def get_state(self):
|
||||
return self.state
|
||||
|
||||
def get_value(self):
|
||||
|
||||
return self.value
|
||||
|
||||
def get_unit(self):
|
||||
return "°%s" % self.get_parameter("TEMP_UNIT", "C")
|
||||
|
||||
def stop(self):
|
||||
try:
|
||||
self.t.stop()
|
||||
except:
|
||||
pass
|
||||
|
||||
async def run(self, cbpi):
|
||||
self.value = 0
|
||||
while True:
|
||||
await asyncio.sleep(self.interval)
|
||||
self.value = random.randint(1,101)
|
||||
self.log_data(self.value)
|
||||
await cbpi.bus.fire("sensor/%s/data" % self.id, value=self.value)
|
||||
|
||||
|
||||
|
||||
|
||||
def setup(cbpi):
|
||||
|
||||
'''
|
||||
This method is called by the server during startup
|
||||
Here you need to register your plugins at the server
|
||||
|
||||
:param cbpi: the cbpi core
|
||||
:return:
|
||||
'''
|
||||
|
||||
cbpi.plugin.register("DS18B20", DS18B20)
|
3
cbpi/extension/ds18b20/config.yaml
Normal file
3
cbpi/extension/ds18b20/config.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: DummySensor
|
||||
version: 4
|
||||
active: true
|
|
@ -4,13 +4,12 @@ from aiohttp import web
|
|||
from cbpi.api import *
|
||||
|
||||
import re
|
||||
|
||||
import random
|
||||
class CustomSensor(CBPiSensor):
|
||||
|
||||
# Custom Properties which will can be configured by the user
|
||||
|
||||
p1 = Property.Number(label="Test")
|
||||
p2 = Property.Text(label="Test")
|
||||
|
||||
interval = Property.Number(label="interval", configurable=True)
|
||||
|
||||
# Internal runtime variable
|
||||
|
@ -45,104 +44,10 @@ class CustomSensor(CBPiSensor):
|
|||
self.value = 0
|
||||
while True:
|
||||
await asyncio.sleep(self.interval)
|
||||
self.value = self.value + 1
|
||||
self.value = random.randint(1,101)
|
||||
self.log_data(self.value)
|
||||
await cbpi.bus.fire("sensor/%s/data" % self.id, value=self.value)
|
||||
|
||||
cache = {}
|
||||
|
||||
|
||||
class HTTPSensor(CBPiSensor):
|
||||
|
||||
# Custom Properties which will can be configured by the user
|
||||
|
||||
key = Property.Text(label="Key", configurable=True)
|
||||
|
||||
def init(self):
|
||||
super().init()
|
||||
|
||||
self.state = True
|
||||
|
||||
def get_state(self):
|
||||
return self.state
|
||||
|
||||
def get_value(self):
|
||||
|
||||
return self.value
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
async def run(self, cbpi):
|
||||
self.value = 0
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
try:
|
||||
value = cache.pop(self.key, None)
|
||||
|
||||
if value is not None:
|
||||
self.log_data(value)
|
||||
await cbpi.bus.fire("sensor/%s/data" % self.id, value=value)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
class HTTPSensorEndpoint(CBPiExtension):
|
||||
|
||||
|
||||
def __init__(self, cbpi):
|
||||
'''
|
||||
Initializer
|
||||
|
||||
:param cbpi:
|
||||
'''
|
||||
self.pattern_check = re.compile("^[a-zA-Z0-9,.]{0,10}$")
|
||||
|
||||
self.cbpi = cbpi
|
||||
# register component for http, events
|
||||
# In addtion the sub folder static is exposed to access static content via http
|
||||
self.cbpi.register(self, "/httpsensor")
|
||||
|
||||
|
||||
@request_mapping(path="/{key}/{value}", auth_required=False)
|
||||
async def http_new_value2(self, request):
|
||||
"""
|
||||
---
|
||||
description: Kettle Heater on
|
||||
tags:
|
||||
- HttpSensor
|
||||
parameters:
|
||||
- name: "key"
|
||||
in: "path"
|
||||
description: "Sensor Key"
|
||||
required: true
|
||||
type: "string"
|
||||
- name: "value"
|
||||
in: "path"
|
||||
description: "Value"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
|
||||
global cache
|
||||
key = request.match_info['key']
|
||||
value = request.match_info['value']
|
||||
if self.pattern_check.match(key) is None:
|
||||
return web.json_response(status=422, data={'error': "Key not matching pattern ^[a-zA-Z0-9,.]{0,10}$"})
|
||||
|
||||
if self.pattern_check.match(value) is None:
|
||||
return web.json_response(status=422, data={'error': "Data not matching pattern ^[a-zA-Z0-9,.]{0,10}$"})
|
||||
|
||||
print("HTTP SENSOR ", key, value)
|
||||
cache[key] = value
|
||||
|
||||
return web.Response(status=204)
|
||||
|
||||
|
||||
def setup(cbpi):
|
||||
|
||||
|
@ -153,6 +58,5 @@ def setup(cbpi):
|
|||
:param cbpi: the cbpi core
|
||||
:return:
|
||||
'''
|
||||
cbpi.plugin.register("HTTPSensor", HTTPSensor)
|
||||
cbpi.plugin.register("HTTPSensorEndpoint", HTTPSensorEndpoint)
|
||||
|
||||
cbpi.plugin.register("CustomSensor", CustomSensor)
|
||||
|
|
116
cbpi/extension/httpsensor/__init__.py
Normal file
116
cbpi/extension/httpsensor/__init__.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import asyncio
|
||||
from aiohttp import web
|
||||
from cbpi.api import *
|
||||
|
||||
import re
|
||||
import random
|
||||
|
||||
|
||||
cache = {}
|
||||
|
||||
|
||||
class HTTPSensor(CBPiSensor):
|
||||
|
||||
# Custom Properties which will can be configured by the user
|
||||
|
||||
key = Property.Text(label="Key", configurable=True)
|
||||
|
||||
def init(self):
|
||||
super().init()
|
||||
|
||||
self.state = True
|
||||
|
||||
def get_state(self):
|
||||
return self.state
|
||||
|
||||
def get_value(self):
|
||||
|
||||
return self.value
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
async def run(self, cbpi):
|
||||
self.value = 0
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
try:
|
||||
value = cache.pop(self.key, None)
|
||||
|
||||
if value is not None:
|
||||
self.log_data(value)
|
||||
await cbpi.bus.fire("sensor/%s/data" % self.id, value=value)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
class HTTPSensorEndpoint(CBPiExtension):
|
||||
|
||||
|
||||
def __init__(self, cbpi):
|
||||
'''
|
||||
Initializer
|
||||
|
||||
:param cbpi:
|
||||
'''
|
||||
self.pattern_check = re.compile("^[a-zA-Z0-9,.]{0,10}$")
|
||||
|
||||
self.cbpi = cbpi
|
||||
# register component for http, events
|
||||
# In addtion the sub folder static is exposed to access static content via http
|
||||
self.cbpi.register(self, "/httpsensor")
|
||||
|
||||
|
||||
@request_mapping(path="/{key}/{value}", auth_required=False)
|
||||
async def http_new_value2(self, request):
|
||||
"""
|
||||
---
|
||||
description: Kettle Heater on
|
||||
tags:
|
||||
- HttpSensor
|
||||
parameters:
|
||||
- name: "key"
|
||||
in: "path"
|
||||
description: "Sensor Key"
|
||||
required: true
|
||||
type: "string"
|
||||
- name: "value"
|
||||
in: "path"
|
||||
description: "Value"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
|
||||
global cache
|
||||
key = request.match_info['key']
|
||||
value = request.match_info['value']
|
||||
if self.pattern_check.match(key) is None:
|
||||
return web.json_response(status=422, data={'error': "Key not matching pattern ^[a-zA-Z0-9,.]{0,10}$"})
|
||||
|
||||
if self.pattern_check.match(value) is None:
|
||||
return web.json_response(status=422, data={'error': "Data not matching pattern ^[a-zA-Z0-9,.]{0,10}$"})
|
||||
|
||||
print("HTTP SENSOR ", key, value)
|
||||
cache[key] = value
|
||||
|
||||
return web.Response(status=204)
|
||||
|
||||
|
||||
def setup(cbpi):
|
||||
|
||||
'''
|
||||
This method is called by the server during startup
|
||||
Here you need to register your plugins at the server
|
||||
|
||||
:param cbpi: the cbpi core
|
||||
:return:
|
||||
'''
|
||||
cbpi.plugin.register("HTTPSensor", HTTPSensor)
|
||||
cbpi.plugin.register("HTTPSensorEndpoint", HTTPSensorEndpoint)
|
||||
|
3
cbpi/extension/httpsensor/config.yaml
Normal file
3
cbpi/extension/httpsensor/config.yaml
Normal file
|
@ -0,0 +1,3 @@
|
|||
name: DummySensor
|
||||
version: 4
|
||||
active: true
|
|
@ -118,7 +118,9 @@ class DashBoardHttpEndpoints(HttpCrudEndpoints):
|
|||
"204":
|
||||
description: successful operation
|
||||
"""
|
||||
return await super().http_delete_one(request)
|
||||
id = request.match_info['id']
|
||||
await self.cbpi.dashboard.delete_dashboard(id)
|
||||
return web.Response(status=204)
|
||||
|
||||
@request_mapping(path="/{id:\d+}/content", auth_required=False)
|
||||
async def get_content(self, request):
|
||||
|
|
|
@ -180,9 +180,9 @@ class SensorHttpEndpoints(HttpCrudEndpoints):
|
|||
"""
|
||||
|
||||
---
|
||||
description: Toogle an actor on or off
|
||||
description: Execute action on sensor
|
||||
tags:
|
||||
- Actor
|
||||
- Sensor
|
||||
parameters:
|
||||
- name: "id"
|
||||
in: "path"
|
||||
|
|
BIN
cbpi/static/splash.png
Normal file
BIN
cbpi/static/splash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 MiB |
17
cbpi/static/test.html
Normal file
17
cbpi/static/test.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CraftBeerPi 4.0</title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-image: url("/static/splash.png");
|
||||
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -7,11 +7,13 @@ import yaml
|
|||
|
||||
|
||||
def load_config(fname):
|
||||
|
||||
try:
|
||||
with open(fname, 'rt') as f:
|
||||
data = yaml.load(f)
|
||||
data = yaml.load(f, Loader=yaml.FullLoader)
|
||||
return data
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
def json_dumps(obj):
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
listeners:
|
||||
default:
|
||||
type: tcp
|
||||
bind: 0.0.0.0:1883
|
||||
sys_interval: 20
|
||||
auth:
|
||||
allow-anonymous: true
|
||||
plugins:
|
||||
- auth_file
|
||||
- auth_anonymous
|
||||
topic-check:
|
||||
enabled': True
|
||||
plugins':
|
||||
- topic_taboo
|
|
@ -1,13 +0,0 @@
|
|||
SampleActor:
|
||||
description: A sample Actor for CraftBeerPi
|
||||
api: 4.0
|
||||
author: CraftBeerPi11
|
||||
pip: requests
|
||||
repo_url: https://github.com/craftbeerpi/sample_actor
|
||||
|
||||
SampleActor2:
|
||||
description: A sample Actor2 for CraftBeerPi
|
||||
api: 4.0
|
||||
author: CraftBeerPi
|
||||
pip: requests
|
||||
repo_url: https://github.com/craftbeerpi/sample_actor
|
|
@ -1,62 +0,0 @@
|
|||
import yaml
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
def load_yaml():
|
||||
try:
|
||||
with open('./repo/plugins.yaml', 'rt') as f:
|
||||
data = yaml.load(f)
|
||||
return data
|
||||
except Exception as e:
|
||||
print(e)
|
||||
pass
|
||||
|
||||
data = load_yaml()
|
||||
|
||||
for k, v in data.items():
|
||||
del v["pip"]
|
||||
data2 = load_yaml()
|
||||
|
||||
|
||||
async def check(request):
|
||||
peername = request.transport.get_extra_info('peername')
|
||||
if peername is not None:
|
||||
host, port = peername
|
||||
print(host, port)
|
||||
data = await request.json()
|
||||
print(data)
|
||||
return web.json_response(data=dict(latestversion="4.0.0.3"))
|
||||
|
||||
async def reload_yaml(request):
|
||||
global data, data2
|
||||
|
||||
file = load_yaml()
|
||||
for k, v in file.items():
|
||||
del v["pip"]
|
||||
data = file
|
||||
data2 = load_yaml()
|
||||
|
||||
return web.json_response(data=data2)
|
||||
|
||||
async def get_list(request):
|
||||
print("Request List")
|
||||
return web.json_response(data=data)
|
||||
|
||||
async def get_package_name(request):
|
||||
print("Request Package")
|
||||
name = request.match_info.get('plugin_name', None)
|
||||
if name in data2:
|
||||
package_name = data2[name]["pip"]
|
||||
else:
|
||||
package_name = None
|
||||
return web.json_response(data=dict(package_name=package_name))
|
||||
|
||||
|
||||
app = web.Application()
|
||||
app.add_routes([
|
||||
web.get('/list', get_list),
|
||||
web.post('/check', check),
|
||||
web.get('/reload', reload_yaml),
|
||||
web.get('/get/{plugin_name}', get_package_name)])
|
||||
|
||||
web.run_app(app, port=2202)
|
|
@ -1,11 +1 @@
|
|||
cbpi-actor:
|
||||
api: 4.0
|
||||
author: CraftBeerPi11
|
||||
description: A sample Actor for CraftBeerPi
|
||||
repo_url: https://github.com/craftbeerpi/sample_actor
|
||||
cbpi-ui:
|
||||
api: 4.0
|
||||
author: CraftBeerPi
|
||||
description: A sample Actor2 for CraftBeerPi
|
||||
repo_url: https://github.com/craftbeerpi/sample_actor
|
||||
|
||||
{}
|
||||
|
|
BIN
craftbeerpi.db
BIN
craftbeerpi.db
Binary file not shown.
81
sample.py
81
sample.py
|
@ -1,72 +1,19 @@
|
|||
import datetime
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from time import strftime, localtime
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
sid = 2
|
||||
import yaml
|
||||
from cbpi.utils.utils import load_config
|
||||
|
||||
package_name = "test222"
|
||||
|
||||
with open("./config/plugin_list.txt", 'rt') as f:
|
||||
print(f)
|
||||
plugins = yaml.load(f)
|
||||
if plugins is None:
|
||||
plugins = {}
|
||||
|
||||
|
||||
data_logger = logging.getLogger('cbpi.sensor.%s' % sid)
|
||||
data_logger.propagate = False
|
||||
data_logger.setLevel(logging.DEBUG)
|
||||
handler = RotatingFileHandler('./logs/sensor_%s.log' % sid, maxBytes=100_000, backupCount=10)
|
||||
data_logger.addHandler(handler)
|
||||
import random
|
||||
now = datetime.datetime.now()
|
||||
|
||||
start = datetime.datetime.now()
|
||||
'''
|
||||
v = random.randint(50,60)
|
||||
for i in range(5760):
|
||||
d = start + datetime.timedelta(seconds=6*i)
|
||||
formatted_time = d.strftime("%Y-%m-%d %H:%M:%S")
|
||||
if i % 750 == 0:
|
||||
v = random.randint(50,60)
|
||||
data_logger.info("%s,%s" % (formatted_time, v))
|
||||
|
||||
|
||||
'''
|
||||
def dateparse (time_in_secs):
|
||||
return datetime.datetime.strptime(time_in_secs, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
all_filenames = glob.glob('./logs/sensor_1.log*')
|
||||
all_filenames.sort()
|
||||
|
||||
|
||||
all_filenames2 = glob.glob('./logs/sensor_2.log*')
|
||||
all_filenames2.sort()
|
||||
|
||||
combined_csv = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Sensor1'], header=None) for f in all_filenames])
|
||||
combined_csv2 = pd.concat([pd.read_csv(f, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime', 'Sensor2'], header=None) for f in all_filenames2])
|
||||
|
||||
|
||||
print(combined_csv)
|
||||
print(combined_csv2)
|
||||
|
||||
|
||||
m2 = pd.merge(combined_csv, combined_csv2, how='inner', left_index=True, right_index=True)
|
||||
|
||||
print(m2)
|
||||
|
||||
m2.plot()
|
||||
|
||||
m2.plot(y=['Sensor1','Sensor2'])
|
||||
|
||||
ts = combined_csv.Sensor1.resample('5000s').max()
|
||||
|
||||
#ts.plot(y='Sensor1')
|
||||
|
||||
i = 0
|
||||
def myconverter(o):
|
||||
if isinstance(o, datetime.datetime):
|
||||
return o.__str__()
|
||||
|
||||
|
||||
|
||||
data = {"time": ts.index.tolist(), "data": ts.tolist()}
|
||||
s1 = json.dumps(data, default = myconverter)
|
||||
|
||||
plt.show()
|
||||
plugins[package_name] = dict(version="1.0", installation_date=now.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
with open('./config/plugin_list.txt', 'w') as outfile:
|
||||
yaml.dump(plugins, outfile, default_flow_style=False)
|
||||
|
|
2
setup.py
2
setup.py
|
@ -1,7 +1,7 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='cbpi',
|
||||
version='4.0.0.4',
|
||||
version='4.0.0.5',
|
||||
description='CraftBeerPi',
|
||||
author='Manuel Fritsch',
|
||||
author_email='manuel@craftbeerpi.com',
|
||||
|
|
20
tests/test_cli.py
Normal file
20
tests/test_cli.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import logging
|
||||
import unittest
|
||||
|
||||
from cli import add, remove, list_plugins
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
||||
|
||||
|
||||
class CLITest(unittest.TestCase):
|
||||
|
||||
def test_install(self):
|
||||
assert add("cbpi4-ui-plugin") == True
|
||||
assert add("cbpi4-ui-plugin") == False
|
||||
assert remove("cbpi4-ui-plugin") == True
|
||||
|
||||
def test_list(self):
|
||||
list_plugins()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in a new issue