work: Автоматический режим управления скоростью
отлажен!
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import patch
|
||||
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
from app.my_dataclasses import (
|
||||
QINGPING_SAMPLE_TIMEOUT,
|
||||
QINGPING_RECOVERY_TIMEOUT,
|
||||
)
|
||||
from app.qingping.service import QingpingService
|
||||
|
||||
|
||||
class FakeClock:
|
||||
def __init__(self):
|
||||
self.now = 0.0
|
||||
|
||||
def monotonic(self) -> float:
|
||||
return self.now
|
||||
|
||||
def advance(self, seconds: float) -> None:
|
||||
self.now += seconds
|
||||
|
||||
|
||||
class FakeMqttClient:
|
||||
def __init__(self):
|
||||
self.subscriptions = []
|
||||
self.published = []
|
||||
|
||||
def subscribe(self, topic):
|
||||
self.subscriptions.append(topic)
|
||||
|
||||
def publish(
|
||||
self,
|
||||
topic,
|
||||
payload,
|
||||
):
|
||||
self.published.append(
|
||||
(
|
||||
topic,
|
||||
json.loads(payload),
|
||||
)
|
||||
)
|
||||
|
||||
return SimpleNamespace(
|
||||
rc=mqtt.MQTT_ERR_SUCCESS
|
||||
)
|
||||
|
||||
|
||||
class FakeMessage:
|
||||
def __init__(self, payload: dict):
|
||||
self.payload = json.dumps(
|
||||
payload
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def sensor_message(
|
||||
timestamp: int,
|
||||
co2: int,
|
||||
) -> FakeMessage:
|
||||
return FakeMessage(
|
||||
{
|
||||
"type": 17,
|
||||
"sensorData": [
|
||||
{
|
||||
"timestamp": {
|
||||
"value": timestamp
|
||||
},
|
||||
"temperature": {
|
||||
"value": 25.0
|
||||
},
|
||||
"humidity": {
|
||||
"value": 50.0
|
||||
},
|
||||
"co2": {
|
||||
"value": co2
|
||||
},
|
||||
"pm25": {
|
||||
"value": 1
|
||||
},
|
||||
"pm10": {
|
||||
"value": 1
|
||||
},
|
||||
"battery": {
|
||||
"value": 100
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def service_message() -> FakeMessage:
|
||||
return FakeMessage(
|
||||
{
|
||||
"type": 13,
|
||||
"wifi_info": (
|
||||
"TestWiFi,-50,1,"
|
||||
"00:00:00:00:00:00"
|
||||
),
|
||||
"sw_version": "4.8.5",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def assert_state(
|
||||
service: QingpingService,
|
||||
*,
|
||||
online: bool,
|
||||
recovery_waiting: bool,
|
||||
co2,
|
||||
):
|
||||
status = service.status()
|
||||
|
||||
assert status["online"] is online, status
|
||||
assert (
|
||||
status["recovery_waiting"]
|
||||
is recovery_waiting
|
||||
), status
|
||||
|
||||
assert status["co2"] == co2, status
|
||||
|
||||
|
||||
def test_double_recovery_cycle() -> None:
|
||||
print()
|
||||
print("TEST — DOUBLE RECOVERY CYCLE")
|
||||
|
||||
clock = FakeClock()
|
||||
|
||||
service = QingpingService(
|
||||
host="127.0.0.1",
|
||||
)
|
||||
|
||||
client = FakeMqttClient()
|
||||
|
||||
# _send_recovery() использует self._client.
|
||||
service._client = client
|
||||
|
||||
with patch(
|
||||
"app.qingping.service.time.monotonic",
|
||||
clock.monotonic,
|
||||
):
|
||||
|
||||
# --------------------------------------------------
|
||||
# START
|
||||
# --------------------------------------------------
|
||||
|
||||
print("1. MQTT connect")
|
||||
|
||||
service._on_connect(
|
||||
client,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
)
|
||||
|
||||
assert len(client.published) == 1
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=False,
|
||||
recovery_waiting=True,
|
||||
co2=None,
|
||||
)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Первый type 17.
|
||||
# --------------------------------------------------
|
||||
|
||||
print("2. First sensor sample")
|
||||
|
||||
service._on_message(
|
||||
None,
|
||||
None,
|
||||
sensor_message(
|
||||
timestamp=1000,
|
||||
co2=900,
|
||||
),
|
||||
)
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=True,
|
||||
recovery_waiting=False,
|
||||
co2=900,
|
||||
)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Первый отказ.
|
||||
# --------------------------------------------------
|
||||
|
||||
print("3. First sensor timeout")
|
||||
|
||||
clock.advance(
|
||||
QINGPING_SAMPLE_TIMEOUT + 1
|
||||
)
|
||||
|
||||
service._watchdog_tick(
|
||||
clock.monotonic()
|
||||
)
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=False,
|
||||
recovery_waiting=False,
|
||||
co2=900,
|
||||
)
|
||||
|
||||
# Recovery пока НЕ отправлялся.
|
||||
assert len(client.published) == 1
|
||||
|
||||
# --------------------------------------------------
|
||||
# Устройство снова появляется.
|
||||
# Любой пакет запускает recovery.
|
||||
# --------------------------------------------------
|
||||
|
||||
print("4. First recovery")
|
||||
|
||||
service._on_message(
|
||||
None,
|
||||
None,
|
||||
service_message(),
|
||||
)
|
||||
|
||||
assert len(client.published) == 2
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=True,
|
||||
recovery_waiting=True,
|
||||
co2=None,
|
||||
)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Новый type 17.
|
||||
# --------------------------------------------------
|
||||
|
||||
print("5. First recovery completed")
|
||||
|
||||
service._on_message(
|
||||
None,
|
||||
None,
|
||||
sensor_message(
|
||||
timestamp=1015,
|
||||
co2=1000,
|
||||
),
|
||||
)
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=True,
|
||||
recovery_waiting=False,
|
||||
co2=1000,
|
||||
)
|
||||
|
||||
# --------------------------------------------------
|
||||
# Второй отказ.
|
||||
# --------------------------------------------------
|
||||
|
||||
print("6. Second sensor timeout")
|
||||
|
||||
clock.advance(
|
||||
QINGPING_SAMPLE_TIMEOUT + 1
|
||||
)
|
||||
|
||||
service._watchdog_tick(
|
||||
clock.monotonic()
|
||||
)
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=False,
|
||||
recovery_waiting=False,
|
||||
co2=1000,
|
||||
)
|
||||
|
||||
assert len(client.published) == 2
|
||||
|
||||
# --------------------------------------------------
|
||||
# Второе восстановление.
|
||||
# --------------------------------------------------
|
||||
|
||||
print("7. Second recovery")
|
||||
|
||||
service._on_message(
|
||||
None,
|
||||
None,
|
||||
service_message(),
|
||||
)
|
||||
|
||||
assert len(client.published) == 3
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=True,
|
||||
recovery_waiting=True,
|
||||
co2=None,
|
||||
)
|
||||
|
||||
service._on_message(
|
||||
None,
|
||||
None,
|
||||
sensor_message(
|
||||
timestamp=1030,
|
||||
co2=1100,
|
||||
),
|
||||
)
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=True,
|
||||
recovery_waiting=False,
|
||||
co2=1100,
|
||||
)
|
||||
|
||||
print("8. Second recovery completed")
|
||||
|
||||
|
||||
def test_recovery_timeout_and_retry() -> None:
|
||||
print()
|
||||
print("TEST — RECOVERY TIMEOUT AND RETRY")
|
||||
|
||||
clock = FakeClock()
|
||||
|
||||
service = QingpingService(
|
||||
host="127.0.0.1",
|
||||
)
|
||||
|
||||
client = FakeMqttClient()
|
||||
|
||||
service._client = client
|
||||
|
||||
with patch(
|
||||
"app.qingping.service.time.monotonic",
|
||||
clock.monotonic,
|
||||
):
|
||||
|
||||
# Startup recovery.
|
||||
service._on_connect(
|
||||
client,
|
||||
None,
|
||||
None,
|
||||
0,
|
||||
None,
|
||||
)
|
||||
|
||||
assert len(client.published) == 1
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=False,
|
||||
recovery_waiting=True,
|
||||
co2=None,
|
||||
)
|
||||
|
||||
# Type 17 так и не пришёл.
|
||||
clock.advance(
|
||||
QINGPING_RECOVERY_TIMEOUT + 1
|
||||
)
|
||||
|
||||
service._watchdog_tick(
|
||||
clock.monotonic()
|
||||
)
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=False,
|
||||
recovery_waiting=False,
|
||||
co2=None,
|
||||
)
|
||||
|
||||
# Любой следующий пакет должен разрешить
|
||||
# новую recovery-попытку.
|
||||
service._on_message(
|
||||
None,
|
||||
None,
|
||||
service_message(),
|
||||
)
|
||||
|
||||
assert len(client.published) == 2
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=True,
|
||||
recovery_waiting=True,
|
||||
co2=None,
|
||||
)
|
||||
|
||||
# Теперь приходит type 17.
|
||||
service._on_message(
|
||||
None,
|
||||
None,
|
||||
sensor_message(
|
||||
timestamp=2000,
|
||||
co2=1200,
|
||||
),
|
||||
)
|
||||
|
||||
assert_state(
|
||||
service,
|
||||
online=True,
|
||||
recovery_waiting=False,
|
||||
co2=1200,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_double_recovery_cycle()
|
||||
test_recovery_timeout_and_retry()
|
||||
|
||||
print()
|
||||
print(
|
||||
"ALL QINGPING RECOVERY TESTS PASSED"
|
||||
)
|
||||
Reference in New Issue
Block a user