repo: archive legacy pico dashboard
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
from .protocol import (
|
||||
make_status_request,
|
||||
make_set_relay,
|
||||
is_status_response,
|
||||
is_relay_response,
|
||||
is_error,
|
||||
)
|
||||
|
||||
|
||||
class CommunicationService:
|
||||
def __init__(
|
||||
self,
|
||||
uart_client,
|
||||
app_state,
|
||||
http_client=None,
|
||||
clock=None,
|
||||
timeout_seconds=5,
|
||||
):
|
||||
self.uart_client = uart_client
|
||||
self.http_client = http_client
|
||||
self.app_state = app_state
|
||||
self.clock = clock
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.last_messages = []
|
||||
self.use_http_fallback = False
|
||||
self.last_status_received_at = None
|
||||
|
||||
def now(self):
|
||||
if self.clock:
|
||||
return self.clock()
|
||||
|
||||
try:
|
||||
import time
|
||||
return time.time()
|
||||
except ImportError:
|
||||
return 0
|
||||
|
||||
def request_status(self):
|
||||
if self.use_http_fallback and self.http_client:
|
||||
message = self.http_client.get_status()
|
||||
self.handle_message(message)
|
||||
return message
|
||||
|
||||
self.uart_client.send_message(make_status_request())
|
||||
return None
|
||||
|
||||
def set_relay(self, relay, enabled):
|
||||
if self.use_http_fallback and self.http_client:
|
||||
message = self.http_client.set_relay(relay, enabled)
|
||||
self.handle_message(message)
|
||||
return message
|
||||
|
||||
self.uart_client.send_message(make_set_relay(relay, enabled))
|
||||
return None
|
||||
|
||||
def poll(self):
|
||||
messages = self.uart_client.read_available_messages()
|
||||
self.last_messages = messages
|
||||
|
||||
for message in messages:
|
||||
self.handle_message(message)
|
||||
|
||||
self.update_connection_state()
|
||||
|
||||
return messages
|
||||
|
||||
def update_connection_state(self):
|
||||
if self.last_status_received_at is None:
|
||||
self.app_state.network["uart_connected"] = False
|
||||
return
|
||||
|
||||
age = self.now() - self.last_status_received_at
|
||||
self.app_state.network["uart_connected"] = age <= self.timeout_seconds
|
||||
|
||||
def should_use_http_fallback(self):
|
||||
return (
|
||||
self.http_client is not None
|
||||
and not self.app_state.network.get("uart_connected", False)
|
||||
)
|
||||
|
||||
def auto_select_transport(self):
|
||||
self.use_http_fallback = self.should_use_http_fallback()
|
||||
return self.use_http_fallback
|
||||
|
||||
def enable_http_fallback(self):
|
||||
self.use_http_fallback = True
|
||||
|
||||
def disable_http_fallback(self):
|
||||
self.use_http_fallback = False
|
||||
|
||||
def handle_message(self, message):
|
||||
if is_status_response(message):
|
||||
self.last_status_received_at = self.now()
|
||||
self.app_state.update_from_status(message)
|
||||
self.app_state.network["uart_connected"] = True
|
||||
return
|
||||
|
||||
if is_relay_response(message):
|
||||
self.app_state.update_from_relay_response(message)
|
||||
return
|
||||
|
||||
if is_error(message):
|
||||
self.app_state.set_error(message)
|
||||
return
|
||||
|
||||
self.app_state.set_error({
|
||||
"type": "error",
|
||||
"message": "unknown_message_type",
|
||||
"raw": message,
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
class HttpClient:
|
||||
def __init__(self, requests_module, base_url="http://192.168.4.1"):
|
||||
self.requests = requests_module
|
||||
self.base_url = base_url.rstrip("/")
|
||||
if not self.base_url.endswith("/api/v1"):
|
||||
self.base_url = f"{self.base_url}/api/v1"
|
||||
|
||||
def get_status(self):
|
||||
response = self.requests.get(f"{self.base_url}/status")
|
||||
return response.json()
|
||||
|
||||
def set_relay(self, relay, enabled):
|
||||
response = self.requests.post(
|
||||
f"{self.base_url}/relay/set",
|
||||
json={"id": relay, "state": enabled},
|
||||
)
|
||||
return response.json()
|
||||
@@ -0,0 +1,35 @@
|
||||
STATUS_REQUEST = "status_request"
|
||||
STATUS_RESPONSE = "status_response"
|
||||
SET_RELAY = "set_relay"
|
||||
RELAY_RESPONSE = "relay_response"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
VALID_RELAYS = {"starlink", "fridge"}
|
||||
|
||||
|
||||
def make_status_request():
|
||||
return {"type": STATUS_REQUEST}
|
||||
|
||||
|
||||
def make_set_relay(relay, enabled):
|
||||
if relay not in VALID_RELAYS:
|
||||
raise ValueError(f"Invalid relay: {relay}")
|
||||
|
||||
return {
|
||||
"type": SET_RELAY,
|
||||
"relay": relay,
|
||||
"enabled": bool(enabled),
|
||||
}
|
||||
|
||||
|
||||
def is_status_response(message):
|
||||
return message.get("type") == STATUS_RESPONSE
|
||||
|
||||
|
||||
def is_relay_response(message):
|
||||
return message.get("type") == RELAY_RESPONSE
|
||||
|
||||
|
||||
def is_error(message):
|
||||
return message.get("type") == ERROR
|
||||
@@ -0,0 +1,40 @@
|
||||
import json
|
||||
|
||||
|
||||
class UartClient:
|
||||
def __init__(self, uart):
|
||||
self.uart = uart
|
||||
self.buffer = ""
|
||||
|
||||
def send_message(self, message):
|
||||
payload = json.dumps(message, separators=(",", ":")) + "\n"
|
||||
self.uart.write(payload.encode("utf-8"))
|
||||
|
||||
def read_available_messages(self):
|
||||
messages = []
|
||||
|
||||
while self.uart.any():
|
||||
chunk = self.uart.read()
|
||||
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
self.buffer += chunk.decode("utf-8")
|
||||
|
||||
while "\n" in self.buffer:
|
||||
line, self.buffer = self.buffer.split("\n", 1)
|
||||
line = line.strip()
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
try:
|
||||
messages.append(json.loads(line))
|
||||
except ValueError:
|
||||
messages.append({
|
||||
"type": "error",
|
||||
"message": "invalid_json",
|
||||
"raw": line,
|
||||
})
|
||||
|
||||
return messages
|
||||
Reference in New Issue
Block a user