craftbeerpi4-pione/cbpi/controller/basic_controller2.py

179 lines
6.9 KiB
Python

import logging
import os.path
import json
from cbpi.api.dataclasses import Fermenter, Actor, Props, NotificationType
import sys, os
import shortuuid
import asyncio
from tabulate import tabulate
class BasicController:
def __init__(self, cbpi, resource, file):
self.resource = resource
self.update_key = ""
self.sorting = False
self.name = self.__class__.__name__
self.cbpi = cbpi
self.cbpi.register(self)
self.service = self
self.types = {}
self.logger = logging.getLogger(__name__)
self.data = []
self.autostart = True
#self._loop = asyncio.get_event_loop()
self.path = self.cbpi.config_folder.get_file_path(file)
self.cbpi.app.on_cleanup.append(self.shutdown)
async def init(self):
await self.load()
def create(self, data):
return self.resource(data.get("id"), data.get("name"), type=data.get("type"), props=Props(data.get("props", {})) )
async def load(self):
try:
logging.info("{} Load ".format(self.name))
with open(self.path) as json_file:
data = json.load(json_file)
data['data'].sort(key=lambda x: x.get('name').upper())
for i in data["data"]:
self.data.append(self.create(i))
if self.autostart is True:
for item in self.data:
logging.info("{} Starting ".format(self.name))
await self.start(item.id)
await self.push_udpate()
except Exception as e:
#logging.error(e)
logging.warning("Invalid {} file - Creating empty file".format(self.path))
os.remove(self.path)
with open(self.path, "w") as file:
json.dump(dict( data=[]), file, indent=4, sort_keys=True)
with open(self.path) as json_file:
data = json.load(json_file)
data['data'].sort(key=lambda x: x.get('name').upper())
for i in data["data"]:
self.data.append(self.create(i))
if self.autostart is True:
for item in self.data:
logging.info("{} Starting ".format(self.name))
await self.start(item.id)
await self.push_udpate()
async def save(self):
logging.info("{} Save ".format(self.name))
data = dict(data=list(map(lambda actor: actor.to_dict(), self.data)))
with open(self.path, "w") as file:
json.dump(data, file, indent=4, sort_keys=True)
await self.push_udpate()
async def push_udpate(self):
self.cbpi.ws.send(dict(topic=self.update_key, data=list(map(lambda item: item.to_dict(), self.data))),self.sorting)
#self.cbpi.push_update("cbpi/{}".format(self.update_key), list(map(lambda item: item.to_dict(), self.data)))
for item in self.data:
self.cbpi.push_update("cbpi/{}/{}".format(self.update_key,item.id), item.to_dict())
def find_by_id(self, id):
return next((item for item in self.data if item.id == id), None)
def get_index_by_id(self, id):
return next((i for i, item in enumerate(self.data) if item.id == id), None)
async def shutdown(self, app):
logging.info("{} Shutdown ".format(self.name))
tasks = []
for item in self.data:
if item.instance is not None and item.instance.running is True:
item.instance.task.cancel()
tasks.append(item.instance.task)
await asyncio.gather(*tasks)
await self.save()
async def stop(self, id):
logging.info("{} Stop Id {} ".format(self.name, id))
try:
item = self.find_by_id(id)
await item.instance.stop()
item.instance.running = False
await self.push_udpate()
except Exception as e:
logging.error("{} Cant stop {} - {}".format(self.name, id, e))
async def start(self, id):
logging.info("{} Start Id {} ".format(self.name, id))
try:
item = self.find_by_id(id)
if item.instance is not None and item.instance.running is True:
logging.warning("{} already running {}".format(self.name, id))
return
if item.type is None:
logging.warning("{} No Type {}".format(self.name, id))
return
clazz = self.types[item.type]["class"]
item.instance = clazz(self.cbpi, item.id, item.props)
await item.instance.start()
item.instance.running = True
item.instance.task = asyncio.get_event_loop().create_task(item.instance._run())
#item.instance.task = self._loop.create_task(item.instance._run())
logging.info("{} started {}".format(self.name, id))
# await self.push_udpate()
except Exception as e:
line="{} Cant start {} - {}".format(self.name, id, e)
logging.error(line)
self.cbpi.notify("Error", line, NotificationType.ERROR)
def get_types(self):
# logging.info("{} Get Types".format(self.name))
result = {}
for key, value in self.types.items():
result[key] = dict(name=value.get("name"), properties=value.get("properties"), actions=value.get("actions"))
return result
def get_state(self):
# logging.info("{} Get State".format(self.name))
return {"data": list(map(lambda x: x.to_dict(), self.data)), "types":self.get_types()}
async def add(self, item):
logging.info("{} Add".format(self.name))
item.id = shortuuid.uuid()
self.data.append(item)
if self.autostart is True:
await self.start(item.id)
await self.save()
return item
async def update(self, item):
logging.info("{} Get Update".format(self.name))
await self.stop(item.id)
self.data = list(map(lambda old_item: item if old_item.id == item.id else old_item, self.data))
if self.autostart is True:
await self.start(item.id)
await self.save()
return self.find_by_id(item.id)
async def delete(self, id) -> None:
logging.info("{} Delete".format(self.name))
await self.stop(id)
self.data = list(filter(lambda x: x.id != id, self.data))
await self.save()
async def call_action(self, id, action, parameter) -> None:
logging.info("{} call all Action {} {}".format(self.name, id, action))
try:
item = self.find_by_id(id)
await item.instance.__getattribute__(action)(**parameter)
except Exception as e:
logging.error("{} Failed to call action on {} {} {}".format(self.name, id, action, e))