craftbeerpi4-pione/cbpi/craftbeerpi.py

329 lines
13 KiB
Python
Raw Permalink Normal View History

2021-02-16 20:37:51 +01:00
2021-03-14 11:52:46 +01:00
import asyncio
import sys
import socket
try:
from asyncio import set_event_loop_policy, WindowsSelectorEventLoopPolicy
except ImportError:
pass
2021-03-14 11:52:46 +01:00
import json
from voluptuous.schema_builder import message
from cbpi.api.dataclasses import NotificationType
2021-03-07 22:11:25 +01:00
from cbpi.controller.notification_controller import NotificationController
2018-11-01 19:50:04 +01:00
import logging
2019-01-01 15:35:35 +01:00
from os import urandom
2019-08-16 21:36:55 +02:00
import os
2022-03-24 07:33:48 +01:00
from cbpi import __version__, __codename__
2018-11-01 19:50:04 +01:00
from aiohttp import web
from aiohttp_auth import auth
from aiohttp_session import session_middleware
from aiohttp_session.cookie_storage import EncryptedCookieStorage
from aiohttp_swagger import setup_swagger
2019-01-05 20:43:48 +01:00
from cbpi.api.exceptions import CBPiException
2019-01-02 21:20:44 +01:00
from voluptuous import MultipleInvalid
2018-11-18 15:40:10 +01:00
2019-01-05 20:43:48 +01:00
from cbpi.controller.dashboard_controller import DashboardController
from cbpi.controller.job_controller import JobController
from cbpi.controller.actor_controller import ActorController
from cbpi.controller.config_controller import ConfigController
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
2021-02-27 20:09:19 +01:00
from cbpi.controller.recipe_controller import RecipeController
2022-02-24 13:46:56 +01:00
from cbpi.controller.fermenter_recipe_controller import FermenterRecipeController
from cbpi.controller.upload_controller import UploadController
from cbpi.controller.fermentation_controller import FermentationController
2019-01-05 20:43:48 +01:00
from cbpi.controller.system_controller import SystemController
2021-03-14 11:52:46 +01:00
from cbpi.controller.satellite_controller import SatelliteController
2021-01-22 23:25:20 +01:00
2019-08-05 20:51:20 +02:00
from cbpi.controller.log_file_controller import LogController
2021-02-16 20:37:51 +01:00
2019-01-05 20:43:48 +01:00
from cbpi.eventbus import CBPiEventBus
from cbpi.http_endpoints.http_login import Login
from cbpi.utils import *
from cbpi.websocket import CBPiWebSocket
from cbpi.http_endpoints.http_actor import ActorHttpEndpoints
2021-01-22 23:25:20 +01:00
2019-01-05 20:43:48 +01:00
from cbpi.http_endpoints.http_config import ConfigHttpEndpoints
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
2021-02-27 20:09:19 +01:00
from cbpi.http_endpoints.http_recipe import RecipeHttpEndpoints
2022-02-24 13:46:56 +01:00
from cbpi.http_endpoints.http_fermenterrecipe import FermenterRecipeHttpEndpoints
2019-08-05 20:51:20 +02:00
from cbpi.http_endpoints.http_plugin import PluginHttpEndpoints
from cbpi.http_endpoints.http_system import SystemHttpEndpoints
2019-08-05 23:00:18 +02:00
from cbpi.http_endpoints.http_log import LogHttpEndpoints
2021-03-07 22:11:25 +01:00
from cbpi.http_endpoints.http_notification import NotificationHttpEndpoints
from cbpi.http_endpoints.http_upload import UploadHttpEndpoints
from cbpi.http_endpoints.http_fermentation import FermentationHttpEndpoints
2018-12-29 00:27:19 +01:00
2021-03-07 22:11:25 +01:00
import shortuuid
2018-12-29 00:27:19 +01:00
logger = logging.getLogger(__name__)
2019-01-05 20:43:48 +01:00
2019-01-02 00:48:36 +01:00
@web.middleware
async def error_middleware(request, handler):
try:
response = await handler(request)
if response.status != 404:
return response
message = response.message
except web.HTTPException as ex:
if ex.status != 404:
raise
message = ex.reason
except CBPiException as ex:
message = str(ex)
return web.json_response(status=500, data={'error': message})
2019-01-02 21:20:44 +01:00
except MultipleInvalid as ex:
return web.json_response(status=500, data={'error': str(ex)})
2019-07-27 21:08:19 +02:00
except Exception as ex:
return web.json_response(status=500, data={'error': str(ex)})
2019-01-21 22:33:29 +01:00
return web.json_response(status=500, data={'error': message})
2019-01-02 00:48:36 +01:00
2018-11-04 00:47:26 +01:00
2021-02-01 01:38:04 +01:00
class CraftBeerPi:
2019-01-05 20:43:48 +01:00
def __init__(self, configFolder):
operationsystem= sys.platform
if operationsystem.startswith('win'):
set_event_loop_policy(WindowsSelectorEventLoopPolicy())
2021-02-01 01:38:04 +01:00
self.path = os.sep.join(os.path.abspath(__file__).split(os.sep)[:-1]) # The path to the package dir
2021-02-02 21:22:59 +01:00
2021-01-22 23:25:20 +01:00
self.version = __version__
2022-03-24 07:33:48 +01:00
self.codename = __codename__
self.config_folder = configFolder
self.static_config = load_config(configFolder.get_file_path("config.yaml"))
2021-02-16 20:37:51 +01:00
2018-11-01 19:50:04 +01:00
logger.info("Init CraftBeerPI")
2019-01-01 15:35:35 +01:00
2018-11-01 19:50:04 +01:00
policy = auth.SessionTktAuthentication(urandom(32), 60, include_ip=True)
2021-02-01 01:38:04 +01:00
middlewares = [web.normalize_path_middleware(), session_middleware(EncryptedCookieStorage(urandom(32))),
auth.auth_middleware(policy), error_middleware]
# max upload size increased to 5 Mb. Default is 1 Mb -> config and svg upload
self.app = web.Application(middlewares=middlewares, client_max_size=5*1024*1024)
2019-07-31 07:58:54 +02:00
self.app["cbpi"] = self
2018-12-16 21:42:47 +01:00
2019-01-01 15:35:35 +01:00
self._setup_shutdownhook()
self.initializer = []
2019-01-14 07:33:59 +01:00
self.bus = CBPiEventBus(self.app.loop, self)
2019-01-01 15:35:35 +01:00
self.job = JobController(self)
2019-01-14 07:33:59 +01:00
self.config = ConfigController(self)
self.ws = CBPiWebSocket(self)
2018-11-01 19:50:04 +01:00
self.actor = ActorController(self)
self.sensor = SensorController(self)
2018-11-04 00:47:26 +01:00
self.plugin = PluginController(self)
2019-08-05 20:51:20 +02:00
self.log = LogController(self)
2018-11-01 19:50:04 +01:00
self.system = SystemController(self)
2018-11-18 15:40:10 +01:00
self.kettle = KettleController(self)
self.fermenter : FermentationController = FermentationController(self)
2021-02-16 20:37:51 +01:00
self.step : StepController = StepController(self)
2021-02-27 20:09:19 +01:00
self.recipe : RecipeController = RecipeController(self)
2022-02-24 13:46:56 +01:00
self.fermenterrecipe : FermenterRecipeController = FermenterRecipeController(self)
self.upload : UploadController = UploadController(self)
2021-03-07 22:11:25 +01:00
self.notification : NotificationController = NotificationController(self)
2021-03-14 11:52:46 +01:00
self.satellite = None
if str(self.static_config.get("mqtt", False)).lower() == "true":
2021-03-14 11:52:46 +01:00
self.satellite: SatelliteController = SatelliteController(self)
2021-02-01 01:38:04 +01:00
self.dashboard = DashboardController(self)
2019-01-02 21:20:44 +01:00
self.http_step = StepHttpEndpoints(self)
2021-02-27 20:09:19 +01:00
self.http_recipe = RecipeHttpEndpoints(self)
2022-02-24 13:46:56 +01:00
self.http_fermenterrecipe = FermenterRecipeHttpEndpoints(self)
2019-01-02 21:20:44 +01:00
self.http_sensor = SensorHttpEndpoints(self)
self.http_config = ConfigHttpEndpoints(self)
self.http_actor = ActorHttpEndpoints(self)
self.http_kettle = KettleHttpEndpoints(self)
self.http_dashboard = DashBoardHttpEndpoints(self)
2019-07-31 07:58:54 +02:00
self.http_plugin = PluginHttpEndpoints(self)
2019-08-05 20:51:20 +02:00
self.http_system = SystemHttpEndpoints(self)
2019-08-05 23:00:18 +02:00
self.http_log = LogHttpEndpoints(self)
2021-03-07 22:11:25 +01:00
self.http_notification = NotificationHttpEndpoints(self)
self.http_upload = UploadHttpEndpoints(self)
self.http_fermenter = FermentationHttpEndpoints(self)
2018-11-01 19:50:04 +01:00
self.login = Login(self)
2018-11-04 01:55:54 +01:00
2019-01-01 15:35:35 +01:00
def _setup_shutdownhook(self):
self.shutdown = False
2019-01-14 07:33:59 +01:00
2019-01-01 15:35:35 +01:00
async def on_cleanup(app):
self.shutdown = True
2018-11-01 19:50:04 +01:00
2019-01-01 15:35:35 +01:00
self.app.on_cleanup.append(on_cleanup)
2018-11-01 19:50:04 +01:00
2018-11-04 00:47:26 +01:00
def register_on_startup(self, obj):
2021-02-01 01:38:04 +01:00
for method in [getattr(obj, f) for f in dir(obj) if
callable(getattr(obj, f)) and hasattr(getattr(obj, f), "on_startup")]:
2018-11-04 00:47:26 +01:00
name = method.__getattribute__("name")
order = method.__getattribute__("order")
self.initializer.append(dict(name=name, method=method, order=order))
2018-12-07 00:18:35 +01:00
def register(self, obj, url_prefix=None, static=None):
2018-11-16 20:35:59 +01:00
'''
This method parses the provided object
:param obj: the object wich will be parsed for registration
:param url_prefix: that prefix for HTTP Endpoints
:return: None
'''
2018-12-07 00:18:35 +01:00
self.register_http_endpoints(obj, url_prefix, static)
2019-01-01 15:35:35 +01:00
self.bus.register_object(obj)
2021-02-01 01:38:04 +01:00
# self.ws.register_object(obj)
2019-01-01 15:35:35 +01:00
self.job.register_background_task(obj)
2018-11-04 00:47:26 +01:00
self.register_on_startup(obj)
2018-11-01 19:50:04 +01:00
2018-12-07 00:18:35 +01:00
def register_http_endpoints(self, obj, url_prefix=None, static=None):
2019-01-02 21:20:44 +01:00
if url_prefix is None:
2021-02-01 01:38:04 +01:00
logger.debug(
"URL Prefix is None for %s. No endpoints will be registered. Please set / explicit if you want to add it to the root path" % obj)
2019-01-02 21:20:44 +01:00
return
2018-11-01 19:50:04 +01:00
routes = []
2021-02-01 01:38:04 +01:00
for method in [getattr(obj, f) for f in dir(obj) if
callable(getattr(obj, f)) and hasattr(getattr(obj, f), "route")]:
2018-11-01 19:50:04 +01:00
http_method = method.__getattribute__("method")
path = method.__getattribute__("path")
2018-11-01 21:25:42 +01:00
class_name = method.__self__.__class__.__name__
2021-02-01 01:38:04 +01:00
logger.debug(
"Register Endpoint : %s.%s %s %s%s " % (class_name, method.__name__, http_method, url_prefix, path))
2018-11-01 19:50:04 +01:00
def add_post():
routes.append(web.post(method.__getattribute__("path"), method))
def add_get():
2019-01-02 00:48:36 +01:00
routes.append(web.get(method.__getattribute__("path"), method, allow_head=False))
2018-11-01 19:50:04 +01:00
def add_delete():
routes.append(web.delete(path, method))
def add_put():
routes.append(web.put(path, method))
2018-11-01 21:25:42 +01:00
2018-11-01 19:50:04 +01:00
switcher = {
"POST": add_post,
"GET": add_get,
"DELETE": add_delete,
"PUT": add_put
}
switcher[http_method]()
2019-01-02 21:20:44 +01:00
if url_prefix != "/":
2021-02-01 01:38:04 +01:00
logger.debug("URL Prefix: %s " % (url_prefix,))
2018-11-01 19:50:04 +01:00
sub = web.Application()
sub.add_routes(routes)
2018-12-07 00:18:35 +01:00
if static is not None:
sub.add_routes([web.static('/static', static, show_index=True)])
2018-11-16 20:35:59 +01:00
self.app.add_subapp(url_prefix, sub)
2018-11-01 19:50:04 +01:00
else:
2021-02-01 01:38:04 +01:00
2018-11-01 19:50:04 +01:00
self.app.add_routes(routes)
2018-11-04 00:47:26 +01:00
def _swagger_setup(self):
2018-11-16 20:35:59 +01:00
'''
2021-02-01 01:38:04 +01:00
Internal method to expose REST API documentation by swagger
2018-11-16 20:35:59 +01:00
:return:
'''
2018-11-04 00:47:26 +01:00
long_description = """
2018-11-18 15:40:10 +01:00
This is the api for CraftBeerPi
2018-11-04 00:47:26 +01:00
"""
setup_swagger(self.app,
description=long_description,
2021-01-22 23:25:20 +01:00
title="CraftBeerPi",
api_version=self.version,
2018-11-04 00:47:26 +01:00
contact="info@craftbeerpi.com")
2021-02-16 20:37:51 +01:00
2021-03-14 11:52:46 +01:00
def notify(self, title: str, message: str, type: NotificationType = NotificationType.INFO, action=[]) -> None:
2021-03-07 22:11:25 +01:00
self.notification.notify(title, message, type, action)
2021-03-14 11:52:46 +01:00
def push_update(self, topic, data, retain=False) -> None:
2021-03-14 17:40:44 +01:00
if self.satellite is not None:
asyncio.create_task(self.satellite.publish(topic=topic, message=json.dumps(data), retain=retain))
2021-02-16 20:37:51 +01:00
async def call_initializer(self, app):
self.initializer = sorted(self.initializer, key=lambda k: k['order'])
for i in self.initializer:
logger.info("CALL INITIALIZER %s - %s " % (i["name"], i["method"].__name__))
await i["method"]()
2018-11-04 00:47:26 +01:00
def _print_logo(self):
from pyfiglet import Figlet
f = Figlet(font='big')
logger.warning("\n%s" % f.renderText("CraftBeerPi %s " % self.version))
logger.warning("www.CraftBeerPi.com")
2024-02-23 17:35:01 +01:00
logger.warning("(c) 2021 - 2024 Manuel Fritsch / Alexander Vollkopf")
2018-11-04 00:47:26 +01:00
2019-01-02 00:48:36 +01:00
def _setup_http_index(self):
async def http_index(request):
url = self.config.static.get("index_url")
2021-02-01 01:38:04 +01:00
2019-01-02 00:48:36 +01:00
if url is not None:
2019-01-02 00:48:36 +01:00
raise web.HTTPFound(url)
else:
2019-07-27 21:08:19 +02:00
return web.Response(text="Hello from CraftbeerPi!")
2019-01-02 00:48:36 +01:00
2021-02-01 01:38:04 +01:00
self.app.add_routes([web.get('/', http_index),
web.static('/static', os.path.join(os.path.dirname(__file__), "static"), show_index=True)])
def testport(self, port=8000):
HOST = "localhost"
# Creates a new socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Try to connect to the given host and port
if sock.connect_ex((HOST, port)) == 0:
#print("Port " + str(port) + " is open") # Connected successfully
isrunning = True
else:
#print("Port " + str(port) + " is closed") # Failed to connect because port is in use (or bad host)
isrunning = False
# Close the connection
sock.close()
return isrunning
2019-01-02 00:48:36 +01:00
async def init_serivces(self):
2018-11-04 00:47:26 +01:00
self._print_logo()
2019-01-01 15:35:35 +01:00
await self.job.init()
2021-02-16 20:37:51 +01:00
2019-01-01 15:35:35 +01:00
await self.config.init()
2021-03-14 11:52:46 +01:00
if self.satellite is not None:
await self.satellite.init()
2019-01-02 00:48:36 +01:00
self._setup_http_index()
self.plugin.load_plugins()
self.plugin.load_plugins_from_evn()
await self.fermenter.init()
await self.sensor.init()
2021-01-22 23:25:20 +01:00
await self.step.init()
2021-02-16 20:37:51 +01:00
await self.actor.init()
await self.kettle.init()
await self.call_initializer(self.app)
2019-01-02 21:20:44 +01:00
await self.dashboard.init()
2022-11-27 15:16:18 +01:00
2018-11-04 00:47:26 +01:00
self._swagger_setup()
2018-11-16 20:35:59 +01:00
return self.app
2018-11-16 20:35:59 +01:00
def start(self):
port=self.static_config.get("port",8000)
if not self.testport(port):
web.run_app(self.init_serivces(), port=port)
else:
logging.error("Port {} is already in use! Please check, if server is already running (e.g. in automode)".format(port))
exit(1)