diff --git a/cbpi/__init__.py b/cbpi/__init__.py index f5b9f8a..2f67a14 100644 --- a/cbpi/__init__.py +++ b/cbpi/__init__.py @@ -1 +1 @@ -__version__ = "4.0.0.26" \ No newline at end of file +__version__ = "4.0.0.27" \ No newline at end of file diff --git a/cbpi/api/config.py b/cbpi/api/config.py index 35ed0ea..0188d65 100644 --- a/cbpi/api/config.py +++ b/cbpi/api/config.py @@ -4,5 +4,8 @@ class ConfigType(Enum): STRING = "string" NUMBER = "number" SELECT = "select" + KETTLE = "kettle" + ACTOR = "actor" + SENSOR = "sensor" diff --git a/cbpi/cli.py b/cbpi/cli.py index f9851b2..53e4c30 100644 --- a/cbpi/cli.py +++ b/cbpi/cli.py @@ -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") diff --git a/cbpi/config/config.json b/cbpi/config/config.json index 92ef521..731cfd3 100644 --- a/cbpi/config/config.json +++ b/cbpi/config/config.json @@ -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" } } \ No newline at end of file diff --git a/cbpi/config/step_data.json b/cbpi/config/step_data.json index 4bd1d70..60a4e28 100644 --- a/cbpi/config/step_data.json +++ b/cbpi/config/step_data.json @@ -2,7 +2,7 @@ "basic": { "name": "" }, - "profile": [ + "steps": [ ] } \ No newline at end of file diff --git a/cbpi/controller/log_file_controller.py b/cbpi/controller/log_file_controller.py index 0033c3f..d4d6576 100644 --- a/cbpi/controller/log_file_controller.py +++ b/cbpi/controller/log_file_controller.py @@ -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: diff --git a/cbpi/controller/recipe_controller.py b/cbpi/controller/recipe_controller.py new file mode 100644 index 0000000..4516bf2 --- /dev/null +++ b/cbpi/controller/recipe_controller.py @@ -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 \ No newline at end of file diff --git a/cbpi/controller/step_controller.py b/cbpi/controller/step_controller.py index c7b91a8..7a0ebe7 100644 --- a/cbpi/controller/step_controller.py +++ b/cbpi/controller/step_controller.py @@ -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,14 +55,15 @@ 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)) if startActive is True: @@ -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() @@ -131,7 +132,11 @@ 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,8 +251,11 @@ 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): - self.cbpi.ws.send(dict(topic="step_update", data=list(map(lambda item: item.to_dict(), self.profile)))) + 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): try: @@ -268,4 +277,32 @@ class StepController: item = self.find_by_id(id) await item.instance.__getattribute__(action)(**parameter) except Exception as e: - logging.error("Step Controller -Faild to call action on {} {} {}".format(id, action, e)) \ No newline at end of file + 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) + diff --git a/cbpi/craftbeerpi.py b/cbpi/craftbeerpi.py index 9ef089f..89f31da 100644 --- a/cbpi/craftbeerpi.py +++ b/cbpi/craftbeerpi.py @@ -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) diff --git a/cbpi/extension/dummysensor/__init__.py b/cbpi/extension/dummysensor/__init__.py index 2453799..fd24b73 100644 --- a/cbpi/extension/dummysensor/__init__.py +++ b/cbpi/extension/dummysensor/__init__.py @@ -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) diff --git a/cbpi/extension/gpioactor/__init__.py b/cbpi/extension/gpioactor/__init__.py index bb470fa..4c3b3f0 100644 --- a/cbpi/extension/gpioactor/__init__.py +++ b/cbpi/extension/gpioactor/__init__.py @@ -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: diff --git a/cbpi/extension/mashstep/__init__.py b/cbpi/extension/mashstep/__init__.py index b0da35e..05dd9eb 100644 --- a/cbpi/extension/mashstep/__init__.py +++ b/cbpi/extension/mashstep/__init__.py @@ -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,14 +11,18 @@ 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 Step Action 2", parameters=[]) - async def hello2(self, **kwargs): - print("ACTION2") + @action(key="Custom RESET", parameters=[]) + async def custom_reset(self, **kwargs): + self.summary = "" + await self.push_update() + + @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 = "" await self.next() @@ -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 = "" diff --git a/cbpi/extension/onewire/__init__.py b/cbpi/extension/onewire/__init__.py index 777d21b..49cd610 100644 --- a/cbpi/extension/onewire/__init__.py +++ b/cbpi/extension/onewire/__init__.py @@ -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) diff --git a/cbpi/http_endpoints/http_actor.py b/cbpi/http_endpoints/http_actor.py index 0471912..0572206 100644 --- a/cbpi/http_endpoints/http_actor.py +++ b/cbpi/http_endpoints/http_actor.py @@ -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) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_log.py b/cbpi/http_endpoints/http_log.py index f6de7a9..2ddaadd 100644 --- a/cbpi/http_endpoints/http_log.py +++ b/cbpi/http_endpoints/http_log.py @@ -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) - return web.Response(status=204) \ No newline at end of file + 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) \ No newline at end of file diff --git a/cbpi/http_endpoints/http_recipe.py b/cbpi/http_endpoints/http_recipe.py new file mode 100644 index 0000000..39f5b50 --- /dev/null +++ b/cbpi/http_endpoints/http_recipe.py @@ -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")))) + \ No newline at end of file diff --git a/cbpi/http_endpoints/http_step.py b/cbpi/http_endpoints/http_step.py index b58aae2..9a3568f 100644 --- a/cbpi/http_endpoints/http_step.py +++ b/cbpi/http_endpoints/http_step.py @@ -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) + + + + \ No newline at end of file diff --git a/cbpi/utils/encoder.py b/cbpi/utils/encoder.py index 136cb82..616d3a9 100644 --- a/cbpi/utils/encoder.py +++ b/cbpi/utils/encoder.py @@ -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: diff --git a/requirements.txt b/requirements.txt index 9ec385a..739db3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/sample.py b/sample.py index 8d2a30d..68c63eb 100644 --- a/sample.py +++ b/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()}}) diff --git a/setup.py b/setup.py index facae91..0f78330 100644 --- a/setup.py +++ b/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",