craftbeerpi4-pione/cbpi/controller/basic_controller2.py

176 lines
6.7 KiB
Python

import logging
import os.path
import json
from cbpi.api.dataclasses import Fermenter, Actor, Props
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.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:
logging.error("{} Cant start {} - {}".format(self.name, id, e))
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))