mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2025-06-27 16:36:53 +00:00
Some checks failed
docker-build / platform-excludes (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Run Pytest on Pull Request / test (push) Has been cancelled
docker-build / build (push) Has been cancelled
docker-build / merge (push) Has been cancelled
Close stale pull requests/issues / Find Stale issues and PRs (push) Has been cancelled
* Fix logging configuration issues that made logging stop operation. Switch to Loguru logging (from Python logging). Enable console and file logging with different log levels. Add logging documentation. * Fix logging configuration and EOS configuration out of sync. Added tracking support for nested value updates of Pydantic models. This used to update the logging configuration when the EOS configurationm for logging is changed. Should keep logging config and EOS config in sync as long as all changes to the EOS logging configuration are done by set_nested_value(), which is the case for the REST API. * Fix energy management task looping endlessly after the second update when trying to update the last_update datetime. * Fix get_nested_value() to correctly take values from the dicts in a Pydantic model instance. * Fix usage of model classes instead of model instances in nested value access when evaluation the value type that is associated to each key. * Fix illegal json format in prediction documentation for PVForecastAkkudoktor provider. * Fix documentation qirks and add EOS Connect to integrations. * Support deprecated fields in configuration in documentation generation and EOSdash. * Enhance EOSdash demo to show BrightSky humidity data (that is often missing) * Update documentation reference to German EOS installation videos. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
340 lines
9.9 KiB
Python
340 lines
9.9 KiB
Python
import argparse
|
|
import os
|
|
import sys
|
|
import traceback
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import psutil
|
|
import uvicorn
|
|
from fasthtml.common import FileResponse, JSONResponse
|
|
from loguru import logger
|
|
from monsterui.core import FastHTML, Theme
|
|
|
|
from akkudoktoreos.config.config import get_config
|
|
|
|
# Pages
|
|
from akkudoktoreos.server.dash.admin import Admin
|
|
from akkudoktoreos.server.dash.bokeh import BokehJS
|
|
from akkudoktoreos.server.dash.components import Page
|
|
from akkudoktoreos.server.dash.configuration import ConfigKeyUpdate, Configuration
|
|
from akkudoktoreos.server.dash.demo import Demo
|
|
from akkudoktoreos.server.dash.footer import Footer
|
|
from akkudoktoreos.server.dash.hello import Hello
|
|
from akkudoktoreos.server.server import get_default_host, wait_for_port_free
|
|
|
|
config_eos = get_config()
|
|
|
|
# The favicon for EOSdash
|
|
favicon_filepath = Path(__file__).parent.joinpath("dash/assets/favicon/favicon.ico")
|
|
if not favicon_filepath.exists():
|
|
raise ValueError(f"Does not exist {favicon_filepath}")
|
|
|
|
# Command line arguments
|
|
args: Optional[argparse.Namespace] = None
|
|
|
|
|
|
# Get frankenui and tailwind headers via CDN using Theme.green.headers()
|
|
# Add Bokeh headers
|
|
hdrs = (
|
|
Theme.green.headers(highlightjs=True),
|
|
BokehJS,
|
|
)
|
|
|
|
# The EOSdash application
|
|
app: FastHTML = FastHTML(
|
|
title="EOSdash",
|
|
hdrs=hdrs,
|
|
secret_key=os.getenv("EOS_SERVER__EOSDASH_SESSKEY"),
|
|
)
|
|
|
|
|
|
def eos_server() -> tuple[str, int]:
|
|
"""Retrieves the EOS server host and port configuration.
|
|
|
|
If `args` is provided, it uses the `eos_host` and `eos_port` from `args`.
|
|
Otherwise, it falls back to the values from `config_eos.server`.
|
|
|
|
Returns:
|
|
tuple[str, int]: A tuple containing:
|
|
- `eos_host` (str): The EOS server hostname or IP.
|
|
- `eos_port` (int): The EOS server port.
|
|
"""
|
|
if args is None:
|
|
eos_host = str(config_eos.server.host)
|
|
eos_port = config_eos.server.port
|
|
else:
|
|
eos_host = args.eos_host
|
|
eos_port = args.eos_port
|
|
eos_host = eos_host if eos_host else get_default_host()
|
|
eos_port = eos_port if eos_port else 8503
|
|
|
|
return eos_host, eos_port
|
|
|
|
|
|
@app.get("/favicon.ico")
|
|
def get_eosdash_favicon(): # type: ignore
|
|
"""Get favicon."""
|
|
return FileResponse(path=favicon_filepath)
|
|
|
|
|
|
@app.get("/")
|
|
def get_eosdash(): # type: ignore
|
|
"""Serves the main EOSdash page.
|
|
|
|
Returns:
|
|
Page: The main dashboard page with navigation links and footer.
|
|
"""
|
|
return Page(
|
|
None,
|
|
{
|
|
"EOSdash": "/eosdash/hello",
|
|
"Config": "/eosdash/configuration",
|
|
"Demo": "/eosdash/demo",
|
|
"Admin": "/eosdash/admin",
|
|
},
|
|
Hello(),
|
|
Footer(*eos_server()),
|
|
"/eosdash/footer",
|
|
)
|
|
|
|
|
|
@app.get("/eosdash/footer")
|
|
def get_eosdash_footer(): # type: ignore
|
|
"""Serves the EOSdash Foooter information.
|
|
|
|
Returns:
|
|
Footer: The Footer component.
|
|
"""
|
|
return Footer(*eos_server())
|
|
|
|
|
|
@app.get("/eosdash/hello")
|
|
def get_eosdash_hello(): # type: ignore
|
|
"""Serves the EOSdash Hello page.
|
|
|
|
Returns:
|
|
Hello: The Hello page component.
|
|
"""
|
|
return Hello()
|
|
|
|
|
|
@app.get("/eosdash/admin")
|
|
def get_eosdash_admin(): # type: ignore
|
|
"""Serves the EOSdash Admin page.
|
|
|
|
Returns:
|
|
Admin: The Admin page component.
|
|
"""
|
|
return Admin(*eos_server())
|
|
|
|
|
|
@app.post("/eosdash/admin")
|
|
def post_eosdash_admin(data: dict): # type: ignore
|
|
return Admin(*eos_server(), data)
|
|
|
|
|
|
@app.get("/eosdash/configuration")
|
|
def get_eosdash_configuration(): # type: ignore
|
|
"""Serves the EOSdash Configuration page.
|
|
|
|
Returns:
|
|
Configuration: The Configuration page component.
|
|
"""
|
|
return Configuration(*eos_server())
|
|
|
|
|
|
@app.put("/eosdash/configuration")
|
|
def put_eosdash_configuration(data: dict): # type: ignore
|
|
return ConfigKeyUpdate(*eos_server(), data["key"], data["value"])
|
|
|
|
|
|
@app.get("/eosdash/demo")
|
|
def get_eosdash_demo(): # type: ignore
|
|
"""Serves the EOSdash Demo page.
|
|
|
|
Returns:
|
|
Demo: The Demo page component.
|
|
"""
|
|
return Demo(*eos_server())
|
|
|
|
|
|
@app.get("/eosdash/health")
|
|
def get_eosdash_health(): # type: ignore
|
|
"""Health check endpoint to verify that the EOSdash server is alive."""
|
|
return JSONResponse(
|
|
{
|
|
"status": "alive",
|
|
"pid": psutil.Process().pid,
|
|
}
|
|
)
|
|
|
|
|
|
@app.get("/eosdash/assets/{fname:path}.{ext:static}")
|
|
def get_eosdash_assets(fname: str, ext: str): # type: ignore
|
|
"""Get assets."""
|
|
asset_filepath = Path(__file__).parent.joinpath(f"dash/assets/{fname}.{ext}")
|
|
return FileResponse(path=asset_filepath)
|
|
|
|
|
|
def run_eosdash() -> None:
|
|
"""Run the EOSdash server with the specified configurations.
|
|
|
|
This function starts the EOSdash server using the Uvicorn ASGI server. It accepts
|
|
arguments for the host, port, log level, access log, and reload options. The
|
|
log level is converted to lowercase to ensure compatibility with Uvicorn's
|
|
expected log level format. If an error occurs while attempting to bind the
|
|
server to the specified host and port, an error message is logged and the
|
|
application exits.
|
|
|
|
Returns:
|
|
None
|
|
"""
|
|
# Setup parameters from args, config_eos and default
|
|
# Remember parameters that are also in config
|
|
# - EOS host
|
|
if args and args.eos_host:
|
|
eos_host = args.eos_host
|
|
elif config_eos.server.host:
|
|
eos_host = config_eos.server.host
|
|
else:
|
|
eos_host = get_default_host()
|
|
config_eos.server.host = eos_host
|
|
# - EOS port
|
|
if args and args.eos_port:
|
|
eos_port = args.eos_port
|
|
elif config_eos.server.port:
|
|
eos_port = config_eos.server.port
|
|
else:
|
|
eos_port = 8503
|
|
config_eos.server.port = eos_port
|
|
# - EOSdash host
|
|
if args and args.host:
|
|
eosdash_host = args.host
|
|
elif config_eos.server.eosdash.host:
|
|
eosdash_host = config_eos.server.eosdash_host
|
|
else:
|
|
eosdash_host = get_default_host()
|
|
config_eos.server.eosdash_host = eosdash_host
|
|
# - EOS port
|
|
if args and args.port:
|
|
eosdash_port = args.port
|
|
elif config_eos.server.eosdash_port:
|
|
eosdash_port = config_eos.server.eosdash_port
|
|
else:
|
|
eosdash_port = 8504
|
|
config_eos.server.eosdash_port = eosdash_port
|
|
# - log level
|
|
if args and args.log_level:
|
|
log_level = args.log_level
|
|
else:
|
|
log_level = "info"
|
|
# - access log
|
|
if args and args.access_log:
|
|
access_log = args.access_log
|
|
else:
|
|
access_log = False
|
|
# - reload
|
|
if args and args.reload:
|
|
reload = args.reload
|
|
else:
|
|
reload = False
|
|
|
|
# Make hostname Windows friendly
|
|
if eosdash_host == "0.0.0.0" and os.name == "nt": # noqa: S104
|
|
eosdash_host = "localhost"
|
|
|
|
# Wait for EOSdash port to be free - e.g. in case of restart
|
|
wait_for_port_free(eosdash_port, timeout=120, waiting_app_name="EOSdash")
|
|
|
|
try:
|
|
uvicorn.run(
|
|
"akkudoktoreos.server.eosdash:app",
|
|
host=eosdash_host,
|
|
port=eosdash_port,
|
|
log_level=log_level.lower(),
|
|
access_log=access_log,
|
|
reload=reload,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Could not bind to host {eosdash_host}:{eosdash_port}. Error: {e}")
|
|
raise e
|
|
|
|
|
|
def main() -> None:
|
|
"""Parse command-line arguments and start the EOSdash server with the specified options.
|
|
|
|
This function sets up the argument parser to accept command-line arguments for
|
|
host, port, log_level, access_log, and reload. It uses default values from the
|
|
config module if arguments are not provided. After parsing the arguments,
|
|
it starts the EOSdash server with the specified configurations.
|
|
|
|
Command-line Arguments:
|
|
--host (str): Host for the EOSdash server (default: value from config).
|
|
--port (int): Port for the EOSdash server (default: value from config).
|
|
--eos-host (str): Host for the EOS server (default: value from config).
|
|
--eos-port (int): Port for the EOS server (default: value from config).
|
|
--log_level (str): Log level for the server. Options: "critical", "error", "warning", "info", "debug", "trace" (default: "info").
|
|
--access_log (bool): Enable or disable access log. Options: True or False (default: False).
|
|
--reload (bool): Enable or disable auto-reload. Useful for development. Options: True or False (default: False).
|
|
"""
|
|
parser = argparse.ArgumentParser(description="Start EOSdash server.")
|
|
|
|
parser.add_argument(
|
|
"--host",
|
|
type=str,
|
|
default=str(config_eos.server.eosdash_host),
|
|
help="Host for the EOSdash server (default: value from config)",
|
|
)
|
|
parser.add_argument(
|
|
"--port",
|
|
type=int,
|
|
default=config_eos.server.eosdash_port,
|
|
help="Port for the EOSdash server (default: value from config)",
|
|
)
|
|
parser.add_argument(
|
|
"--eos-host",
|
|
type=str,
|
|
default=str(config_eos.server.host),
|
|
help="Host of the EOS server (default: value from config)",
|
|
)
|
|
parser.add_argument(
|
|
"--eos-port",
|
|
type=int,
|
|
default=config_eos.server.port,
|
|
help="Port of the EOS server (default: value from config)",
|
|
)
|
|
parser.add_argument(
|
|
"--log_level",
|
|
type=str,
|
|
default="info",
|
|
help='Log level for the server. Options: "critical", "error", "warning", "info", "debug", "trace" (default: "info")',
|
|
)
|
|
parser.add_argument(
|
|
"--access_log",
|
|
type=bool,
|
|
default=False,
|
|
help="Enable or disable access log. Options: True or False (default: False)",
|
|
)
|
|
parser.add_argument(
|
|
"--reload",
|
|
type=bool,
|
|
default=False,
|
|
help="Enable or disable auto-reload. Useful for development. Options: True or False (default: False)",
|
|
)
|
|
|
|
global args
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
run_eosdash()
|
|
except Exception as ex:
|
|
error_msg = f"Failed to run EOSdash: {ex}"
|
|
logger.error(error_msg)
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|