work: добавил автоматический режим
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from app.auto.co2_policy import Co2SpeedPolicy
|
||||
|
||||
class AutoController:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
schedule_service,
|
||||
qingping_service,
|
||||
tion_service,
|
||||
policy: Co2SpeedPolicy | None = None,
|
||||
interval: float = 5.0,
|
||||
):
|
||||
self._schedule = schedule_service
|
||||
self._qingping = qingping_service
|
||||
self._tion = tion_service
|
||||
self._policy = policy
|
||||
|
||||
self._interval = interval
|
||||
|
||||
self._task: asyncio.Task | None = None
|
||||
|
||||
self._state = "inactive"
|
||||
self._reason: str | None = None
|
||||
|
||||
self._target_speed: int | None = None
|
||||
self._last_error: str | None = None
|
||||
|
||||
self._auto_speed: int | None = None
|
||||
|
||||
|
||||
async def start(self) -> None:
|
||||
|
||||
if self._task is not None:
|
||||
return
|
||||
|
||||
self._task = asyncio.create_task( self._loop() )
|
||||
|
||||
|
||||
async def stop(self) -> None:
|
||||
|
||||
if self._task is None:
|
||||
return
|
||||
|
||||
self._task.cancel()
|
||||
|
||||
try:
|
||||
await self._task
|
||||
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
finally:
|
||||
self._task = None
|
||||
|
||||
|
||||
async def _loop(self) -> None:
|
||||
|
||||
while True:
|
||||
|
||||
try:
|
||||
await self._process()
|
||||
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
|
||||
except Exception as exc:
|
||||
self._last_error = str(exc)
|
||||
|
||||
await asyncio.sleep( self._interval )
|
||||
|
||||
|
||||
async def _process(self) -> None:
|
||||
|
||||
now = datetime.now()
|
||||
|
||||
resolution = self._schedule.resolve(now)
|
||||
|
||||
# ----------------------------------------------
|
||||
# Активен ручной override.
|
||||
#
|
||||
# Пока пользователь вручную управляет Tion,
|
||||
# AUTO вообще не вмешивается.
|
||||
# ----------------------------------------------
|
||||
if self._schedule.override_active:
|
||||
self._state = "suspended"
|
||||
self._reason = "manual_override"
|
||||
self._target_speed = None
|
||||
self._last_error = None
|
||||
self._auto_speed = None
|
||||
|
||||
return
|
||||
|
||||
# ----------------------------------------------
|
||||
# AUTO сейчас не активен
|
||||
# ----------------------------------------------
|
||||
|
||||
if (
|
||||
not resolution.enabled
|
||||
or not resolution.auto_active
|
||||
):
|
||||
self._state = "inactive"
|
||||
self._reason = None
|
||||
self._target_speed = None
|
||||
self._last_error = None
|
||||
self._auto_speed = None
|
||||
|
||||
return
|
||||
|
||||
fallback_speed = resolution.auto_fallback_speed
|
||||
|
||||
if fallback_speed is None:
|
||||
raise RuntimeError(
|
||||
"AUTO is active but "
|
||||
"fallback speed is missing"
|
||||
)
|
||||
# ----------------------------------------------
|
||||
# CO2 policy недоступна.
|
||||
#
|
||||
# Например, auto.yaml не загрузился.
|
||||
# AUTO работает в аварийном fallback-only режиме.
|
||||
# ----------------------------------------------
|
||||
if self._policy is None:
|
||||
self._state = "fallback"
|
||||
self._reason = "auto_policy_unavailable"
|
||||
self._auto_speed = None
|
||||
|
||||
await self._set_speed(
|
||||
fallback_speed
|
||||
)
|
||||
|
||||
self._last_error = None
|
||||
|
||||
return
|
||||
|
||||
qingping_state = self._qingping.state
|
||||
|
||||
# ----------------------------------------------
|
||||
# Qingping исправен
|
||||
# ----------------------------------------------
|
||||
|
||||
if (
|
||||
self._qingping.online
|
||||
and qingping_state.co2 is not None
|
||||
):
|
||||
target_speed = (
|
||||
self._policy.select_speed(
|
||||
co2=qingping_state.co2,
|
||||
current_speed=self._auto_speed,
|
||||
)
|
||||
)
|
||||
|
||||
self._state = "active"
|
||||
self._reason = None
|
||||
|
||||
await self._set_speed(
|
||||
target_speed
|
||||
)
|
||||
|
||||
self._auto_speed = target_speed
|
||||
self._last_error = None
|
||||
|
||||
return
|
||||
|
||||
# ----------------------------------------------
|
||||
# AUTO не может работать.
|
||||
# Переходим на fallback.
|
||||
# ----------------------------------------------
|
||||
|
||||
if not self._qingping.online:
|
||||
reason = "qingping_offline"
|
||||
|
||||
else:
|
||||
reason = "co2_missing"
|
||||
|
||||
self._state = "fallback"
|
||||
self._reason = reason
|
||||
|
||||
self._auto_speed = None
|
||||
|
||||
await self._set_speed(fallback_speed)
|
||||
|
||||
self._last_error = None
|
||||
|
||||
|
||||
async def _set_speed(self, speed: int) -> None:
|
||||
|
||||
if self._target_speed == speed:
|
||||
return
|
||||
|
||||
await self._tion.execute(
|
||||
lambda controller:
|
||||
controller.set_speed(speed)
|
||||
)
|
||||
|
||||
self._target_speed = speed
|
||||
|
||||
|
||||
def status(self) -> dict:
|
||||
return {
|
||||
"state": self._state,
|
||||
"reason": self._reason,
|
||||
"target_speed": self._target_speed,
|
||||
"auto_speed": self._auto_speed,
|
||||
"last_error": self._last_error,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user