Add location recorder
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -149,4 +149,5 @@ dmypy.json
|
|||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
temp_data/
|
temp_data/
|
||||||
|
*.db
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
60
src/util/location_recorder.py
Normal file
60
src/util/location_recorder.py
Normal 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)
|
||||||
Reference in New Issue
Block a user