|
|
|
|
|
import serial
|
|
|
|
|
|
import json
|
|
|
|
|
|
import threading
|
|
|
|
|
|
import time
|
|
|
|
|
|
from pynput import keyboard
|
|
|
|
|
|
|
|
|
|
|
|
SYSTEM_ID = 2
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
ser = serial.Serial('/dev/ttyUSB1', 115200, timeout=0.1)
|
|
|
|
|
|
print("XBee 地面站啟動")
|
|
|
|
|
|
print("[1] START [2] STOP [3] GOTO [Esc] 退出程式")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"無法開啟序列埠: {e}")
|
|
|
|
|
|
exit()
|
|
|
|
|
|
|
|
|
|
|
|
GPS_TYPE_LABELS = {
|
|
|
|
|
|
0: "no GPS",
|
|
|
|
|
|
1: "no fix",
|
|
|
|
|
|
2: "2D",
|
|
|
|
|
|
3: "3D",
|
|
|
|
|
|
4: "DGPS",
|
|
|
|
|
|
5: "RTK float",
|
|
|
|
|
|
6: "RTK fixed",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def format_uav_data(uav_data):
|
|
|
|
|
|
system_id = uav_data.get("s", "?")
|
|
|
|
|
|
mode = uav_data.get("m", "?")
|
|
|
|
|
|
battery = uav_data.get("b", "?")
|
|
|
|
|
|
position = uav_data.get("p", "?")
|
|
|
|
|
|
|
|
|
|
|
|
parts = [
|
|
|
|
|
|
f"[UAV] ID:{system_id}",
|
|
|
|
|
|
f"Mode:{mode}",
|
|
|
|
|
|
f"Bat:{battery}%",
|
|
|
|
|
|
f"Pos:{position}",
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
if "a" in uav_data:
|
|
|
|
|
|
armed_text = "ARMED" if int(uav_data.get("a", 0)) == 1 else "DISARMED"
|
|
|
|
|
|
parts.append(f"Arm:{armed_text}")
|
|
|
|
|
|
|
|
|
|
|
|
ypr = uav_data.get("ypr")
|
|
|
|
|
|
if isinstance(ypr, list) and len(ypr) >= 3:
|
|
|
|
|
|
parts.append(f"Height:{uav_data.get('h', '?')}m")
|
|
|
|
|
|
parts.append(f"Yaw:{ypr[0]}deg")
|
|
|
|
|
|
parts.append(f"Pitch:{ypr[1]}deg")
|
|
|
|
|
|
parts.append(f"Roll:{ypr[2]}deg")
|
|
|
|
|
|
elif "y" in uav_data:
|
|
|
|
|
|
parts.append(f"Height:{uav_data.get('h', '?')}m")
|
|
|
|
|
|
parts.append(f"Yaw:{uav_data.get('y')}deg")
|
|
|
|
|
|
elif "h" in uav_data:
|
|
|
|
|
|
parts.append(f"Heading:{uav_data.get('h')}")
|
|
|
|
|
|
|
|
|
|
|
|
if "v" in uav_data:
|
|
|
|
|
|
parts.append(f"Vel:{uav_data.get('v')}m/s")
|
|
|
|
|
|
|
|
|
|
|
|
if "g" in uav_data:
|
|
|
|
|
|
gps_type = uav_data.get("g")
|
|
|
|
|
|
parts.append(f"GPS:{gps_type}({GPS_TYPE_LABELS.get(gps_type, 'unknown')})")
|
|
|
|
|
|
|
|
|
|
|
|
dop = uav_data.get("d")
|
|
|
|
|
|
if isinstance(dop, list) and len(dop) >= 2:
|
|
|
|
|
|
parts.append(f"DOP(H/V):{dop[0]}/{dop[1]}")
|
|
|
|
|
|
|
|
|
|
|
|
return " | ".join(parts)
|
|
|
|
|
|
|
|
|
|
|
|
def receive_thread():
|
|
|
|
|
|
while True:
|
|
|
|
|
|
try:
|
|
|
|
|
|
if ser.in_waiting > 0:
|
|
|
|
|
|
line = ser.readline().decode('utf-8', errors='ignore').strip()
|
|
|
|
|
|
if line:
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = json.loads(line)
|
|
|
|
|
|
# 判斷是確認訊息(ack)還是 UAV 狀態
|
|
|
|
|
|
if "result" in data and "command" in data and "message" in data:
|
|
|
|
|
|
# 這是確認訊息
|
|
|
|
|
|
result = data.get("result")
|
|
|
|
|
|
cmd = data.get("command")
|
|
|
|
|
|
msg = data.get("message")
|
|
|
|
|
|
print(f"\n[確認] 命令:{cmd} 狀態:{result} 訊息:{msg}", flush=True)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 這是 UAV 狀態訊息
|
|
|
|
|
|
print(format_uav_data(data), flush=True)
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
|
pass
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"\n[接收錯誤] {e}")
|
|
|
|
|
|
time.sleep(0.01)
|
|
|
|
|
|
|
|
|
|
|
|
t = threading.Thread(target=receive_thread, daemon=True)
|
|
|
|
|
|
t.start()
|
|
|
|
|
|
|
|
|
|
|
|
def send_command(command, position=None):
|
|
|
|
|
|
try:
|
|
|
|
|
|
if command not in {"START", "STOP", "GOTO"}:
|
|
|
|
|
|
raise ValueError("指令必須為大寫 START、STOP 或 GOTO")
|
|
|
|
|
|
cmd = {
|
|
|
|
|
|
"s": SYSTEM_ID,
|
|
|
|
|
|
"c": command
|
|
|
|
|
|
}
|
|
|
|
|
|
if position is not None:
|
|
|
|
|
|
cmd["p"] = position
|
|
|
|
|
|
|
|
|
|
|
|
payload = json.dumps(cmd, separators=(',', ':')) + "\n"
|
|
|
|
|
|
ser.write(payload.encode('utf-8'))
|
|
|
|
|
|
print(f"\n>> [指令已發送] {payload.strip()}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"\n發送失敗: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
def on_press(key):
|
|
|
|
|
|
try:
|
|
|
|
|
|
if key.char == "1":
|
|
|
|
|
|
send_command("START")
|
|
|
|
|
|
elif key.char == "2":
|
|
|
|
|
|
send_command("STOP")
|
|
|
|
|
|
elif key.char == "3":
|
|
|
|
|
|
send_command("GOTO", [24.1199660, 120.677230])
|
|
|
|
|
|
except AttributeError:
|
|
|
|
|
|
if key == keyboard.Key.esc:
|
|
|
|
|
|
print("\n程式結束")
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
with keyboard.Listener(on_press=on_press) as listener:
|
|
|
|
|
|
listener.join()
|