mirror of
https://github.com/PiBrewing/craftbeerpi4.git
synced 2024-11-23 23:48:16 +01:00
"recipe book added"
This commit is contained in:
parent
79fd2b8727
commit
a55ed2a5e5
21 changed files with 476 additions and 262 deletions
|
@ -1 +1 @@
|
|||
__version__ = "4.0.0.26"
|
||||
__version__ = "4.0.0.27"
|
|
@ -4,5 +4,8 @@ class ConfigType(Enum):
|
|||
STRING = "string"
|
||||
NUMBER = "number"
|
||||
SELECT = "select"
|
||||
KETTLE = "kettle"
|
||||
ACTOR = "actor"
|
||||
SENSOR = "sensor"
|
||||
|
||||
|
||||
|
|
|
@ -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/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/recipes')).mkdir(parents=True, exist_ok=True)
|
||||
print("Folder created")
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
{
|
||||
"AUTHOR": {
|
||||
"description": "Author",
|
||||
"name": "AUTHOR",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "John Doe"
|
||||
},
|
||||
"BREWERY_NAME": {
|
||||
"description": "Brewery Name",
|
||||
"name": "BREWERY_NAME",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "MANUL"
|
||||
"value": "CraftBeerPi Brewery"
|
||||
},
|
||||
"NAME": {
|
||||
"description": "Brew Name",
|
||||
"name": "NAME",
|
||||
"MASH_TUN": {
|
||||
"description": "Default Mash Tun",
|
||||
"name": "MASH_TUN",
|
||||
"options": null,
|
||||
"type": "string",
|
||||
"value": "HEDER"
|
||||
"type": "kettle",
|
||||
"value": ""
|
||||
},
|
||||
"TEMP_UNIT": {
|
||||
"description": "Temperature Unit",
|
||||
|
@ -27,6 +34,6 @@
|
|||
}
|
||||
],
|
||||
"type": "select",
|
||||
"value": "F"
|
||||
"value": "C"
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"basic": {
|
||||
"name": ""
|
||||
},
|
||||
"profile": [
|
||||
"steps": [
|
||||
|
||||
]
|
||||
}
|
|
@ -38,12 +38,10 @@ class LogController:
|
|||
async def get_data(self, names, sample_rate='60s'):
|
||||
|
||||
'''
|
||||
|
||||
:param names: name as string or list of names as string
|
||||
:param sample_rate: rate for resampling the data
|
||||
:return:
|
||||
'''
|
||||
|
||||
# make string to array
|
||||
if isinstance(names, list) is False:
|
||||
names = [names]
|
||||
|
@ -87,14 +85,23 @@ class LogController:
|
|||
|
||||
if len(names) > 1:
|
||||
for name in names:
|
||||
|
||||
data[name] = result[name].interpolate(limit_direction='both', limit=10).tolist()
|
||||
else:
|
||||
data[name] = result.interpolate().tolist()
|
||||
|
||||
|
||||
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:
|
||||
'''
|
||||
|
@ -106,14 +113,9 @@ class LogController:
|
|||
return [os.path.basename(x) for x in glob.glob('./logs/sensor_%s.log*' % name)]
|
||||
|
||||
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)
|
||||
for f in all_filenames:
|
||||
|
||||
os.remove(f)
|
||||
|
||||
if name in self.datalogger:
|
||||
|
|
85
cbpi/controller/recipe_controller.py
Normal file
85
cbpi/controller/recipe_controller.py
Normal 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
|
|
@ -3,7 +3,8 @@ import copy
|
|||
import json
|
||||
import logging
|
||||
import os.path
|
||||
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
import shortuuid
|
||||
from cbpi.api.dataclasses import Props, Step
|
||||
from tabulate import tabulate
|
||||
|
@ -54,13 +55,14 @@ class StepController:
|
|||
# create file if not exists
|
||||
if os.path.exists(self.path) is False:
|
||||
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
|
||||
with open(self.path) as json_file:
|
||||
data = json.load(json_file)
|
||||
self.basic_data = data["basic"]
|
||||
self.profile = data["profile"]
|
||||
self.profile = data["steps"]
|
||||
|
||||
|
||||
# Start step after start up
|
||||
self.profile = list(map(lambda item: self.create(item), self.profile))
|
||||
|
@ -73,7 +75,6 @@ class StepController:
|
|||
logging.debug("Add step")
|
||||
item.id = shortuuid.uuid()
|
||||
item.status = StepState.INITIAL
|
||||
print(item)
|
||||
try:
|
||||
type_cfg = self.types.get(item.type)
|
||||
clazz = type_cfg.get("class")
|
||||
|
@ -104,7 +105,7 @@ class StepController:
|
|||
|
||||
async def save(self):
|
||||
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:
|
||||
json.dump(data, file, indent=4, sort_keys=True)
|
||||
self.push_udpate()
|
||||
|
@ -132,6 +133,10 @@ class StepController:
|
|||
self.cbpi.notify(message="BREWING COMPLETE")
|
||||
logging.info("BREWING COMPLETE")
|
||||
|
||||
async def previous(self):
|
||||
logging.info("Trigger Next")
|
||||
|
||||
|
||||
async def next(self):
|
||||
logging.info("Trigger Next")
|
||||
step = self.find_by_status(StepState.ACTIVE)
|
||||
|
@ -190,7 +195,7 @@ class StepController:
|
|||
return result
|
||||
|
||||
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):
|
||||
index = self.get_index_by_id(id)
|
||||
|
@ -215,7 +220,7 @@ class StepController:
|
|||
self.profile = list(filter(lambda item: item.id != id, self.profile))
|
||||
await self.save()
|
||||
|
||||
async def shutdown(self, app):
|
||||
async def shutdown(self, app=None):
|
||||
logging.info("Mash Profile Shutdonw")
|
||||
for p in self.profile:
|
||||
instance = p.instance
|
||||
|
@ -225,6 +230,7 @@ class StepController:
|
|||
await instance.stop()
|
||||
await instance.task
|
||||
await self.save()
|
||||
self.push_udpate()
|
||||
|
||||
def done(self, step, result):
|
||||
if result == StepResult.NEXT:
|
||||
|
@ -245,7 +251,10 @@ class StepController:
|
|||
def get_index_by_id(self, id):
|
||||
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))))
|
||||
|
||||
async def start_step(self,step):
|
||||
|
@ -269,3 +278,31 @@ class StepController:
|
|||
await item.instance.__getattribute__(action)(**parameter)
|
||||
except Exception as 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)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ from cbpi.controller.kettle_controller import KettleController
|
|||
from cbpi.controller.plugin_controller import PluginController
|
||||
from cbpi.controller.sensor_controller import SensorController
|
||||
from cbpi.controller.step_controller import StepController
|
||||
|
||||
from cbpi.controller.recipe_controller import RecipeController
|
||||
from cbpi.controller.system_controller import SystemController
|
||||
|
||||
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_sensor import SensorHttpEndpoints
|
||||
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_system import SystemHttpEndpoints
|
||||
from cbpi.http_endpoints.http_log import LogHttpEndpoints
|
||||
|
@ -97,10 +97,12 @@ class CraftBeerPi:
|
|||
self.system = SystemController(self)
|
||||
self.kettle = KettleController(self)
|
||||
self.step : StepController = StepController(self)
|
||||
self.recipe : RecipeController = RecipeController(self)
|
||||
#self.satellite: SatelliteController = SatelliteController(self)
|
||||
self.dashboard = DashboardController(self)
|
||||
|
||||
self.http_step = StepHttpEndpoints(self)
|
||||
self.http_recipe = RecipeHttpEndpoints(self)
|
||||
self.http_sensor = SensorHttpEndpoints(self)
|
||||
self.http_config = ConfigHttpEndpoints(self)
|
||||
self.http_actor = ActorHttpEndpoints(self)
|
||||
|
|
|
@ -13,18 +13,12 @@ class CustomSensor(CBPiSensor):
|
|||
def __init__(self, cbpi, id, props):
|
||||
super(CustomSensor, self).__init__(cbpi, id, props)
|
||||
self.value = 0
|
||||
|
||||
|
||||
|
||||
|
||||
async def run(self):
|
||||
|
||||
while self.running is True:
|
||||
|
||||
self.value = random.randint(0,10)
|
||||
self.push_update(self.value)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
def get_state(self):
|
||||
return dict(value=self.value)
|
||||
|
||||
|
|
|
@ -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")])
|
||||
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):
|
||||
# ON
|
||||
if state == 1:
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import asyncio
|
||||
from cbpi.api.step import CBPiStep, StepResult
|
||||
from cbpi.api.timer import Timer
|
||||
|
||||
from cbpi.api import *
|
||||
import logging
|
||||
|
||||
|
@ -12,13 +11,17 @@ import logging
|
|||
Property.Kettle(label="Kettle")])
|
||||
class MashStep(CBPiStep):
|
||||
|
||||
@action(key="Custom Step Action", parameters=[])
|
||||
async def hello(self, **kwargs):
|
||||
print("ACTION")
|
||||
@action(key="Custom RESET", parameters=[])
|
||||
async def custom_reset(self, **kwargs):
|
||||
self.summary = ""
|
||||
await self.push_update()
|
||||
|
||||
@action(key="Custom Step Action 2", parameters=[])
|
||||
async def hello2(self, **kwargs):
|
||||
print("ACTION2")
|
||||
|
||||
@action(key="Custom Action", parameters=[Property.Number(label="Value", configurable=True)])
|
||||
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):
|
||||
self.summary = ""
|
||||
|
@ -40,6 +43,7 @@ class MashStep(CBPiStep):
|
|||
await self.push_update()
|
||||
|
||||
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)
|
||||
|
||||
async def run(self):
|
||||
|
@ -56,10 +60,12 @@ class WaitStep(CBPiStep):
|
|||
@action(key="Custom Step Action", parameters=[])
|
||||
async def hello(self, **kwargs):
|
||||
print("ACTION")
|
||||
self.cbpi.notify("ACTION 1 CALLED")
|
||||
|
||||
@action(key="Custom Step Action 2", parameters=[])
|
||||
async def hello2(self, **kwargs):
|
||||
print("ACTION2")
|
||||
self.cbpi.notify("ACTION 2 CALLED")
|
||||
|
||||
async def on_timer_done(self,timer):
|
||||
self.summary = ""
|
||||
|
|
|
@ -7,7 +7,7 @@ from aiohttp import web
|
|||
from cbpi.api import *
|
||||
import os, re, threading, time
|
||||
from subprocess import call
|
||||
|
||||
import random
|
||||
|
||||
def getSensors():
|
||||
try:
|
||||
|
@ -56,7 +56,7 @@ class OneWire(CBPiSensor):
|
|||
|
||||
def __init__(self, cbpi, id, props):
|
||||
super(OneWire, self).__init__(cbpi, id, props)
|
||||
self.value = 0
|
||||
self.value = 200
|
||||
|
||||
async def start(self):
|
||||
await super().start()
|
||||
|
@ -79,6 +79,7 @@ class OneWire(CBPiSensor):
|
|||
async def run(self):
|
||||
while True:
|
||||
self.value = self.t.value
|
||||
|
||||
self.log_data(self.value)
|
||||
self.push_update(self.value)
|
||||
await asyncio.sleep(self.interval)
|
||||
|
|
|
@ -206,6 +206,7 @@ class ActorHttpEndpoints():
|
|||
"""
|
||||
actor_id = request.match_info['id']
|
||||
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)
|
|
@ -1,7 +1,8 @@
|
|||
from cbpi.utils.encoder import ComplexEncoder
|
||||
from aiohttp import web
|
||||
from cbpi.utils.utils import json_dumps
|
||||
from cbpi.api import request_mapping
|
||||
|
||||
import json
|
||||
class LogHttpEndpoints:
|
||||
|
||||
def __init__(self,cbpi):
|
||||
|
@ -140,7 +141,7 @@ class LogHttpEndpoints:
|
|||
return web.json_response(data, dumps=json_dumps)
|
||||
|
||||
@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
|
||||
|
@ -163,19 +164,82 @@ class LogHttpEndpoints:
|
|||
data = await self.cbpi.log.get_data(log_name)
|
||||
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)
|
||||
async def delete_all_logs(self, request):
|
||||
async def clear_log(self, request):
|
||||
"""
|
||||
---
|
||||
description: Get log data for sensor
|
||||
tags:
|
||||
- Log
|
||||
parameters:
|
||||
|
||||
- name: "name"
|
||||
in: "path"
|
||||
description: "Sensor ID"
|
||||
required: true
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
responses:
|
||||
"204":
|
||||
description: successful operation.
|
||||
"""
|
||||
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)
|
||||
|
||||
@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)
|
170
cbpi/http_endpoints/http_recipe.py
Normal file
170
cbpi/http_endpoints/http_recipe.py
Normal 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"))))
|
||||
|
|
@ -253,5 +253,25 @@ class StepHttpEndpoints():
|
|||
await self.controller.call_action(id,data.get("action"), data.get("parameter",[]))
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
from json import JSONEncoder
|
||||
|
||||
from pandas import Timestamp
|
||||
|
||||
class ComplexEncoder(JSONEncoder):
|
||||
|
||||
|
@ -11,7 +12,11 @@ class ComplexEncoder(JSONEncoder):
|
|||
return obj.to_json()
|
||||
elif isinstance(obj, datetime.datetime):
|
||||
return obj.__str__()
|
||||
elif isinstance(obj, Timestamp):
|
||||
print("TIMe")
|
||||
return obj.__str__()
|
||||
else:
|
||||
print(type(obj))
|
||||
raise TypeError()
|
||||
except Exception as e:
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
aiohttp==3.7.3
|
||||
aiohttp==3.7.4
|
||||
aiohttp-auth==0.1.1
|
||||
aiohttp-route-decorator==0.1.4
|
||||
aiohttp-security==0.4.0
|
||||
|
|
216
sample.py
216
sample.py
|
@ -1,208 +1,14 @@
|
|||
|
||||
import pandas as pd
|
||||
import datetime
|
||||
|
||||
from abc import abstractmethod
|
||||
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
|
||||
def dateparse(time_in_secs):
|
||||
'''
|
||||
Internal helper for date parsing
|
||||
:param time_in_secs:
|
||||
:return:
|
||||
'''
|
||||
return datetime.datetime.strptime(time_in_secs, '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
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):
|
||||
|
||||
def __init__(self, timeout, on_done = None, on_update = None) -> None:
|
||||
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
|
||||
|
||||
|
||||
'''
|
||||
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()}})
|
||||
|
|
2
setup.py
2
setup.py
|
@ -15,7 +15,7 @@ setup(name='cbpi',
|
|||
'cbpi': ['*','*.txt', '*.rst', '*.yaml']},
|
||||
|
||||
install_requires=[
|
||||
"aiohttp==3.7.3",
|
||||
"aiohttp==3.7.4",
|
||||
"aiohttp-auth==0.1.1",
|
||||
"aiohttp-route-decorator==0.1.4",
|
||||
"aiohttp-security==0.4.0",
|
||||
|
|
Loading…
Reference in a new issue