import argparse import json import time from datetime import datetime from urllib.parse import urlparse import paho.mqtt.client as mqtt from config.dependencies import build_subscriber_dependencies def parse_broker(broker): if not broker: raise ValueError("broker is required") if "://" not in broker: broker = "tcp://" + broker parsed = urlparse(broker) host = parsed.hostname or "localhost" port = parsed.port or 1883 scheme = (parsed.scheme or "tcp").lower() return scheme, host, port def print_flat_fields(title, value): print(title) if isinstance(value, dict): for k, v in value.items(): print(f" {k}: {v}") else: print(f" {value}") def run_subscriber(args, deps): mqtt_config = deps.config.mqtt tms_config = deps.config.tms topic = args.topic or mqtt_config.sub_topic or mqtt_config.pub_topic if not topic: raise ValueError("No topic to subscribe. Set sub-topic or pub-topic, or pass --topic") scheme, host, port = parse_broker(mqtt_config.broker) print("MQTT subscriber config:") print(f" broker: {scheme}://{host}:{port}") print(f" client-id: {mqtt_config.subscriber_client_id}") print(f" topic: {topic}") client = mqtt.Client(client_id=mqtt_config.subscriber_client_id, clean_session=True) if mqtt_config.username is not None: client.username_pw_set(mqtt_config.username, mqtt_config.password) if scheme in ("ssl", "tls", "mqtts"): client.tls_set() def on_connect(c, userdata, flags, rc): if rc == 0: print("Connected") c.subscribe(topic) print(f"Subscribed: {topic}") else: print(f"Connect failed rc={rc}") def on_disconnect(c, userdata, rc): print(f"Disconnected callback rc={rc}") def on_message(c, userdata, msg): raw_payload = msg.payload.decode("utf-8", errors="replace") print("=" * 80) print(f"RX Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"Topic: {msg.topic}") print(f"QoS: {msg.qos}, Retain: {msg.retain}, Bytes: {len(msg.payload)}") try: obj = json.loads(raw_payload) print("Raw JSON:") print(json.dumps(obj, ensure_ascii=False, indent=2)) if isinstance(obj, dict): print_flat_fields("meta:", obj.get("meta")) print_flat_fields("data:", obj.get("data")) extra_keys = [key for key in obj.keys() if key not in ("meta", "data")] for key in extra_keys: print_flat_fields(f"{key}:", obj.get(key)) except Exception: print("Raw payload:") print(raw_payload) client.on_connect = on_connect client.on_disconnect = on_disconnect client.on_message = on_message client.connect(host, port, keepalive=tms_config.keepalive) client.loop_start() try: while True: time.sleep(1) except KeyboardInterrupt: pass finally: client.loop_stop() client.disconnect() print("Disconnected") def main(): ap = argparse.ArgumentParser(description="MQTT subscriber") ap.add_argument("--config", default="config.yaml", help="Path to config yaml") ap.add_argument("--topic", default="", help="Override topic to subscribe") args = ap.parse_args() deps = build_subscriber_dependencies(args.config) run_subscriber(args, deps) if __name__ == "__main__": main()