Add location recorder

This commit is contained in:
2024-08-27 16:34:04 +02:00
parent 5a9b1fed44
commit b21b509c84
5 changed files with 90 additions and 6 deletions

3
.gitignore vendored
View File

@@ -149,4 +149,5 @@ dmypy.json
# Cython debug symbols # Cython debug symbols
cython_debug/ cython_debug/
temp_data/ temp_data/
*.db

View File

@@ -9,6 +9,7 @@ fastapi==0.112.1
fastapi-cli==0.0.5 fastapi-cli==0.0.5
fastapi-mqtt==2.2.0 fastapi-mqtt==2.2.0
gmqtt==0.6.16 gmqtt==0.6.16
greenlet==3.0.3
h11==0.14.0 h11==0.14.0
httpcore==1.0.5 httpcore==1.0.5
httptools==0.6.1 httptools==0.6.1
@@ -34,6 +35,7 @@ requests==2.32.3
rich==13.7.1 rich==13.7.1
shellingham==1.5.4 shellingham==1.5.4
sniffio==1.3.1 sniffio==1.3.1
SQLAlchemy==2.0.32
starlette==0.38.2 starlette==0.38.2
typer==0.12.4 typer==0.12.4
typing_extensions==4.12.2 typing_extensions==4.12.2

View File

@@ -1,26 +1,32 @@
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI from fastapi import FastAPI
from src.components.poo_recorder import PooRecorder from src.components.poo_recorder import PooRecorder
from src.config import Config from src.config import Config
from src.util.homeassistant import HomeAssistant from src.util.homeassistant import HomeAssistant
from src.util.location_recorder import LocationRecorder
from src.util.mqtt import MQTT from src.util.mqtt import MQTT
from src.util.notion import NotionAsync from src.util.notion import NotionAsync
from src.util.ticktick import TickTick from src.util.ticktick import TickTick
Config.init() Config.init()
location_recorder_db = str(Path(__file__).resolve().parent / ".." / "location_recorder.db")
ticktick = TickTick() ticktick = TickTick()
notion = NotionAsync(token=Config.get_env(key="NOTION_TOKEN")) notion = NotionAsync(token=Config.get_env(key="NOTION_TOKEN"))
mqtt = MQTT() mqtt = MQTT()
homeassistant = HomeAssistant(ticktick=ticktick) location_recorder = LocationRecorder(db_path=location_recorder_db)
homeassistant = HomeAssistant(ticktick=ticktick, location_recorder=location_recorder)
poo_recorder = PooRecorder(mqtt=mqtt, notion=notion, homeassistant=homeassistant) poo_recorder = PooRecorder(mqtt=mqtt, notion=notion, homeassistant=homeassistant)
@asynccontextmanager @asynccontextmanager
async def _lifespan(_app: FastAPI): # noqa: ANN202 async def _lifespan(_app: FastAPI): # noqa: ANN202
await mqtt.start() await mqtt.start()
await location_recorder.create_db_engine()
yield yield
await mqtt.stop() await mqtt.stop()

View File

@@ -1,5 +1,3 @@
from __future__ import annotations
import ast import ast
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
@@ -7,6 +5,7 @@ import httpx
from pydantic import BaseModel from pydantic import BaseModel
from src.config import Config from src.config import Config
from src.util.location_recorder import LocationData, LocationRecorder
from src.util.ticktick import TickTick from src.util.ticktick import TickTick
@@ -16,13 +15,15 @@ class HomeAssistant:
action: str action: str
content: str content: str
def __init__(self, ticktick: TickTick) -> None: def __init__(self, ticktick: TickTick, location_recorder: LocationRecorder) -> None:
self._ticktick = ticktick self._ticktick = ticktick
self._location_recorder = location_recorder
async def process_message(self, message: Message) -> dict[str, str]: async def process_message(self, message: Message) -> dict[str, str]:
if message.target == "ticktick": if message.target == "ticktick":
return await self._process_ticktick_message(message=message) return await self._process_ticktick_message(message=message)
if message.target == "location_recorder":
return await self._process_location(message=message)
return {"Status": "Unknown target"} return {"Status": "Unknown target"}
async def trigger_webhook(self, payload: dict[str, str], webhook_id: str) -> None: async def trigger_webhook(self, payload: dict[str, str], webhook_id: str) -> None:
@@ -39,6 +40,20 @@ class HomeAssistant:
return {"Status": "Unknown action"} return {"Status": "Unknown action"}
async def _process_location(self, message: Message) -> dict[str, str]:
if message.action == "record":
location: dict[str, str] = ast.literal_eval(message.content)
await self._location_recorder.insert_location_now(
people=location["person"],
location=LocationData(
latitude=float(location["latitude"]),
longitude=float(location["longitude"]),
altitude=float(location["altitude"]),
),
)
return {"Status": "Location recorded"}
return {"Status": "Unknown action"}
async def _create_shopping_list(self, content: str) -> dict[str, str]: async def _create_shopping_list(self, content: str) -> dict[str, str]:
project_id = Config.get_env("TICKTICK_SHOPPING_LIST") project_id = Config.get_env("TICKTICK_SHOPPING_LIST")
item: dict[str, str] = ast.literal_eval(content) item: dict[str, str] = ast.literal_eval(content)

View File

@@ -0,0 +1,60 @@
import datetime
from dataclasses import dataclass
from sqlalchemy import INTEGER, REAL, TEXT, insert
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class Version(Base):
__tablename__ = "version"
version_type: Mapped[str] = mapped_column(type_=TEXT, primary_key=True)
version: Mapped[int] = mapped_column(type_=INTEGER)
class Location(Base):
__tablename__ = "location"
people: Mapped[str] = mapped_column(type_=TEXT, primary_key=True)
datetime: Mapped[str] = mapped_column(type_=TEXT, primary_key=True)
latitude: Mapped[float] = mapped_column(type_=REAL)
longitude: Mapped[float] = mapped_column(type_=REAL)
altitude: Mapped[float] = mapped_column(type_=REAL)
@dataclass
class LocationData:
latitude: float
longitude: float
altitude: float
class LocationRecorder:
def __init__(self, db_path: str) -> None:
self._db_path = "sqlite+aiosqlite:///" + db_path
async def create_db_engine(self) -> None:
self._engine = create_async_engine(self._db_path)
async with self._engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def insert_location(self, people: str, datetime: str, location: LocationData) -> None:
async with self._engine.connect() as conn:
await conn.execute(
insert(Location).values(
people=people,
datetime=datetime,
latitude=location.latitude,
longitude=location.longitude,
altitude=location.altitude,
),
)
await conn.commit()
async def insert_location_now(self, people: str, location: LocationData) -> None:
now = datetime.datetime.now(tz=datetime.UTC)
now_str = now.strftime("%Y-%m-%dT%H:%M:%S%z")
await self.insert_location(people, now_str, location)