|
| 1 | +# SPDX-FileCopyrightText: 2026-present Inria |
| 2 | +# SPDX-License-Identifier: BSD-3-Clause |
| 3 | + |
| 4 | +"""`dotbot gateway` — host-side Mari gateway bridge. |
| 5 | +
|
| 6 | +Runs on whatever computer the gateway firmware is plugged into (a |
| 7 | +laptop for a starter setup, a Pi for a permanent install). Bridges UART |
| 8 | +HDLC frames to/from an MQTT broker, so a `dotbot controller --conn |
| 9 | +mqtts://…` can reach the swarm from anywhere. |
| 10 | +
|
| 11 | +Thin re-mount of marilib's `mari-edge`: wraps a `MarilibEdge` with a |
| 12 | +serial adapter and (optionally) an MQTT adapter. With no `--mqtt-url` |
| 13 | +it runs in **local-stdout mode** — received frames print to stdout, so |
| 14 | +a freshly-flashed gateway can be sanity-checked with zero MQTT infra. |
| 15 | +
|
| 16 | +Phase 1 is a raw bridge (mari frames + raw mari topics). DotBot-semantic |
| 17 | +MQTT topics are a later phase, tracked in the controller-CLI-redesign |
| 18 | +plan. |
| 19 | +""" |
| 20 | + |
| 21 | +import os |
| 22 | +import time |
| 23 | + |
| 24 | +import click |
| 25 | + |
| 26 | + |
| 27 | +def _run_gateway(port, mqtt_url): # pragma: no cover - needs a real gateway |
| 28 | + """Construct a MarilibEdge bridge and pump it until interrupted. |
| 29 | +
|
| 30 | + Imports marilib lazily so `dotbot gateway --help` is cheap and the |
| 31 | + command is importable without a serial port present. |
| 32 | + """ |
| 33 | + from marilib.communication_adapter import MQTTAdapter, SerialAdapter |
| 34 | + from marilib.marilib_edge import MarilibEdge |
| 35 | + from marilib.model import EdgeEvent |
| 36 | + from marilib.serial_uart import get_default_port |
| 37 | + |
| 38 | + port = port or get_default_port() |
| 39 | + stdout_mode = mqtt_url is None |
| 40 | + |
| 41 | + def on_event(event, event_data): |
| 42 | + # In local-stdout mode, surface received data frames so a fresh |
| 43 | + # gateway can be eyeballed without a broker. |
| 44 | + if stdout_mode and event == EdgeEvent.NODE_DATA: |
| 45 | + src = getattr(event_data.header, "source", 0) |
| 46 | + payload = getattr(event_data, "payload", b"") |
| 47 | + click.echo(f"<- {src:016x}: {bytes(payload).hex()}") |
| 48 | + |
| 49 | + mqtt_interface = None |
| 50 | + if mqtt_url is not None: |
| 51 | + # Broker credentials come from the environment (see the controller |
| 52 | + # surface). They take effect once the marilib companion adds |
| 53 | + # username/password to MQTTAdapter; until then anonymous connect. |
| 54 | + mqtt_interface = MQTTAdapter.from_url(mqtt_url, is_edge=True) |
| 55 | + _ = (os.environ.get("DOTBOT_MQTT_USER"), os.environ.get("DOTBOT_MQTT_PASS")) |
| 56 | + |
| 57 | + mari = MarilibEdge( |
| 58 | + on_event, |
| 59 | + serial_interface=SerialAdapter(port), |
| 60 | + mqtt_interface=mqtt_interface, |
| 61 | + ) |
| 62 | + where = mqtt_url if mqtt_url else "local-stdout" |
| 63 | + click.echo(f"dotbot gateway: {port} <-> {where}", err=True) |
| 64 | + try: |
| 65 | + while True: |
| 66 | + mari.update() |
| 67 | + time.sleep(0.5) |
| 68 | + except KeyboardInterrupt: |
| 69 | + pass |
| 70 | + finally: |
| 71 | + try: |
| 72 | + mari.close() |
| 73 | + except Exception: # pylint: disable=broad-except |
| 74 | + pass |
| 75 | + |
| 76 | + |
| 77 | +@click.command( |
| 78 | + name="gateway", |
| 79 | + help=( |
| 80 | + "Host-side Mari gateway bridge (UART <-> MQTT). Runs wherever the " |
| 81 | + "gateway firmware is plugged in. Without --mqtt-url, prints received " |
| 82 | + "frames to stdout (local debug mode)." |
| 83 | + ), |
| 84 | +) |
| 85 | +@click.option( |
| 86 | + "-p", |
| 87 | + "--port", |
| 88 | + type=str, |
| 89 | + default=None, |
| 90 | + help="Serial port of the attached gateway firmware. Default: autodetect.", |
| 91 | +) |
| 92 | +@click.option( |
| 93 | + "-m", |
| 94 | + "--mqtt-url", |
| 95 | + type=str, |
| 96 | + default=None, |
| 97 | + help=( |
| 98 | + "MQTT broker to bridge to (`mqtts://host:port`). Absent → " |
| 99 | + "local-stdout debug mode." |
| 100 | + ), |
| 101 | +) |
| 102 | +def cmd(port, mqtt_url): |
| 103 | + """Run the gateway bridge.""" |
| 104 | + _run_gateway(port, mqtt_url) |
0 commit comments