From 357343e401730cb15f467db89142e3397a121da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=96=87=E5=B3=B0?= Date: Wed, 3 Jun 2026 12:17:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 3 + mqtpp/mqtpp.go | 4 + .../mqtt_forward_to_local.cpython-314.pyc | Bin 0 -> 7452 bytes py/mqtt_forward_to_local.py | 108 ++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 .gitmodules create mode 100644 py/__pycache__/mqtt_forward_to_local.cpython-314.pyc create mode 100644 py/mqtt_forward_to_local.py diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..538a71f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "firmware"] + path = firmware + url = https://github.com/meshtastic/firmware.git \ No newline at end of file diff --git a/mqtpp/mqtpp.go b/mqtpp/mqtpp.go index 659d1be..dc547e9 100644 --- a/mqtpp/mqtpp.go +++ b/mqtpp/mqtpp.go @@ -127,6 +127,10 @@ func MQTTPP(topic string, raw []byte, key []byte) (bool, []byte, map[string]any) //解码失败 return false, nil, map[string]any{"topic": topic, "error": err.Error(), "payload_len": len(raw)} } + if record["type"] == "encrypted_packet" { + return false, nil, map[string]any{"topic": topic, "error": "cannot be decrypted", "payload_len": len(raw)} + } + return true, raw, record } diff --git a/py/__pycache__/mqtt_forward_to_local.cpython-314.pyc b/py/__pycache__/mqtt_forward_to_local.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b921398597f3d315ae32b2d85709be80c981fa8 GIT binary patch literal 7452 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 None: + print(json.dumps(record, ensure_ascii=False, separators=(",", ":")), flush=True) + + +def on_local_connect(client: mqtt.Client, userdata: argparse.Namespace, flags: Any, reason_code: Any, properties: Any = None) -> None: + print_json({"event": "local_connected", "host": userdata.local_host, "port": userdata.local_port, "reason_code": str(reason_code)}) + + +def on_source_connect(client: mqtt.Client, userdata: argparse.Namespace, flags: Any, reason_code: Any, properties: Any = None) -> None: + print_json({"event": "source_connected", "host": userdata.host, "port": userdata.port, "reason_code": str(reason_code)}) + for topic in userdata.topics: + client.subscribe(topic, qos=userdata.qos) + print_json({"event": "source_subscribed", "topic": topic, "qos": userdata.qos}) + + +def on_source_message(client: mqtt.Client, userdata: argparse.Namespace, msg: mqtt.MQTTMessage) -> None: + result = userdata.local_client.publish(msg.topic, payload=msg.payload, qos=msg.qos, retain=msg.retain) + print_json( + { + "event": "forwarded", + "topic": msg.topic, + "payload_len": len(msg.payload), + "qos": msg.qos, + "retain": msg.retain, + "result": result.rc, + } + ) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Forward Meshtastic MQTT messages from mqtt.meshtastic.org to a local MQTT broker.") + parser.add_argument("--host", default=DEFAULT_HOST, help="Source MQTT broker hostname") + parser.add_argument("--port", type=int, default=1883, help="Source MQTT broker port") + parser.add_argument("--username", default=DEFAULT_USERNAME, help="Source MQTT username") + parser.add_argument("--password", default=DEFAULT_PASSWORD, help="Source MQTT password") + parser.add_argument( + "--topic", + action="append", + dest="topics", + help="Source topic to subscribe; may be repeated. Defaults to msh/US/#", + ) + parser.add_argument("--qos", type=int, default=0, choices=(0, 1, 2), help="Source subscription QoS") + parser.add_argument("--client-id", default="meshtastic-forward-source", help="Source MQTT client id") + parser.add_argument("--local-host", default=DEFAULT_LOCAL_HOST, help="Local MQTT broker hostname") + parser.add_argument("--local-port", type=int, default=DEFAULT_LOCAL_PORT, help="Local MQTT broker port") + parser.add_argument("--local-client-id", default="meshtastic-forward-local", help="Local MQTT client id") + return parser.parse_args() + + +def main() -> int: + args = parse_args() + if not args.topics: + args.topics = list(DEFAULT_TOPICS) + + local_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=args.local_client_id) + local_client.user_data_set(args) + local_client.on_connect = on_local_connect + local_client.connect(args.local_host, args.local_port, keepalive=60) + local_client.loop_start() + args.local_client = local_client + + source_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=args.client_id) + source_client.user_data_set(args) + source_client.on_connect = on_source_connect + source_client.on_message = on_source_message + if args.username is not None: + source_client.username_pw_set(args.username, args.password) + + source_client.connect(args.host, args.port, keepalive=60) + try: + source_client.loop_forever() + finally: + local_client.loop_stop() + local_client.disconnect() + return 0 + + +if __name__ == "__main__": + sys.exit(main())