From 5defaba45fa47b8afc46b8b558a5c12af8e6f19b Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 16 Jan 2025 19:14:34 +0100 Subject: [PATCH] Background services running properly; New Bedscale Entity implemented --- src/bridges/bedscale/__init__.py | 1 + src/bridges/bedscale/bedscale_entity.py | 20 +++- .../bett.py => bridges/bedscale/old_bett.py} | 2 +- src/endpoints/bettwaage.py | 98 ++++++++++++------- src/main.py | 36 +++++-- 5 files changed, 106 insertions(+), 51 deletions(-) rename src/{endpoints/handlers/bett.py => bridges/bedscale/old_bett.py} (99%) diff --git a/src/bridges/bedscale/__init__.py b/src/bridges/bedscale/__init__.py index e69de29..e455567 100644 --- a/src/bridges/bedscale/__init__.py +++ b/src/bridges/bedscale/__init__.py @@ -0,0 +1 @@ +from .bedscale_entity import BedscaleEntity diff --git a/src/bridges/bedscale/bedscale_entity.py b/src/bridges/bedscale/bedscale_entity.py index 69b8f3d..fd7ba4e 100644 --- a/src/bridges/bedscale/bedscale_entity.py +++ b/src/bridges/bedscale/bedscale_entity.py @@ -17,20 +17,30 @@ class BedscaleWeightResult: class BedscaleEntity(Entity): - def __init__(self, *, ip_address: str, id, name, room): + + def __init__(self, *, ip_address: str, id: str, name: str, room: str): super().__init__(id=id, name=name, room=room, device_type="bedscale") self._ip_address = ip_address async def poll_weights(self) -> BedscaleWeightResult: + loop = asyncio.get_event_loop() + + tr_request = loop.run_in_executor(None, self.__poll_scale__, "tr") + tl_request = loop.run_in_executor(None, self.__poll_scale__, "tl") + br_request = loop.run_in_executor(None, self.__poll_scale__, "br") + bl_request = loop.run_in_executor(None, self.__poll_scale__, "bl") + results = BedscaleWeightResult( - tr=await asyncio.run_in_executor(None, self.__poll_scale__, "tr"), - tl=await asyncio.run_in_executor(None, self.__poll_scale__, "tl"), - br=await asyncio.run_in_executor(None, self.__poll_scale__, "br"), - bl=await asyncio.run_in_executor(None, self.__poll_scale__, "bl"), + tr=await tr_request, + tl=await tl_request, + br=await br_request, + bl=await bl_request, ) # TODO: Sanity checks + # TODO: Keep track of empty-bed weight + return results def __poll_scale__(self, leg: str) -> float | None: diff --git a/src/endpoints/handlers/bett.py b/src/bridges/bedscale/old_bett.py similarity index 99% rename from src/endpoints/handlers/bett.py rename to src/bridges/bedscale/old_bett.py index c01d84b..f369ccd 100644 --- a/src/endpoints/handlers/bett.py +++ b/src/bridges/bedscale/old_bett.py @@ -5,7 +5,7 @@ import os from statistics import median from typing import Optional import requests as r -from ..hue import hue +from ...endpoints.hue import hue import logging file_path: str = "bettwaage.csv" diff --git a/src/endpoints/bettwaage.py b/src/endpoints/bettwaage.py index 81032bc..f431ae9 100644 --- a/src/endpoints/bettwaage.py +++ b/src/endpoints/bettwaage.py @@ -4,53 +4,79 @@ from fastapi import APIRouter import os import csv -from .handlers.bett import file_path, local_history, log_bed_weights + +from bridges.bedscale import BedscaleEntity router = APIRouter() +bedscale = BedscaleEntity( + ip_address="http://192.168.178.110:80", + id="bedscale", + name="Bettwaage", + room="Max Zimmer", +) + +history = [] + +measure_delay_secs = 1 +history_length_secs = 60 * 10 -@router.get("/file", tags=["file"]) -async def get_file(): - with open(file_path, "r", encoding="UTF-8") as fp: - return HTMLResponse("\n".join(fp.readlines())) +async def bedscale_service(): + global history + global bedscale + history_max_num = int(history_length_secs / measure_delay_secs) + while True: + r = await bedscale.poll_weights() + history.append(r) + if len(history) > history_max_num: + history = history[-history_max_num:] -@router.get("/history") -async def get_history(count: int = None) -> list[dict]: - - points = [] - with open(file_path, "r", encoding="UTF-8") as fp: - reader = csv.DictReader(fp, delimiter=";") - for row in reader: - if not row: - continue - - points.append( - { - "timestamp": row["timestamp"], - "total": float(row["total"]), - "tl": float(row["tl"]), - "tr": float(row["tr"]), - "bl": float(row["bl"]), - "br": float(row["br"]), - } - ) - - if count: - return points[-count] - else: - return points + await asyncio.sleep(1) @router.get("/latest") async def get_latest(): - if len(local_history) == 0: + if len(history) == 0: return HTMLResponse(status_code=200, content="No data given yet") - return JSONResponse(local_history[-1]) + return history[-1] -@router.delete("/delete", tags=["file"]) -async def delete_file(): - os.remove(file_path) - return "Deleted file" +# @router.get("/file", tags=["file"]) +# async def get_file(): +# with open(file_path, "r", encoding="UTF-8") as fp: +# return HTMLResponse("\n".join(fp.readlines())) + + +# @router.get("/history") +# async def get_history(count: int = None) -> list[dict]: + +# points = [] +# with open(file_path, "r", encoding="UTF-8") as fp: +# reader = csv.DictReader(fp, delimiter=";") +# for row in reader: +# if not row: +# continue + +# points.append( +# { +# "timestamp": row["timestamp"], +# "total": float(row["total"]), +# "tl": float(row["tl"]), +# "tr": float(row["tr"]), +# "bl": float(row["bl"]), +# "br": float(row["br"]), +# } +# ) + +# if count: +# return points[-count] +# else: +# return points + + +# @router.delete("/delete", tags=["file"]) +# async def delete_file(): +# os.remove(file_path) +# return "Deleted file" diff --git a/src/main.py b/src/main.py index b9297ea..adfd984 100644 --- a/src/main.py +++ b/src/main.py @@ -1,24 +1,42 @@ import asyncio +from contextlib import asynccontextmanager from fastapi import FastAPI import uvicorn -from endpoints.handlers.bett import log_bed_weights from endpoints.hue import router as hue_router -from endpoints.bettwaage import router as bettwaage_router +from endpoints.bettwaage import bedscale_service, router as bettwaage_router from endpoints.handlers.fritz import track_network_devices -app = FastAPI() +# Background task references +background_tasks = [] -### Background services -loop = asyncio.new_event_loop() -loop.create_task(track_network_devices(), name="Fritz!Box Connection Tracker") -loop.create_task(log_bed_weights(), name="Polling bed-scale") + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Start background services.""" + fritz_task = asyncio.create_task(track_network_devices(), name="Fritz!Box Tracker") + bedscale_task = asyncio.create_task(bedscale_service(), name="Polling bed-scale") + + # Store references to the tasks + background_tasks.extend([fritz_task, bedscale_task]) + + yield + + """Stop background services.""" + for task in background_tasks: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass # Expected when cancelling tasks + + +app = FastAPI(lifespan=lifespan) # API Routes app.include_router(hue_router, prefix="/hue", tags=["hue"]) app.include_router(bettwaage_router, prefix="/bettwaage", tags=["bett"]) + if __name__ == "__main__": # Run API server uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) - - # TODO: Close background services properly