2021-07-11 21:09:05 +02:00
|
|
|
import asyncio
|
2022-09-11 10:33:12 +02:00
|
|
|
from typing import Dict, Optional
|
2021-07-11 21:09:05 +02:00
|
|
|
|
2026-07-11 12:22:31 +02:00
|
|
|
from fastapi import HTTPException, status
|
2025-03-26 20:22:19 +01:00
|
|
|
from loguru import logger
|
2021-06-12 15:07:35 +02:00
|
|
|
|
2025-02-16 20:35:21 +01:00
|
|
|
from app.api.config import config
|
2025-03-26 20:22:19 +01:00
|
|
|
from app.api.error_report.report import Frame
|
2026-07-12 17:11:04 +02:00
|
|
|
from app.api.startup_status import api_startup_status
|
2026-07-11 11:29:55 +02:00
|
|
|
from app.api.utils import Event, broadcast_msg
|
2025-03-26 20:22:19 +01:00
|
|
|
from app.external.result_type.src.result.result import Err, Ok
|
2026-07-12 17:11:04 +02:00
|
|
|
from app.system.health import build_health_info
|
2022-10-03 20:22:00 +02:00
|
|
|
from app.system.models import (
|
2022-08-24 20:56:40 +02:00
|
|
|
APIPlatform,
|
|
|
|
|
ConnectionInfo,
|
|
|
|
|
LoginInput,
|
|
|
|
|
RawDebugLogData,
|
2024-02-20 19:56:11 +01:00
|
|
|
SystemHealthInfo,
|
2022-08-24 20:56:40 +02:00
|
|
|
SystemInfo,
|
|
|
|
|
)
|
2022-03-20 17:20:56 +01:00
|
|
|
|
2025-02-16 20:35:21 +01:00
|
|
|
PLATFORM = config("BAPI_PLATFORM", default=APIPlatform.RASPIBLITZ)
|
2021-12-25 16:53:42 +01:00
|
|
|
if PLATFORM == APIPlatform.RASPIBLITZ:
|
2025-03-26 20:22:19 +01:00
|
|
|
logger.info("using RaspiBlitz system implementation")
|
2022-10-03 20:22:00 +02:00
|
|
|
from app.system.impl.raspiblitz import RaspiBlitzSystem as System
|
2021-12-25 16:53:42 +01:00
|
|
|
elif PLATFORM == APIPlatform.NATIVE_PYTHON:
|
2025-03-26 20:22:19 +01:00
|
|
|
logger.info("using native python system implementation")
|
2022-10-03 20:22:00 +02:00
|
|
|
from app.system.impl.native_python import NativePythonSystem as System
|
2025-02-16 20:35:21 +01:00
|
|
|
else:
|
|
|
|
|
raise RuntimeError(
|
|
|
|
|
f"Unsupported platform '{PLATFORM}'. Options: {APIPlatform.values_as_list()}"
|
|
|
|
|
)
|
2022-05-18 18:20:13 +02:00
|
|
|
|
2022-05-09 19:32:47 +02:00
|
|
|
|
2022-09-11 18:32:39 +02:00
|
|
|
system = System()
|
2022-09-11 10:33:12 +02:00
|
|
|
|
2022-10-03 20:22:00 +02:00
|
|
|
if system is None:
|
2022-09-11 10:33:12 +02:00
|
|
|
raise RuntimeError(f"Unknown platform {PLATFORM}")
|
|
|
|
|
|
2022-10-03 20:22:00 +02:00
|
|
|
HW_INFO_YIELD_TIME = system.get_hardware_info_yield_time()
|
2022-10-02 14:18:29 +02:00
|
|
|
|
2022-09-11 10:33:12 +02:00
|
|
|
|
|
|
|
|
async def change_password(type: Optional[str], old_password: str, new_password: str):
|
|
|
|
|
try:
|
|
|
|
|
return await system.change_password(type, old_password, new_password)
|
2023-05-17 16:02:28 +02:00
|
|
|
except HTTPException:
|
2022-09-11 10:33:12 +02:00
|
|
|
raise
|
|
|
|
|
except NotImplementedError as r:
|
|
|
|
|
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0])
|
2022-05-18 18:20:13 +02:00
|
|
|
|
2022-03-23 22:14:41 +01:00
|
|
|
|
2021-09-05 20:23:59 +02:00
|
|
|
async def get_system_info() -> SystemInfo:
|
2021-11-24 11:12:10 +01:00
|
|
|
try:
|
2022-09-11 10:33:12 +02:00
|
|
|
return await system.get_system_info()
|
2023-05-17 16:02:28 +02:00
|
|
|
except HTTPException:
|
2021-11-24 11:12:10 +01:00
|
|
|
raise
|
|
|
|
|
except NotImplementedError as r:
|
|
|
|
|
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0])
|
2021-09-05 20:23:59 +02:00
|
|
|
|
2022-05-18 18:20:13 +02:00
|
|
|
|
2024-02-20 19:56:11 +01:00
|
|
|
async def system_health(verbose: bool) -> SystemHealthInfo:
|
2026-07-12 17:11:04 +02:00
|
|
|
# Readiness is platform independent: read the shared startup state directly.
|
|
|
|
|
return build_health_info(api_startup_status, verbose)
|
2024-02-20 19:56:11 +01:00
|
|
|
|
|
|
|
|
|
2021-12-15 21:11:46 +01:00
|
|
|
async def get_hardware_info() -> map:
|
2022-09-11 10:33:12 +02:00
|
|
|
try:
|
2022-10-03 20:22:00 +02:00
|
|
|
return await system.get_hardware_info()
|
2023-05-17 16:02:28 +02:00
|
|
|
except HTTPException:
|
2022-09-11 10:33:12 +02:00
|
|
|
raise
|
|
|
|
|
except NotImplementedError as r:
|
|
|
|
|
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0])
|
2021-12-15 21:11:46 +01:00
|
|
|
|
2022-05-18 18:20:13 +02:00
|
|
|
|
2022-05-12 19:49:01 +02:00
|
|
|
async def get_connection_info() -> ConnectionInfo:
|
2025-03-26 20:22:19 +01:00
|
|
|
result = await system.get_connection_info()
|
|
|
|
|
match result:
|
|
|
|
|
case Ok(data):
|
|
|
|
|
return data
|
|
|
|
|
case Err(report):
|
|
|
|
|
e = report.last_error
|
|
|
|
|
report.attach_frame(Frame("unable to get connection info"))
|
|
|
|
|
if e is None:
|
|
|
|
|
logger.error(report.format_verbose())
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
|
detail=report.format_verbose(),
|
|
|
|
|
)
|
|
|
|
|
if isinstance(e, HTTPException):
|
|
|
|
|
raise e
|
|
|
|
|
if isinstance(e, NotImplementedError):
|
|
|
|
|
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=e)
|
|
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
|
detail="unknown error getting connection info",
|
|
|
|
|
)
|
2021-12-15 21:11:46 +01:00
|
|
|
|
2022-05-18 18:20:13 +02:00
|
|
|
|
2022-03-24 20:17:56 +01:00
|
|
|
async def shutdown(reboot: bool) -> bool:
|
|
|
|
|
if reboot:
|
2026-07-11 11:29:55 +02:00
|
|
|
await broadcast_msg(Event.SYSTEM_REBOOT_NOTICE, {"reboot": True})
|
2022-03-24 20:17:56 +01:00
|
|
|
else:
|
2026-07-11 11:29:55 +02:00
|
|
|
await broadcast_msg(Event.SYSTEM_SHUTDOWN_NOTICE, {"shutdown": True})
|
2022-03-24 20:17:56 +01:00
|
|
|
|
2022-09-11 10:33:12 +02:00
|
|
|
try:
|
|
|
|
|
return await system.shutdown(reboot=reboot)
|
2023-05-17 16:02:28 +02:00
|
|
|
except HTTPException:
|
2022-09-11 10:33:12 +02:00
|
|
|
raise
|
|
|
|
|
except NotImplementedError as r:
|
|
|
|
|
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0])
|
2022-03-24 20:17:56 +01:00
|
|
|
|
|
|
|
|
|
2021-09-20 11:22:50 +02:00
|
|
|
async def get_debug_logs_raw() -> RawDebugLogData:
|
2022-09-11 10:33:12 +02:00
|
|
|
try:
|
|
|
|
|
return await system.get_debug_logs_raw()
|
2023-05-17 16:02:28 +02:00
|
|
|
except HTTPException:
|
2022-09-11 10:33:12 +02:00
|
|
|
raise
|
|
|
|
|
except NotImplementedError as r:
|
|
|
|
|
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=r.args[0])
|
2021-09-20 11:22:50 +02:00
|
|
|
|
|
|
|
|
|
2021-07-11 21:09:05 +02:00
|
|
|
async def _handle_gather_hardware_info():
|
|
|
|
|
last_info = {}
|
|
|
|
|
while True:
|
2026-07-08 14:45:21 +02:00
|
|
|
try:
|
|
|
|
|
info = await get_hardware_info()
|
|
|
|
|
if last_info != info:
|
2026-07-11 11:29:55 +02:00
|
|
|
await broadcast_msg(Event.HARDWARE_INFO, info)
|
2026-07-08 14:45:21 +02:00
|
|
|
last_info = info
|
|
|
|
|
except Exception as e:
|
|
|
|
|
# never let a single failure kill the gatherer task, otherwise
|
|
|
|
|
# hardware SSE updates would stop until the API is restarted
|
|
|
|
|
logger.error(f"Error gathering hardware info: {e}")
|
2021-07-11 21:09:05 +02:00
|
|
|
|
|
|
|
|
await asyncio.sleep(HW_INFO_YIELD_TIME)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def register_hardware_info_gatherer():
|
2026-07-03 22:34:44 +02:00
|
|
|
asyncio.create_task(_handle_gather_hardware_info())
|
2022-08-24 20:56:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
async def login(i: LoginInput) -> Dict[str, str]:
|
2025-03-26 20:22:19 +01:00
|
|
|
result = await system.login(i)
|
|
|
|
|
match result:
|
|
|
|
|
case Ok(data):
|
|
|
|
|
return data
|
|
|
|
|
case Err(report):
|
|
|
|
|
e = report.last_error
|
|
|
|
|
report.attach_frame(Frame("error during login"))
|
|
|
|
|
if e is None:
|
|
|
|
|
logger.error("got report without error")
|
|
|
|
|
logger.error(report.format_verbose())
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
|
detail=report.format_verbose(),
|
|
|
|
|
)
|
|
|
|
|
if (
|
|
|
|
|
isinstance(e, HTTPException)
|
|
|
|
|
and e.status_code == status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
):
|
|
|
|
|
logger.info("unauthorized login attempt")
|
|
|
|
|
raise e
|
|
|
|
|
if (
|
|
|
|
|
isinstance(e, HTTPException)
|
|
|
|
|
and e.status_code != status.HTTP_401_UNAUTHORIZED
|
|
|
|
|
):
|
|
|
|
|
logger.error(report.format_verbose())
|
|
|
|
|
raise e
|
|
|
|
|
if isinstance(e, NotImplementedError):
|
|
|
|
|
raise HTTPException(status.HTTP_501_NOT_IMPLEMENTED, detail=e)
|
|
|
|
|
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
|
|
|
detail="unknown error",
|
|
|
|
|
)
|