"recipe book added"

This commit is contained in:
Manuel Fritsch 2021-02-27 20:09:19 +01:00
parent 79fd2b8727
commit a55ed2a5e5
21 changed files with 476 additions and 262 deletions

View file

@ -1 +1 @@
__version__ = "4.0.0.26" __version__ = "4.0.0.27"

View file

@ -4,5 +4,8 @@ class ConfigType(Enum):
STRING = "string" STRING = "string"
NUMBER = "number" NUMBER = "number"
SELECT = "select" SELECT = "select"
KETTLE = "kettle"
ACTOR = "actor"
SENSOR = "sensor"

View file

@ -62,6 +62,7 @@ def create_home_folder_structure():
pathlib.Path(os.path.join(".", 'config')).mkdir(parents=True, exist_ok=True) pathlib.Path(os.path.join(".", 'config')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/dashboard')).mkdir(parents=True, exist_ok=True) pathlib.Path(os.path.join(".", 'config/dashboard')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/dashboard/widgets')).mkdir(parents=True, exist_ok=True) pathlib.Path(os.path.join(".", 'config/dashboard/widgets')).mkdir(parents=True, exist_ok=True)
pathlib.Path(os.path.join(".", 'config/recipes')).mkdir(parents=True, exist_ok=True)
print("Folder created") print("Folder created")

View file

@ -1,17 +1,24 @@
{ {
"AUTHOR": {
"description": "Author",
"name": "AUTHOR",
"options": null,
"type": "string",
"value": "John Doe"
},
"BREWERY_NAME": { "BREWERY_NAME": {
"description": "Brewery Name", "description": "Brewery Name",
"name": "BREWERY_NAME", "name": "BREWERY_NAME",
"options": null, "options": null,
"type": "string", "type": "string",
"value": "MANUL" "value": "CraftBeerPi Brewery"
}, },
"NAME": { "MASH_TUN": {
"description": "Brew Name", "description": "Default Mash Tun",
"name": "NAME", "name": "MASH_TUN",
"options": null, "options": null,
"type": "string", "type": "kettle",
"value": "HEDER" "value": ""
}, },
"TEMP_UNIT": { "TEMP_UNIT": {
"description": "Temperature Unit", "description": "Temperature Unit",
@ -27,6 +34,6 @@
} }
], ],
"type": "select", "type": "select",
"value": "F" "value": "C"
} }
} }

View file

@ -2,7 +2,7 @@
"basic": { "basic": {
"name": "" "name": ""
}, },
"profile": [ "steps": [
] ]
} }

View file

@ -38,12 +38,10 @@ class LogController:
async def get_data(self, names, sample_rate='60s'): async def get_data(self, names, sample_rate='60s'):
''' '''
:param names: name as string or list of names as string :param names: name as string or list of names as string
:param sample_rate: rate for resampling the data :param sample_rate: rate for resampling the data
:return: :return:
''' '''
# make string to array # make string to array
if isinstance(names, list) is False: if isinstance(names, list) is False:
names = [names] names = [names]
@ -87,14 +85,23 @@ class LogController:
if len(names) > 1: if len(names) > 1:
for name in names: for name in names:
data[name] = result[name].interpolate(limit_direction='both', limit=10).tolist() data[name] = result[name].interpolate(limit_direction='both', limit=10).tolist()
else: else:
data[name] = result.interpolate().tolist() data[name] = result.interpolate().tolist()
return data return data
async def get_data2(self, ids) -> dict:
def dateparse(time_in_secs):
return datetime.datetime.strptime(time_in_secs, '%Y-%m-%d %H:%M:%S')
result = dict()
for id in ids:
df = pd.read_csv("./logs/sensor_%s.log" % id, parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime',"Values"], header=None)
result[id] = {"time": df.index.astype(str).tolist(), "value":df.Values.tolist()}
return result
def get_logfile_names(self, name:str ) -> list: def get_logfile_names(self, name:str ) -> list:
''' '''
@ -106,14 +113,9 @@ class LogController:
return [os.path.basename(x) for x in glob.glob('./logs/sensor_%s.log*' % name)] return [os.path.basename(x) for x in glob.glob('./logs/sensor_%s.log*' % name)]
def clear_log(self, name:str ) -> str: def clear_log(self, name:str ) -> str:
'''
:param name: log name as string. pattern /logs/sensor_%s.log*
:return: None
'''
all_filenames = glob.glob('./logs/sensor_%s.log*' % name) all_filenames = glob.glob('./logs/sensor_%s.log*' % name)
for f in all_filenames: for f in all_filenames:
os.remove(f) os.remove(f)
if name in self.datalogger: if name in self.datalogger:

View file

@ -0,0 +1,85 @@
import logging
import os.path
from os import listdir
from os.path import isfile, join
import json
import shortuuid
import yaml
from ..api.step import StepMove, StepResult, StepState
import re
class RecipeController:
def __init__(self, cbpi):
self.cbpi = cbpi
self.logger = logging.getLogger(__name__)
def urlify(self, s):
# Remove all non-word characters (everything except numbers and letters)
s = re.sub(r"[^\w\s]", '', s)
# Replace all runs of whitespace with a single dash
s = re.sub(r"\s+", '-', s)
return s
async def create(self, name):
id = shortuuid.uuid()
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(id))
data = dict(basic=dict(name=name, author=self.cbpi.config.get("AUTHOR", "John Doe")), steps=[])
with open(path, "w") as file:
yaml.dump(data, file)
return id
async def save(self, name, data):
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
with open(path, "w") as file:
yaml.dump(data, file, indent=4, sort_keys=True)
self.cbpi.notify("{} saved".format(data["basic"].get("name")))
async def get_recipes(self):
path = os.path.join(".", 'config', "recipes")
onlyfiles = [os.path.splitext(f)[0] for f in listdir(path) if isfile(join(path, f)) and f.endswith(".yaml")]
result = []
for filename in onlyfiles:
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % filename)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
dataset = data["basic"]
dataset["file"] = filename
result.append(dataset)
return result
async def get_by_name(self, name):
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % name)
with open(recipe_path) as file:
return yaml.load(file, Loader=yaml.FullLoader)
async def remove(self, name):
path = os.path.join(".", 'config', "recipes", "{}.yaml".format(name))
os.remove(path)
self.cbpi.notify("{} delted".format(name))
async def brew(self, name):
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % name)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
await self.cbpi.step.load_recipe(data)
async def clone(self, id, new_name):
recipe_path = os.path.join(".", 'config', "recipes", "%s.yaml" % id)
with open(recipe_path) as file:
data = yaml.load(file, Loader=yaml.FullLoader)
data["basic"]["name"] = new_name
new_id = shortuuid.uuid()
await self.save(new_id, data)
return new_id

View file

@ -3,7 +3,8 @@ import copy
import json import json
import logging import logging
import os.path import os.path
from os import listdir
from os.path import isfile, join
import shortuuid import shortuuid
from cbpi.api.dataclasses import Props, Step from cbpi.api.dataclasses import Props, Step
from tabulate import tabulate from tabulate import tabulate
@ -54,13 +55,14 @@ class StepController:
# create file if not exists # create file if not exists
if os.path.exists(self.path) is False: if os.path.exists(self.path) is False:
with open(self.path, "w") as file: with open(self.path, "w") as file:
json.dump(dict(basic={}, profile=[]), file, indent=4, sort_keys=True) json.dump(dict(basic={}, steps=[]), file, indent=4, sort_keys=True)
#load from json file #load from json file
with open(self.path) as json_file: with open(self.path) as json_file:
data = json.load(json_file) data = json.load(json_file)
self.basic_data = data["basic"] self.basic_data = data["basic"]
self.profile = data["profile"] self.profile = data["steps"]
# Start step after start up # Start step after start up
self.profile = list(map(lambda item: self.create(item), self.profile)) self.profile = list(map(lambda item: self.create(item), self.profile))
@ -73,7 +75,6 @@ class StepController:
logging.debug("Add step") logging.debug("Add step")
item.id = shortuuid.uuid() item.id = shortuuid.uuid()
item.status = StepState.INITIAL item.status = StepState.INITIAL
print(item)
try: try:
type_cfg = self.types.get(item.type) type_cfg = self.types.get(item.type)
clazz = type_cfg.get("class") clazz = type_cfg.get("class")
@ -104,7 +105,7 @@ class StepController:
async def save(self): async def save(self):
logging.debug("save profile") logging.debug("save profile")
data = dict(basic=self.basic_data, profile=list(map(lambda item: item.to_dict(), self.profile))) data = dict(basic=self.basic_data, steps=list(map(lambda item: item.to_dict(), self.profile)))
with open(self.path, "w") as file: with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True) json.dump(data, file, indent=4, sort_keys=True)
self.push_udpate() self.push_udpate()
@ -132,6 +133,10 @@ class StepController:
self.cbpi.notify(message="BREWING COMPLETE") self.cbpi.notify(message="BREWING COMPLETE")
logging.info("BREWING COMPLETE") logging.info("BREWING COMPLETE")
async def previous(self):
logging.info("Trigger Next")
async def next(self): async def next(self):
logging.info("Trigger Next") logging.info("Trigger Next")
step = self.find_by_status(StepState.ACTIVE) step = self.find_by_status(StepState.ACTIVE)
@ -190,7 +195,7 @@ class StepController:
return result return result
def get_state(self): def get_state(self):
return {"basic": self.basic_data, "profile": list(map(lambda item: item.to_dict(), self.profile)), "types":self.get_types()} return {"basic": self.basic_data, "steps": list(map(lambda item: item.to_dict(), self.profile)), "types":self.get_types()}
async def move(self, id, direction: StepMove): async def move(self, id, direction: StepMove):
index = self.get_index_by_id(id) index = self.get_index_by_id(id)
@ -215,7 +220,7 @@ class StepController:
self.profile = list(filter(lambda item: item.id != id, self.profile)) self.profile = list(filter(lambda item: item.id != id, self.profile))
await self.save() await self.save()
async def shutdown(self, app): async def shutdown(self, app=None):
logging.info("Mash Profile Shutdonw") logging.info("Mash Profile Shutdonw")
for p in self.profile: for p in self.profile:
instance = p.instance instance = p.instance
@ -225,6 +230,7 @@ class StepController:
await instance.stop() await instance.stop()
await instance.task await instance.task
await self.save() await self.save()
self.push_udpate()
def done(self, step, result): def done(self, step, result):
if result == StepResult.NEXT: if result == StepResult.NEXT:
@ -245,7 +251,10 @@ class StepController:
def get_index_by_id(self, id): def get_index_by_id(self, id):
return next((i for i, item in enumerate(self.profile) if item.id == id), None) return next((i for i, item in enumerate(self.profile) if item.id == id), None)
def push_udpate(self): def push_udpate(self, complete=False):
if complete is True:
self.cbpi.ws.send(dict(topic="mash_profile_update", data=self.get_state()))
else:
self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda item: item.to_dict(), self.profile)))) self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda item: item.to_dict(), self.profile))))
async def start_step(self,step): async def start_step(self,step):
@ -269,3 +278,31 @@ class StepController:
await item.instance.__getattribute__(action)(**parameter) await item.instance.__getattribute__(action)(**parameter)
except Exception as e: except Exception as e:
logging.error("Step Controller -Faild to call action on {} {} {}".format(id, action, e)) logging.error("Step Controller -Faild to call action on {} {} {}".format(id, action, e))
async def load_recipe(self, data):
try:
await self.shutdown()
except:
pass
def add_runtime_data(item):
item["status"] = "I"
item["id"] = shortuuid.uuid()
list(map(lambda item: add_runtime_data(item), data.get("steps")))
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
self.load()
self.push_udpate(complete=True)
async def clear(self):
try:
await self.shutdown()
except:
pass
data = dict(basic=dict(), steps=[])
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
self.load()
self.push_udpate(complete=True)

View file

@ -19,7 +19,7 @@ from cbpi.controller.kettle_controller import KettleController
from cbpi.controller.plugin_controller import PluginController from cbpi.controller.plugin_controller import PluginController
from cbpi.controller.sensor_controller import SensorController from cbpi.controller.sensor_controller import SensorController
from cbpi.controller.step_controller import StepController from cbpi.controller.step_controller import StepController
from cbpi.controller.recipe_controller import RecipeController
from cbpi.controller.system_controller import SystemController from cbpi.controller.system_controller import SystemController
from cbpi.controller.log_file_controller import LogController from cbpi.controller.log_file_controller import LogController
@ -35,7 +35,7 @@ from cbpi.http_endpoints.http_dashboard import DashBoardHttpEndpoints
from cbpi.http_endpoints.http_kettle import KettleHttpEndpoints from cbpi.http_endpoints.http_kettle import KettleHttpEndpoints
from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints from cbpi.http_endpoints.http_sensor import SensorHttpEndpoints
from cbpi.http_endpoints.http_step import StepHttpEndpoints from cbpi.http_endpoints.http_step import StepHttpEndpoints
from cbpi.http_endpoints.http_recipe import RecipeHttpEndpoints
from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints
from cbpi.http_endpoints.http_system import SystemHttpEndpoints from cbpi.http_endpoints.http_system import SystemHttpEndpoints
from cbpi.http_endpoints.http_log import LogHttpEndpoints from cbpi.http_endpoints.http_log import LogHttpEndpoints
@ -97,10 +97,12 @@ class CraftBeerPi:
self.system = SystemController(self) self.system = SystemController(self)
self.kettle = KettleController(self) self.kettle = KettleController(self)
self.step : StepController = StepController(self) self.step : StepController = StepController(self)
self.recipe : RecipeController = RecipeController(self)
#self.satellite: SatelliteController = SatelliteController(self) #self.satellite: SatelliteController = SatelliteController(self)
self.dashboard = DashboardController(self) self.dashboard = DashboardController(self)
self.http_step = StepHttpEndpoints(self) self.http_step = StepHttpEndpoints(self)
self.http_recipe = RecipeHttpEndpoints(self)
self.http_sensor = SensorHttpEndpoints(self) self.http_sensor = SensorHttpEndpoints(self)
self.http_config = ConfigHttpEndpoints(self) self.http_config = ConfigHttpEndpoints(self)
self.http_actor = ActorHttpEndpoints(self) self.http_actor = ActorHttpEndpoints(self)

View file

@ -13,18 +13,12 @@ class CustomSensor(CBPiSensor):
def __init__(self, cbpi, id, props): def __init__(self, cbpi, id, props):
super(CustomSensor, self).__init__(cbpi, id, props) super(CustomSensor, self).__init__(cbpi, id, props)
self.value = 0 self.value = 0
async def run(self): async def run(self):
while self.running is True: while self.running is True:
self.value = random.randint(0,10) self.value = random.randint(0,10)
self.push_update(self.value) self.push_update(self.value)
await asyncio.sleep(1) await asyncio.sleep(1)
def get_state(self): def get_state(self):
return dict(value=self.value) return dict(value=self.value)

View file

@ -27,6 +27,16 @@ if (mode == None):
@parameters([Property.Select(label="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]), Property.Select(label="Inverted", options=["Yes", "No"],description="No: Active on high; Yes: Active on low")]) @parameters([Property.Select(label="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]), Property.Select(label="Inverted", options=["Yes", "No"],description="No: Active on high; Yes: Active on low")])
class GPIOActor(CBPiActor): class GPIOActor(CBPiActor):
@action(key="Cusotm Action", parameters=[Property.Number("Value", configurable=True), Property.Kettle("Kettle")])
async def custom_action(self, **kwargs):
print("ACTION", kwargs)
self.cbpi.notify("ACTION CALLED")
@action(key="Cusotm Action2", parameters=[Property.Number("Value", configurable=True)])
async def custom_action2(self, **kwargs):
print("ACTION2")
self.cbpi.notify("ACTION CALLED")
def get_GPIO_state(self, state): def get_GPIO_state(self, state):
# ON # ON
if state == 1: if state == 1:

View file

@ -2,7 +2,6 @@
import asyncio import asyncio
from cbpi.api.step import CBPiStep, StepResult from cbpi.api.step import CBPiStep, StepResult
from cbpi.api.timer import Timer from cbpi.api.timer import Timer
from cbpi.api import * from cbpi.api import *
import logging import logging
@ -12,13 +11,17 @@ import logging
Property.Kettle(label="Kettle")]) Property.Kettle(label="Kettle")])
class MashStep(CBPiStep): class MashStep(CBPiStep):
@action(key="Custom Step Action", parameters=[]) @action(key="Custom RESET", parameters=[])
async def hello(self, **kwargs): async def custom_reset(self, **kwargs):
print("ACTION") self.summary = ""
await self.push_update()
@action(key="Custom Step Action 2", parameters=[])
async def hello2(self, **kwargs): @action(key="Custom Action", parameters=[Property.Number(label="Value", configurable=True)])
print("ACTION2") async def custom_action(self, Value, **kwargs):
self.summary = "VALUE FROM ACTION {}".format(Value)
await self.push_update()
self.cbpi.notify("ACTION 2 CALLED".format(Value))
async def on_timer_done(self,timer): async def on_timer_done(self,timer):
self.summary = "" self.summary = ""
@ -40,6 +43,7 @@ class MashStep(CBPiStep):
await self.push_update() await self.push_update()
async def reset(self): async def reset(self):
self.summary = ""
self.timer = Timer(int(self.props.Timer) *60 ,on_update=self.on_timer_update, on_done=self.on_timer_done) self.timer = Timer(int(self.props.Timer) *60 ,on_update=self.on_timer_update, on_done=self.on_timer_done)
async def run(self): async def run(self):
@ -56,10 +60,12 @@ class WaitStep(CBPiStep):
@action(key="Custom Step Action", parameters=[]) @action(key="Custom Step Action", parameters=[])
async def hello(self, **kwargs): async def hello(self, **kwargs):
print("ACTION") print("ACTION")
self.cbpi.notify("ACTION 1 CALLED")
@action(key="Custom Step Action 2", parameters=[]) @action(key="Custom Step Action 2", parameters=[])
async def hello2(self, **kwargs): async def hello2(self, **kwargs):
print("ACTION2") print("ACTION2")
self.cbpi.notify("ACTION 2 CALLED")
async def on_timer_done(self,timer): async def on_timer_done(self,timer):
self.summary = "" self.summary = ""

View file

@ -7,7 +7,7 @@ from aiohttp import web
from cbpi.api import * from cbpi.api import *
import os, re, threading, time import os, re, threading, time
from subprocess import call from subprocess import call
import random
def getSensors(): def getSensors():
try: try:
@ -56,7 +56,7 @@ class OneWire(CBPiSensor):
def __init__(self, cbpi, id, props): def __init__(self, cbpi, id, props):
super(OneWire, self).__init__(cbpi, id, props) super(OneWire, self).__init__(cbpi, id, props)
self.value = 0 self.value = 200
async def start(self): async def start(self):
await super().start() await super().start()
@ -79,6 +79,7 @@ class OneWire(CBPiSensor):
async def run(self): async def run(self):
while True: while True:
self.value = self.t.value self.value = self.t.value
self.log_data(self.value) self.log_data(self.value)
self.push_update(self.value) self.push_update(self.value)
await asyncio.sleep(self.interval) await asyncio.sleep(self.interval)

View file

@ -206,6 +206,7 @@ class ActorHttpEndpoints():
""" """
actor_id = request.match_info['id'] actor_id = request.match_info['id']
data = await request.json() data = await request.json()
await self.controller.call_action(actor_id, data.get("name"), data.get("parameter")) print(data)
await self.controller.call_action(actor_id, data.get("action"), data.get("parameter"))
return web.Response(status=204) return web.Response(status=204)

View file

@ -1,7 +1,8 @@
from cbpi.utils.encoder import ComplexEncoder
from aiohttp import web from aiohttp import web
from cbpi.utils.utils import json_dumps from cbpi.utils.utils import json_dumps
from cbpi.api import request_mapping from cbpi.api import request_mapping
import json
class LogHttpEndpoints: class LogHttpEndpoints:
def __init__(self,cbpi): def __init__(self,cbpi):
@ -140,7 +141,7 @@ class LogHttpEndpoints:
return web.json_response(data, dumps=json_dumps) return web.json_response(data, dumps=json_dumps)
@request_mapping(path="/{name}", method="GET", auth_required=False) @request_mapping(path="/{name}", method="GET", auth_required=False)
async def delete_log(self, request): async def get_log(self, request):
""" """
--- ---
description: delete log data for sensor description: delete log data for sensor
@ -163,19 +164,82 @@ class LogHttpEndpoints:
data = await self.cbpi.log.get_data(log_name) data = await self.cbpi.log.get_data(log_name)
return web.json_response(data, dumps=json_dumps) return web.json_response(data, dumps=json_dumps)
@request_mapping(path="/", method="POST", auth_required=False)
async def get_log2(self, request):
"""
---
description: delete log data for sensor
tags:
- Log
parameters:
- in: body
name: body
description: Sensor Ids
required: true
schema:
type: array
items:
type: string
produces:
- application/json
responses:
"200":
description: successful operation.
"""
data = await request.json()
print(data)
return web.json_response(await self.cbpi.log.get_data2(data), dumps=json_dumps)
@request_mapping(path="/{name}", method="DELETE", auth_required=False) @request_mapping(path="/{name}", method="DELETE", auth_required=False)
async def delete_all_logs(self, request): async def clear_log(self, request):
""" """
--- ---
description: Get log data for sensor description: Get log data for sensor
tags: tags:
- Log - Log
parameters: parameters:
- name: "name"
in: "path"
description: "Sensor ID"
required: true
type: "integer"
format: "int64"
responses: responses:
"204": "204":
description: successful operation. description: successful operation.
""" """
log_name = request.match_info['name'] log_name = request.match_info['name']
await self.cbpi.log.clear_logs(log_name) self.cbpi.log.clear_log(log_name)
return web.Response(status=204) return web.Response(status=204)
@request_mapping(path="/logs", method="POST", auth_required=False)
async def get_logs(self, request):
"""
---
description: Get Logs
tags:
- Log
parameters:
- in: body
name: body
description: Sensor Ids
required: true
schema:
type: array
items:
type: string
produces:
- application/json
responses:
"200":
description: successful operation.
"""
data = await request.json()
result = await self.cbpi.log.get_data(data)
print("JSON")
print(json.dumps(result, cls=ComplexEncoder))
print("JSON----")
return web.json_response(result, dumps=json_dumps)

View file

@ -0,0 +1,170 @@
from cbpi.controller.recipe_controller import RecipeController
from cbpi.api.dataclasses import Props, Step
from aiohttp import web
from cbpi.api import *
class RecipeHttpEndpoints():
def __init__(self, cbpi):
self.cbpi = cbpi
self.controller : RecipeController = cbpi.recipe
self.cbpi.register(self, "/recipe")
@request_mapping(path="/", method="GET", auth_required=False)
async def http_get_all(self, request):
"""
---
description: Get all recipes
tags:
- Recipe
responses:
"200":
description: successful operation
"""
return web.json_response(await self.controller.get_recipes())
@request_mapping(path="/{name}", method="GET", auth_required=False)
async def get_by_name(self, request):
"""
---
description: Get all recipes
tags:
- Recipe
parameters:
- name: "name"
in: "path"
description: "Recipe Name"
required: true
type: "string"
responses:
"200":
description: successful operation
"""
name = request.match_info['name']
return web.json_response(await self.controller.get_by_name(name))
@request_mapping(path="/create", method="POST", auth_required=False)
async def http_create(self, request):
"""
---
description: Add Recipe
tags:
- Recipe
responses:
"200":
description: successful operation
"""
data = await request.json()
print(data)
return web.json_response(dict(id=await self.controller.create(data.get("name"))))
@request_mapping(path="/{name}", method="PUT", auth_required=False)
async def http_save(self, request):
"""
---
description: Save Recipe
tags:
- Recipe
parameters:
- name: "id"
in: "path"
description: "Recipe Id"
required: true
type: "string"
- in: body
name: body
description: Recipe Data
required: false
schema:
type: object
responses:
"200":
description: successful operation
"""
data = await request.json()
name = request.match_info['name']
await self.controller.save(name, data)
return web.Response(status=204)
@request_mapping(path="/{name}", method="DELETE", auth_required=False)
async def http_remove(self, request):
"""
---
description: Delete
tags:
- Recipe
parameters:
- name: "id"
in: "path"
description: "Recipe Id"
required: true
type: "string"
responses:
"200":
description: successful operation
"""
name = request.match_info['name']
await self.controller.remove(name)
return web.Response(status=204)
@request_mapping(path="/{name}/brew", method="POST", auth_required=False)
async def http_brew(self, request):
"""
---
description: Brew
tags:
- Recipe
parameters:
- name: "name"
in: "path"
description: "Recipe Id"
required: true
type: "string"
responses:
"200":
description: successful operation
"""
name = request.match_info['name']
await self.controller.brew(name)
return web.Response(status=204)
@request_mapping(path="/{id}/clone", method="POST", auth_required=False)
async def http_clone(self, request):
"""
---
description: Brew
tags:
- Recipe
parameters:
- name: "id"
in: "path"
description: "Recipe Id"
required: true
type: "string"
- in: body
name: body
description: Recipe Data
required: false
schema:
type: object
responses:
"200":
description: successful operation
"""
id = request.match_info['id']
data = await request.json()
return web.json_response(dict(id=await self.controller.clone(id, data.get("name"))))

View file

@ -253,5 +253,25 @@ class StepHttpEndpoints():
await self.controller.call_action(id,data.get("action"), data.get("parameter",[])) await self.controller.call_action(id,data.get("action"), data.get("parameter",[]))
return web.Response(status=204) return web.Response(status=204)
@request_mapping(path="/clear", method="POST", auth_required=False)
async def http_clear(self, request):
"""
---
description: Clear ALll
tags:
- Step
responses:
"204":
description: successful operation
"""
await self.controller.clear()
return web.Response(status=204)

View file

@ -1,6 +1,7 @@
import datetime import datetime
from json import JSONEncoder from json import JSONEncoder
from pandas import Timestamp
class ComplexEncoder(JSONEncoder): class ComplexEncoder(JSONEncoder):
@ -11,7 +12,11 @@ class ComplexEncoder(JSONEncoder):
return obj.to_json() return obj.to_json()
elif isinstance(obj, datetime.datetime): elif isinstance(obj, datetime.datetime):
return obj.__str__() return obj.__str__()
elif isinstance(obj, Timestamp):
print("TIMe")
return obj.__str__()
else: else:
print(type(obj))
raise TypeError() raise TypeError()
except Exception as e: except Exception as e:

View file

@ -1,4 +1,4 @@
aiohttp==3.7.3 aiohttp==3.7.4
aiohttp-auth==0.1.1 aiohttp-auth==0.1.1
aiohttp-route-decorator==0.1.4 aiohttp-route-decorator==0.1.4
aiohttp-security==0.4.0 aiohttp-security==0.4.0

214
sample.py
View file

@ -1,208 +1,14 @@
import pandas as pd
import datetime
from abc import abstractmethod def dateparse(time_in_secs):
import asyncio
from asyncio import tasks
from cbpi.extension.mashstep import MyStep
from cbpi.controller.step_controller import StepController
from cbpi.extension.gpioactor import GPIOActor
from cbpi.api.dataclasses import Actor, Props, Step
from cbpi.controller.basic_controller2 import BasicController
import time
import math
import json
from dataclasses import dataclass
from unittest.mock import MagicMock, patch
async def main():
cbpi = MagicMock()
cbpi.sensor.get_value.return_value = 99
app = MagicMock()
types = {"GPIOActor":{"name": "GPIOActor", "class": GPIOActor, "properties": [], "actions": []}}
controller = StepController(cbpi)
controller.types = types = {"MyStep":{"name": "MyStep", "class": MyStep, "properties": [], "actions": []}}
controller.load()
await controller.stop()
await controller.reset_all()
await controller.start()
#await controller.start()
await asyncio.sleep(2)
await controller.next()
await asyncio.sleep(2)
if __name__ == "__main__":
asyncio.run(main())
''' '''
class Timer(object): Internal helper for date parsing
:param time_in_secs:
def __init__(self, timeout, on_done = None, on_update = None) -> None: :return:
super().__init__()
self.timeout = timeout
self._timemout = self.timeout
self._task = None
self._callback = on_done
self._update = on_update
self.start_time = None
def done(self, task):
if self._callback is not None:
asyncio.create_task(self._callback(self))
async def _job(self):
self.start_time = time.time()
self.count = int(round(self._timemout, 0))
try:
for seconds in range(self.count, 0, -1):
if self._update is not None:
await self._update(self,seconds)
await asyncio.sleep(1)
except asyncio.CancelledError:
end = time.time()
duration = end - self.start_time
self._timemout = self._timemout - duration
def start(self):
self._task = asyncio.create_task(self._job())
self._task.add_done_callback(self.done)
async def stop(self):
print(self._task.done())
if self._task.done() is False:
self._task.cancel()
await self._task
def reset(self):
if self.is_running is True:
return
self._timemout = self.timeout
def is_running(self):
return not self._task.done()
def set_time(self,timeout):
if self.is_running is True:
return
self.timeout = timeout
def get_time(self):
return self.format_time(int(round(self._timemout,0)))
@classmethod
def format_time(cls, time):
pattern = '{0:02d}:{1:02d}:{2:02d}'
seconds = time % 60
minutes = math.floor(time / 60) % 60
hours = math.floor(time / 3600)
return pattern.format(hours, minutes, seconds)
from enum import Enum
class StepResult(Enum):
STOP=1
NEXT=2
DONE=3
class Step():
def __init__(self, name, props, on_done) -> None:
self.name = name
self.timer = None
self._done_callback = on_done
self.props = props
self.cancel_reason: StepResult = None
def _done(self, task):
print("HALLO")
self._done_callback(self, task.result())
async def start(self):
self.task = asyncio.create_task(self._run())
self.task.add_done_callback(self._done)
async def next(self):
self.cancel_reason = StepResult.NEXT
self.task.cancel()
await self.task
async def stop(self):
self.cancel_reason = StepResult.STOP
self.task.cancel()
await self.task
async def reset(self):
pass
async def on_props_update(self, props):
self.props = {**self.props, **props}
async def save_props(self, props):
pass
async def push_state(self, msg):
pass
async def on_start(self):
pass
async def on_stop(self):
pass
async def _run(self):
try:
await self.on_start()
self.cancel_reason = await self.run()
except asyncio.CancelledError as e:
pass
finally:
await self.on_stop()
return self.cancel_reason
@abstractmethod
async def run(self):
pass
class MyStep(Step):
async def timer_update(self, timer, seconds):
print(Timer.format_time(seconds))
async def timer_done(self, timer):
print("TIMER DONE")
await self.next()
async def on_start(self):
if self.timer is None:
self.timer = Timer(20, on_done=self.timer_done, on_update=self.timer_update)
self.timer.start()
async def on_stop(self):
await self.timer.stop()
async def run(self):
for i in range(10):
print("RUNNING")
await asyncio.sleep(1)
await self.timer.stop()
return StepResult.DONE
''' '''
return datetime.datetime.strptime(time_in_secs, '%Y-%m-%d %H:%M:%S')
df = pd.read_csv("./logs/sensor_JUGteK9KrSVPDxboWjBS4N.log", parse_dates=True, date_parser=dateparse, index_col='DateTime', names=['DateTime',"Values"], header=None)
print({"JUGteK9KrSVPDxboWjBS4N": {"time": df.index.astype(str).tolist(), "value":df.Values.tolist()}})

View file

@ -15,7 +15,7 @@ setup(name='cbpi',
'cbpi': ['*','*.txt', '*.rst', '*.yaml']}, 'cbpi': ['*','*.txt', '*.rst', '*.yaml']},
install_requires=[ install_requires=[
"aiohttp==3.7.3", "aiohttp==3.7.4",
"aiohttp-auth==0.1.1", "aiohttp-auth==0.1.1",
"aiohttp-route-decorator==0.1.4", "aiohttp-route-decorator==0.1.4",
"aiohttp-security==0.4.0", "aiohttp-security==0.4.0",