work: Реализован API
This commit is contained in:
+258
@@ -0,0 +1,258 @@
|
|||||||
|
import logging
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException, Path
|
||||||
|
|
||||||
|
from app.my_dataclasses import (
|
||||||
|
TION_MAC,
|
||||||
|
MIN_FAN_SPEED,
|
||||||
|
MAX_FAN_SPEED,
|
||||||
|
MIN_TARGET_TEMP,
|
||||||
|
MAX_TARGET_TEMP,
|
||||||
|
AIR_MODE_OUTSIDE,
|
||||||
|
AIR_MODE_RECIRCULATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.tion import (
|
||||||
|
TionController,
|
||||||
|
TionService,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def configure_logging() -> None:
|
||||||
|
noisy_loggers = (
|
||||||
|
"asyncio",
|
||||||
|
"bleak",
|
||||||
|
"bleak.backends",
|
||||||
|
"bleak.backends.winrt",
|
||||||
|
"bleak.backends.winrt.client",
|
||||||
|
"tion_btle",
|
||||||
|
"tion_btle.tion",
|
||||||
|
"tion_btle.s4",
|
||||||
|
"tion_btle.light_family",
|
||||||
|
)
|
||||||
|
|
||||||
|
for logger_name in noisy_loggers:
|
||||||
|
logging.getLogger(logger_name).setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
configure_logging()
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Tion
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
controller = TionController(TION_MAC)
|
||||||
|
|
||||||
|
service = TionService(
|
||||||
|
controller,
|
||||||
|
poll_interval=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Application lifecycle
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
await service.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
await service.stop()
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# FastAPI
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Tion Controller",
|
||||||
|
version="0.1.0",
|
||||||
|
lifespan=lifespan,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
def get_status() -> dict:
|
||||||
|
"""
|
||||||
|
Получить текущее состояние сервиса.
|
||||||
|
|
||||||
|
Bluetooth-запрос здесь не выполняется.
|
||||||
|
"""
|
||||||
|
|
||||||
|
state = service.state
|
||||||
|
|
||||||
|
return {
|
||||||
|
"online": service.online,
|
||||||
|
"running": service.running,
|
||||||
|
|
||||||
|
"last_seen": (
|
||||||
|
service.last_seen.isoformat()
|
||||||
|
if service.last_seen is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
|
||||||
|
"last_error": service.last_error,
|
||||||
|
|
||||||
|
"tion": (
|
||||||
|
state.to_dict()
|
||||||
|
if state is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def execute_command(operation) -> dict:
|
||||||
|
"""
|
||||||
|
Выполнить команду Tion и вернуть обновлённый status.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
await service.execute(operation)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=503,
|
||||||
|
detail={
|
||||||
|
"message": "Tion unavailable",
|
||||||
|
"error": f"{type(exc).__name__}: {exc}",
|
||||||
|
},
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
return get_status()
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Status
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.get("/api/status")
|
||||||
|
async def status():
|
||||||
|
return get_status()
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Power
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.post("/api/tion/power/{value}")
|
||||||
|
async def set_power(
|
||||||
|
value: Literal["on", "off"],
|
||||||
|
):
|
||||||
|
if value == "on":
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.power_on()
|
||||||
|
)
|
||||||
|
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.power_off()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Fan speed
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.post("/api/tion/speed/{speed}")
|
||||||
|
async def set_speed(
|
||||||
|
speed: int = Path(
|
||||||
|
ge=MIN_FAN_SPEED,
|
||||||
|
le=MAX_FAN_SPEED,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.set_speed(speed)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Heater
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.post("/api/tion/heater/{value}")
|
||||||
|
async def set_heater(
|
||||||
|
value: Literal["on", "off"],
|
||||||
|
):
|
||||||
|
if value == "on":
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.heater_on()
|
||||||
|
)
|
||||||
|
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.heater_off()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Target temperature
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.post("/api/tion/temperature/{temperature}")
|
||||||
|
async def set_temperature(
|
||||||
|
temperature: int = Path(
|
||||||
|
ge=MIN_TARGET_TEMP,
|
||||||
|
le=MAX_TARGET_TEMP,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.set_target_temperature(
|
||||||
|
temperature
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Air mode
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.post("/api/tion/mode/{mode}")
|
||||||
|
async def set_air_mode(
|
||||||
|
mode: Literal[
|
||||||
|
AIR_MODE_OUTSIDE,
|
||||||
|
AIR_MODE_RECIRCULATION,
|
||||||
|
],
|
||||||
|
):
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.set_air_mode(mode)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Sound
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.post("/api/tion/sound/{value}")
|
||||||
|
async def set_sound(
|
||||||
|
value: Literal["on", "off"],
|
||||||
|
):
|
||||||
|
if value == "on":
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.sound_on()
|
||||||
|
)
|
||||||
|
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.sound_off()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Light
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@app.post("/api/tion/light/{value}")
|
||||||
|
async def set_light(
|
||||||
|
value: Literal["on", "off"],
|
||||||
|
):
|
||||||
|
if value == "on":
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.light_on()
|
||||||
|
)
|
||||||
|
|
||||||
|
return await execute_command(
|
||||||
|
lambda tion: tion.light_off()
|
||||||
|
)
|
||||||
+3
-1
@@ -1 +1,3 @@
|
|||||||
tion-btle==3.3.6
|
tion-btle==3.3.6
|
||||||
|
fastapi
|
||||||
|
uvicorn[standard]
|
||||||
Reference in New Issue
Block a user