|
|
import serial
|
|
|
import json
|
|
|
import math
|
|
|
import time
|
|
|
|
|
|
PORT = '/dev/ttyUSB0' # 替換為實際的序列埠名稱
|
|
|
BAUD_RATE = 115200
|
|
|
SYSTEM_ID = 2
|
|
|
|
|
|
# 可切換為 "essential" 或 "full";預設發送完整狀態。
|
|
|
STATUS_FORMAT = "full"
|
|
|
|
|
|
try:
|
|
|
ser = serial.Serial(PORT, BAUD_RATE, timeout=0.1)
|
|
|
print("XBee 無人機啟動...")
|
|
|
except Exception as e:
|
|
|
print(f"無法開啟序列埠: {e}")
|
|
|
exit()
|
|
|
|
|
|
def get_uav_status(status_format=None):
|
|
|
status_format = status_format or STATUS_FORMAT
|
|
|
essential_status = {
|
|
|
"s": SYSTEM_ID, # system ID
|
|
|
"m": "GUIDED", # mode
|
|
|
"b": 78, # battery (%)
|
|
|
"p": [24.1199660, 120.677230], # [lat, lon] (deg)
|
|
|
"y": 178.5, # yaw (deg)
|
|
|
}
|
|
|
full_status = {
|
|
|
"s": SYSTEM_ID, # system ID
|
|
|
"m": "GUIDED", # mode
|
|
|
"a": 1, # 1=armed, 0=disarmed
|
|
|
"b": 78, # battery (%)
|
|
|
"h": 12.3, # height (m)
|
|
|
"v": 4.2, # velocity (m/s)
|
|
|
"p": [24.1199660, 120.677230], # position: [lat, lon] (deg)
|
|
|
"ypr": [178.5, -0.1, 0.0], # yaw, pitch, roll (deg)
|
|
|
"g": 3, # GPS type {0: no GPS, 1: no fix, 2: 2D, 3: 3D, 4: DGPS, 5: RTK float, 6: RTK fixed}
|
|
|
"d": [0.8, 1.2], # DOP: [hdop, vdop]
|
|
|
}
|
|
|
if status_format == "essential":
|
|
|
return essential_status
|
|
|
if status_format == "full":
|
|
|
return full_status
|
|
|
raise ValueError(
|
|
|
f"不支援的 STATUS_FORMAT: {status_format},請使用 essential 或 full")
|
|
|
|
|
|
def handle_command(cmd_obj):
|
|
|
# 統一指令格式: {"s": <int>, "c": "START|STOP|GOTO"}
|
|
|
target_s = cmd_obj.get("s")
|
|
|
command = cmd_obj.get("c")
|
|
|
ack = None
|
|
|
|
|
|
if (isinstance(target_s, bool)
|
|
|
or not isinstance(target_s, int)
|
|
|
or target_s != SYSTEM_ID):
|
|
|
ack = {
|
|
|
"result": "ERROR",
|
|
|
"command": command,
|
|
|
"message": (
|
|
|
f"指令目標 system_id={target_s} 不符,"
|
|
|
f"本 UAV system_id={SYSTEM_ID}"
|
|
|
),
|
|
|
}
|
|
|
elif not isinstance(command, str) or command not in {"START", "STOP", "GOTO"}:
|
|
|
print(f"\n[接收指令] 未知或格式錯誤的指令: {command}")
|
|
|
ack = {
|
|
|
"result": "ERROR",
|
|
|
"command": command,
|
|
|
"message": "指令 c 必須為大寫 START、STOP 或 GOTO",
|
|
|
}
|
|
|
elif command == "START":
|
|
|
print("\n[接收指令] 開始任務")
|
|
|
ack = {"result": "SUCCESS", "command": "START", "message": "成功"}
|
|
|
elif command == "STOP":
|
|
|
print("\n[接收指令] 停止任務")
|
|
|
ack = {"result": "SUCCESS", "command": "STOP", "message": "成功"}
|
|
|
elif command == "GOTO":
|
|
|
# 優先使用 XBee 格式 p=[lat, lon],並兼容舊版 lat/lon 欄位。
|
|
|
position = cmd_obj.get("p")
|
|
|
if isinstance(position, (list, tuple)) and len(position) >= 2:
|
|
|
lat, lon = position[0], position[1]
|
|
|
else:
|
|
|
lat, lon = cmd_obj.get("lat"), cmd_obj.get("lon")
|
|
|
|
|
|
try:
|
|
|
if isinstance(lat, bool) or isinstance(lon, bool):
|
|
|
raise ValueError
|
|
|
lat = float(lat)
|
|
|
lon = float(lon)
|
|
|
if not math.isfinite(lat) or not math.isfinite(lon):
|
|
|
raise ValueError
|
|
|
if not -90.0 <= lat <= 90.0 or not -180.0 <= lon <= 180.0:
|
|
|
raise ValueError
|
|
|
except (TypeError, ValueError):
|
|
|
print("\n[接收指令] GOTO 目標無效,需要有效的 p=[lat,lon]")
|
|
|
ack = {
|
|
|
"result": "ERROR",
|
|
|
"command": "GOTO",
|
|
|
"message": "GOTO 指令缺少或包含無效的 p=[lat,lon]",
|
|
|
}
|
|
|
else:
|
|
|
target = [lat, lon]
|
|
|
print(f"\n[接收指令] 導航任務 目標位置: {target}")
|
|
|
ack = {
|
|
|
"result": "SUCCESS",
|
|
|
"command": "GOTO",
|
|
|
"p": target,
|
|
|
"message": "成功",
|
|
|
}
|
|
|
# 發送回覆(ack)到 GCS via serial
|
|
|
if ack is not None:
|
|
|
try:
|
|
|
payload = json.dumps(ack, ensure_ascii=False) + "\n"
|
|
|
ser.write(payload.encode('utf-8'))
|
|
|
print(f"已發送回覆: {payload.strip()}")
|
|
|
except Exception as e:
|
|
|
print(f"發送回覆失敗: {e}")
|
|
|
|
|
|
try:
|
|
|
while True:
|
|
|
data = get_uav_status()
|
|
|
json_payload = json.dumps(data, separators=(',', ':')) + "\n"
|
|
|
ser.write(json_payload.encode('utf-8'))
|
|
|
print(f"發送狀態: {json_payload.strip()}")
|
|
|
for _ in range(10):
|
|
|
if ser.in_waiting > 0:
|
|
|
try:
|
|
|
cmd_line = ser.readline().decode('utf-8').strip()
|
|
|
if cmd_line:
|
|
|
cmd_obj = json.loads(cmd_line)
|
|
|
handle_command(cmd_obj)
|
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
pass
|
|
|
time.sleep(0.1)
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
print("發送停止")
|
|
|
finally:
|
|
|
ser.close()
|