138 lines
4.3 KiB
Python
138 lines
4.3 KiB
Python
"""kitchen-display-agent entrypoint."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
import signal
|
|
import socket
|
|
import sys
|
|
import threading
|
|
|
|
import paho.mqtt.client as mqtt
|
|
|
|
from .mqtt_discovery import Discovery
|
|
from .session import launch_pantry_kiosk
|
|
|
|
CONFIG_PATH = os.environ.get("KITCHEN_DISPLAY_AGENT_CONFIG", "/etc/kitchen-display-agent/config.env")
|
|
|
|
CONFIG_KEYS = (
|
|
"MQTT_BROKER_HOST",
|
|
"MQTT_BROKER_PORT",
|
|
"MQTT_USERNAME",
|
|
"MQTT_PASSWORD",
|
|
"PANTRY_WEB_URL",
|
|
"PANTRY_VISION_URL",
|
|
"PANTRY_VISION_TOKEN",
|
|
"KIOSK_USERNAME",
|
|
"KITCHEN_DISPLAY_NAME",
|
|
)
|
|
|
|
log = logging.getLogger("kitchen-display-agent")
|
|
|
|
|
|
def load_config(path: str = CONFIG_PATH) -> dict[str, str]:
|
|
values: dict[str, str] = {}
|
|
try:
|
|
with open(path, encoding="utf-8") as handle:
|
|
for line in handle:
|
|
line = line.strip()
|
|
if not line or line.startswith("#") or "=" not in line:
|
|
continue
|
|
key, _, value = line.partition("=")
|
|
values[key.strip()] = value.strip().strip('"').strip("'")
|
|
except OSError as exc:
|
|
log.warning("could not read %s (%s); falling back to the environment", path, exc)
|
|
|
|
for key in CONFIG_KEYS:
|
|
if key in os.environ:
|
|
values[key] = os.environ[key]
|
|
|
|
return values
|
|
|
|
|
|
def make_client(client_id: str) -> mqtt.Client:
|
|
callback_api = getattr(mqtt, "CallbackAPIVersion", None)
|
|
if callback_api is not None:
|
|
return mqtt.Client(callback_api.VERSION1, client_id=client_id)
|
|
return mqtt.Client(client_id=client_id)
|
|
|
|
|
|
def main() -> int:
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
stream=sys.stdout,
|
|
)
|
|
|
|
config = load_config()
|
|
hostname = socket.gethostname()
|
|
node_id = "".join(c if c.isalnum() else "_" for c in hostname).strip("_") or "kitchendisplay"
|
|
friendly_name = config.get("KITCHEN_DISPLAY_NAME") or f"Kitchen display ({hostname})"
|
|
|
|
broker_host = config.get("MQTT_BROKER_HOST", "")
|
|
broker_port = int(config.get("MQTT_BROKER_PORT") or 1883)
|
|
|
|
client = make_client(f"kitchen-display-agent-{node_id}")
|
|
if config.get("MQTT_USERNAME"):
|
|
client.username_pw_set(config["MQTT_USERNAME"], config.get("MQTT_PASSWORD") or None)
|
|
|
|
discovery = Discovery(client, node_id, friendly_name)
|
|
|
|
def on_show(fragment: str) -> None:
|
|
launch_pantry_kiosk(fragment)
|
|
|
|
def on_connect(_client, _userdata, _flags, rc):
|
|
if rc != 0:
|
|
log.error("MQTT connection refused (rc=%s)", rc)
|
|
return
|
|
log.info("connected to MQTT broker %s:%s", broker_host, broker_port)
|
|
discovery.register_screens(on_show)
|
|
discovery.subscribe_all()
|
|
discovery.publish_available(True)
|
|
|
|
def on_disconnect(_client, _userdata, rc):
|
|
log.warning("disconnected from MQTT broker (rc=%s); paho will retry", rc)
|
|
|
|
def on_message(_client, _userdata, message):
|
|
discovery.dispatch(message.topic, message.payload.decode("utf-8", "replace"))
|
|
|
|
client.on_connect = on_connect
|
|
client.on_disconnect = on_disconnect
|
|
client.on_message = on_message
|
|
client.will_set(discovery.availability_topic, "offline", qos=1, retain=True)
|
|
|
|
stop_event = threading.Event()
|
|
|
|
def handle_signal(_signum, _frame):
|
|
stop_event.set()
|
|
|
|
signal.signal(signal.SIGTERM, handle_signal)
|
|
signal.signal(signal.SIGINT, handle_signal)
|
|
|
|
if not broker_host:
|
|
log.error("MQTT_BROKER_HOST is not set in %s — running without HA control", CONFIG_PATH)
|
|
else:
|
|
# connect_async + loop_start, never a blocking connect(): same "reactive path
|
|
# never depends on a remote service" rule as every other host's agent — the
|
|
# kitchen display's local scan/inventory/recipes flow must work with the
|
|
# container host powered off, only the HA "Show X" buttons need it.
|
|
client.connect_async(broker_host, broker_port, keepalive=60)
|
|
client.loop_start()
|
|
|
|
log.info("kitchen-display-agent %s started (node_id=%s)", node_id, node_id)
|
|
try:
|
|
stop_event.wait()
|
|
finally:
|
|
log.info("shutting down")
|
|
if broker_host:
|
|
discovery.publish_available(False)
|
|
client.loop_stop()
|
|
client.disconnect()
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|