# pip install paho-mqtt import json import re import time from paho.mqtt import client as mqtt # ========= 使用者設定 ========= BROKER_HOST = "169.254.11.130" BROKER_PORT = 1886 TOPIC = "topic_plc_and_py_for_AXIS" QOS = 1 # 建議測試用 1 # ============================ # 全域狀態(方便觀察最新值) state = { "BUTTON_Y": None, "Forward_RPM": None, "UPDown_RPM": None, } def _robust_json_parse(raw: str): """ 將 PLC 端常見的非標準 JSON 嘗試修正後解析為 dict。 例:'{Forward_RPM:1500,UPDown_RPM:800}' -> {'Forward_RPM':1500,'UPDown_RPM':800} 也會把 TRUE/FALSE -> true/false """ s = raw.strip() # 去掉最外層引號(若有) if (s.startswith("'") and s.endswith("'")) or (s.startswith('"') and s.endswith('"')): s = s[1:-1].strip() # PLC 常見布林大寫 -> JSON 規範小寫 s = s.replace("TRUE", "true").replace("FALSE", "false") # 自動補 key 的雙引號:{key: -> {"key": s = re.sub(r'([{,]\s*)([A-Za-z_]\w*)\s*:', r'\1"\2":', s) # 轉單引號為雙引號(若 payload 用了單引號包字串) # 放在補 key 後,避免 key 的引號再被替換 s = s.replace("'", '"') return json.loads(s) def on_connect(client: mqtt.Client, userdata, flags, reason_code, properties=None): """ 連上 Broker 後自動訂閱 Topic。 paho-mqtt 2.x 的 on_connect 第四個參數是 reason_code(非 rc),名稱略有不同。 """ if reason_code == 0: print(f"✅ 已連線到 MQTT Broker {BROKER_HOST}:{BROKER_PORT}") client.subscribe(TOPIC, qos=QOS) print(f"📡 已訂閱 Topic: {TOPIC}(QoS={QOS})") else: print(f"❌ 連線失敗,reason_code={reason_code}") def on_message(client: mqtt.Client, userdata, msg: mqtt.MQTTMessage): payload_raw = msg.payload.decode("utf-8", errors="replace") print(f"\n📥 收到原始訊息:{payload_raw}") # 先試標準 JSON,失敗再用容錯解析 try: data = json.loads(payload_raw) except json.JSONDecodeError: try: data = _robust_json_parse(payload_raw) except Exception as e: print(f"❌ 無法解析為 JSON:{payload_raw} ;錯誤:{e}") return # 只更新我們關心的欄位(有傳才改) for key in ("BUTTON_Y", "Forward_RPM", "UPDown_RPM"): if key in data: state[key] = data[key] print( f"✅ 解析後資料:{data}\n" f"📊 目前狀態:BUTTON_Y={state['BUTTON_Y']}, " f"Forward_RPM={state['Forward_RPM']}, UPDown_RPM={state['UPDown_RPM']}" ) def on_disconnect(client: mqtt.Client, userdata, reason_code, properties=None): print(f"🔌 已斷線,reason_code={reason_code}") def sub_to_plc(broker_host: str, broker_port: int, topic: str): client = mqtt.Client(client_id="py_sub_plc_test", protocol=mqtt.MQTTv311) # 如需帳密或 TLS,在這裡設定 # client.username_pw_set("user", "pass") # client.tls_set(ca_certs="ca.pem") # 自動重連延遲(paho-mqtt 2.x) client.reconnect_delay_set(min_delay=1, max_delay=10) client.on_connect = on_connect client.on_message = on_message client.on_disconnect = on_disconnect # 先連線,再進入 loop client.connect(broker_host, broker_port, keepalive=60) try: # loop_forever 會阻塞執行緒,內建自動重連 client.loop_forever() except KeyboardInterrupt: print("\n🛑 收到中斷訊號,準備斷線...") finally: try: client.disconnect() except Exception: pass if __name__ == "__main__": print("🚀 啟動 MQTT 訂閱器...") print(f"Broker={BROKER_HOST}:{BROKER_PORT}, Topic={TOPIC}, QoS={QOS}") sub_to_plc(BROKER_HOST, BROKER_PORT, TOPIC)