397 lines
13 KiB
Python
397 lines
13 KiB
Python
'''
|
||
This demo show the communication interface of MR813 motion control board based on Lcm.
|
||
Dependency:
|
||
- robot_control_cmd_lcmt.py
|
||
- robot_control_response_lcmt.py
|
||
'''
|
||
import lcm
|
||
import sys
|
||
import os
|
||
import time
|
||
from threading import Thread, Lock
|
||
import sys
|
||
|
||
import rclpy
|
||
|
||
from utils.robot_control_cmd_lcmt import robot_control_cmd_lcmt
|
||
from utils.robot_control_response_lcmt import robot_control_response_lcmt
|
||
from utils.localization_lcmt import localization_lcmt
|
||
from utils.image_raw import ImageProcessor
|
||
from utils.base_msg import BaseMsg
|
||
# from utils.marker_client import MarkerRunner
|
||
|
||
from task_1.task_1 import run_task_1
|
||
from task_2_5.task_2_5 import run_task_2_5
|
||
from task_3.task_3 import run_task_3, run_task_3_back
|
||
from task_1.task_1 import run_task_1, run_task_1_back
|
||
from task_2.task_2 import run_task_2, run_task_2_back
|
||
from task_2_5.task_2_5 import run_task_2_5, run_task_2_5_back
|
||
from task_4.task_4 import run_task_4, run_task_4_back
|
||
from task_test.task_left_line import run_task_test
|
||
from task_5.task_5 import run_task_5
|
||
|
||
from base_move.turn_degree import turn_degree, turn_degree_v2
|
||
from base_move.center_on_dual_tracks import center_on_dual_tracks
|
||
from base_move.go_to_xy import go_to_x_v2, go_to_y_v2
|
||
|
||
from utils.log_helper import info
|
||
|
||
# 新增:导入配置文件
|
||
from config import get_config, SYSTEM_CONFIG
|
||
|
||
# 新增:导入single-test测试模块
|
||
try:
|
||
from single_test.test_main import main_menu as test_main_menu
|
||
from single_test.test_main import run_interactive_test_selection
|
||
SINGLE_TEST_AVAILABLE = get_config('SYSTEM', 'ENABLE_SINGLE_TEST', True)
|
||
except ImportError:
|
||
print("警告:single-test模块未找到,测试功能将不可用")
|
||
SINGLE_TEST_AVAILABLE = False
|
||
|
||
pass_marker = True
|
||
TIME_SLEEP = get_config('SYSTEM', 'TIME_SLEEP', 5000)
|
||
|
||
def print_main_menu():
|
||
"""打印主程序菜单"""
|
||
print("\n" + "="*70)
|
||
print(" 机器狗控制系统")
|
||
print("="*70)
|
||
print("🎯 运行模式选择:")
|
||
print(" 1. 完整任务模式 - 运行完整的任务1-5流程")
|
||
print(" 2. 测试模式 - 进入子运动函数测试")
|
||
print(" 3. 交互式测试 - 快速测试单个功能")
|
||
print(" 0. 退出程序")
|
||
print("="*70)
|
||
print("💡 提示:")
|
||
print(" - 模式1:执行完整比赛任务流程")
|
||
print(" - 模式2:菜单式测试各种子运动函数")
|
||
print(" - 模式3:快速命令行测试界面")
|
||
print("="*70)
|
||
|
||
def run_complete_task_mode():
|
||
"""运行完整任务模式(原main函数逻辑)"""
|
||
rclpy.init()
|
||
Ctrl = Robot_Ctrl()
|
||
Ctrl.run()
|
||
msg = robot_control_cmd_lcmt()
|
||
|
||
try:
|
||
info("Recovery stand", "info")
|
||
Ctrl.base_msg.stand_up()
|
||
Ctrl.base_msg.stop() # BUG 垃圾指令 for eat
|
||
|
||
run_task_1(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
|
||
arrow_direction = run_task_2(Ctrl, msg, xy_flag=False)
|
||
|
||
info(f"识别到箭头方向: {arrow_direction}", "info")
|
||
info(f"识别到箭头方向: {arrow_direction}", "info")
|
||
run_task_2_5(Ctrl, msg, direction=arrow_direction)
|
||
|
||
if arrow_direction == 'left':
|
||
run_task_4(Ctrl, msg)
|
||
else:
|
||
run_task_3(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
|
||
turn_degree_v2(Ctrl, msg, degree=90, absolute=True)
|
||
run_task_5(Ctrl, msg, direction=arrow_direction, time_sleep=TIME_SLEEP)
|
||
|
||
if arrow_direction == 'left':
|
||
run_task_3_back(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
else:
|
||
run_task_4_back(Ctrl, msg)
|
||
|
||
run_task_2_5_back(Ctrl, msg, direction=arrow_direction)
|
||
|
||
run_task_2_back(Ctrl, msg, xy_flag=False)
|
||
|
||
run_task_1_back(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n程序被用户中断")
|
||
except Exception as e:
|
||
print(f"发生错误: {e}")
|
||
finally:
|
||
print("正在清理资源...")
|
||
Ctrl.quit()
|
||
rclpy.shutdown()
|
||
print("程序已退出")
|
||
|
||
def run_test_mode():
|
||
"""运行测试模式"""
|
||
if not SINGLE_TEST_AVAILABLE:
|
||
print("❌ 测试模块不可用,请检查single-test目录是否存在")
|
||
return
|
||
|
||
rclpy.init()
|
||
Ctrl = Robot_Ctrl()
|
||
Ctrl.run()
|
||
msg = robot_control_cmd_lcmt()
|
||
|
||
try:
|
||
info("初始化测试环境", "info")
|
||
Ctrl.base_msg.stand_up()
|
||
Ctrl.base_msg.stop()
|
||
|
||
# 进入测试主菜单
|
||
test_main_menu(Ctrl, msg)
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n测试被用户中断")
|
||
except Exception as e:
|
||
print(f"测试过程中发生错误: {e}")
|
||
finally:
|
||
print("正在清理测试环境...")
|
||
Ctrl.quit()
|
||
rclpy.shutdown()
|
||
print("测试环境已清理")
|
||
|
||
def run_interactive_test_mode():
|
||
"""运行交互式测试模式"""
|
||
if not SINGLE_TEST_AVAILABLE:
|
||
print("❌ 测试模块不可用,请检查single-test目录是否存在")
|
||
return
|
||
|
||
rclpy.init()
|
||
Ctrl = Robot_Ctrl()
|
||
Ctrl.run()
|
||
msg = robot_control_cmd_lcmt()
|
||
|
||
try:
|
||
info("初始化交互式测试环境", "info")
|
||
Ctrl.base_msg.stand_up()
|
||
Ctrl.base_msg.stop()
|
||
|
||
# 进入交互式测试
|
||
run_interactive_test_selection(Ctrl, msg)
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n交互式测试被用户中断")
|
||
except Exception as e:
|
||
print(f"交互式测试过程中发生错误: {e}")
|
||
finally:
|
||
print("正在清理交互式测试环境...")
|
||
Ctrl.quit()
|
||
rclpy.shutdown()
|
||
print("交互式测试环境已清理")
|
||
|
||
def main():
|
||
"""主函数 - 支持模式选择"""
|
||
while True:
|
||
print_main_menu()
|
||
|
||
try:
|
||
choice = input("请选择运行模式 (输入编号): ").strip()
|
||
|
||
if choice == '0':
|
||
print("👋 感谢使用机器狗控制系统,再见!")
|
||
break
|
||
elif choice == '1':
|
||
print("🚀 启动完整任务模式...")
|
||
run_complete_task_mode()
|
||
print("\n✅ 完整任务模式执行完毕")
|
||
input("按回车键返回主菜单...")
|
||
elif choice == '2':
|
||
print("🔧 启动测试模式...")
|
||
run_test_mode()
|
||
print("\n✅ 测试模式执行完毕")
|
||
input("按回车键返回主菜单...")
|
||
elif choice == '3':
|
||
print("⚡ 启动交互式测试模式...")
|
||
run_interactive_test_mode()
|
||
print("\n✅ 交互式测试模式执行完毕")
|
||
input("按回车键返回主菜单...")
|
||
else:
|
||
print("❌ 无效的选择,请输入 0-3 之间的数字")
|
||
input("按回车键继续...")
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n👋 程序被用户中断,再见!")
|
||
break
|
||
except Exception as e:
|
||
print(f"❌ 发生错误: {e}")
|
||
input("按回车键继续...")
|
||
|
||
# 保留原来的main函数作为legacy_main,以防需要
|
||
def legacy_main():
|
||
"""原始的main函数(保留作为备用)"""
|
||
rclpy.init()
|
||
Ctrl = Robot_Ctrl()
|
||
Ctrl.run()
|
||
msg = robot_control_cmd_lcmt()
|
||
|
||
try:
|
||
info("Recovery stand", "info")
|
||
Ctrl.base_msg.stand_up()
|
||
Ctrl.base_msg.stop() # BUG 垃圾指令 for eat
|
||
|
||
run_task_1(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
|
||
arrow_direction = run_task_2(Ctrl, msg, xy_flag=False)
|
||
|
||
info(f"识别到箭头方向: {arrow_direction}", "info")
|
||
info(f"识别到箭头方向: {arrow_direction}", "info")
|
||
run_task_2_5(Ctrl, msg, direction=arrow_direction)
|
||
|
||
if arrow_direction == 'left':
|
||
run_task_4(Ctrl, msg)
|
||
else:
|
||
run_task_3(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
|
||
turn_degree_v2(Ctrl, msg, degree=90, absolute=True)
|
||
run_task_5(Ctrl, msg, direction=arrow_direction, time_sleep=TIME_SLEEP)
|
||
|
||
if arrow_direction == 'left':
|
||
run_task_3_back(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
else:
|
||
run_task_4_back(Ctrl, msg)
|
||
|
||
run_task_2_5_back(Ctrl, msg, direction=arrow_direction)
|
||
|
||
run_task_2_back(Ctrl, msg, xy_flag=False)
|
||
|
||
run_task_1_back(Ctrl, msg, time_sleep=TIME_SLEEP)
|
||
|
||
except KeyboardInterrupt:
|
||
print("\n程序被用户中断")
|
||
except Exception as e:
|
||
print(f"发生错误: {e}")
|
||
finally:
|
||
print("正在清理资源...")
|
||
Ctrl.quit()
|
||
rclpy.shutdown()
|
||
print("程序已退出")
|
||
sys.exit()
|
||
|
||
class Robot_Ctrl(object):
|
||
def __init__(self):
|
||
self.rec_thread = Thread(target=self.rec_responce)
|
||
self.send_thread = Thread(target=self.send_publish)
|
||
self.odo_thread = Thread(target=self.rec_responce_o)
|
||
self.lc_r = lcm.LCM("udpm://239.255.76.67:7670?ttl=255") # 控制指令
|
||
self.lc_s = lcm.LCM("udpm://239.255.76.67:7671?ttl=255") # 状态反馈
|
||
self.lc_o = lcm.LCM("udpm://239.255.76.67:7667?ttl=255") # 里程计
|
||
self.cmd_msg = robot_control_cmd_lcmt()
|
||
self.rec_msg = robot_control_response_lcmt()
|
||
self.odo_msg = localization_lcmt()
|
||
self.image_processor = ImageProcessor()
|
||
# DEBUG
|
||
# self.marker_runner = MarkerRunner(pass_flag=pass_marker)
|
||
self.send_lock = Lock()
|
||
self.delay_cnt = 0
|
||
self.mode_ok = 0
|
||
self.gait_ok = 0
|
||
self.runing = 1
|
||
# 新增: 校准相关变量
|
||
self.is_calibrated = False # 是否已完成校准
|
||
self.calibration_offset = [0, 0, 0] # 校准偏移量
|
||
|
||
# 新增: 基础消息
|
||
self.base_msg = BaseMsg(self, self.cmd_msg)
|
||
|
||
def run(self):
|
||
self.lc_r.subscribe("robot_control_response", self.msg_handler)
|
||
self.lc_o.subscribe("global_to_robot", self.msg_handler_o)
|
||
self.send_thread.start()
|
||
self.rec_thread.start()
|
||
self.odo_thread.start()
|
||
self.image_processor.run()
|
||
# self.marker_runner.run()
|
||
|
||
def msg_handler(self, channel, data):
|
||
self.rec_msg = robot_control_response_lcmt().decode(data)
|
||
if self.rec_msg.order_process_bar >= 95:
|
||
self.mode_ok = self.rec_msg.mode
|
||
else:
|
||
self.mode_ok = 0
|
||
|
||
def reset_offset(self):
|
||
self.is_calibrated = False
|
||
|
||
def msg_handler_o(self, channel, data):
|
||
self.odo_msg = localization_lcmt().decode(data)
|
||
# 如果尚未校准,记录第一帧数据作为校准基准
|
||
if not self.is_calibrated:
|
||
self.calibration_offset = self.odo_msg.xyz
|
||
self.is_calibrated = True
|
||
print(f"校准完成,基准值: {self.calibration_offset}")
|
||
# 将接收到的 odo 数据减去校准基准值
|
||
self.odo_msg.xyz = [
|
||
self.odo_msg.xyz[0] - self.calibration_offset[0],
|
||
self.odo_msg.xyz[1] - self.calibration_offset[1],
|
||
self.odo_msg.xyz[2] - self.calibration_offset[2]
|
||
]
|
||
# if self.odo_msg.timestamp % 100 == 0:
|
||
# print(self.odo_msg.xyz, self.odo_msg.rpy, self.odo_msg.vxyz, self.odo_msg.omegaBody, self.odo_msg.vBody)
|
||
|
||
def odo_reset(self):
|
||
self.calibration_offset = self.odo_msg.xyz
|
||
|
||
def rec_responce(self):
|
||
while self.runing:
|
||
self.lc_r.handle()
|
||
time.sleep(0.002)
|
||
|
||
def rec_responce_o(self):
|
||
while self.runing:
|
||
self.lc_o.handle()
|
||
time.sleep(0.002)
|
||
|
||
def Wait_finish(self, mode, gait_id):
|
||
count = 0
|
||
while self.runing and count < 2000: # 10s
|
||
if self.mode_ok == mode and self.gait_ok == gait_id:
|
||
return True
|
||
else:
|
||
time.sleep(0.005)
|
||
count += 1
|
||
print("Wait_finish timeout.")
|
||
return False
|
||
|
||
def send_publish(self):
|
||
while self.runing:
|
||
self.send_lock.acquire()
|
||
if self.delay_cnt > 20: # Heartbeat signal 10HZ
|
||
self.lc_s.publish("robot_control_cmd", self.cmd_msg.encode())
|
||
self.delay_cnt = 0
|
||
self.delay_cnt += 1
|
||
self.send_lock.release()
|
||
time.sleep(0.005)
|
||
|
||
def Send_cmd(self, msg):
|
||
self.send_lock.acquire()
|
||
self.delay_cnt = 50
|
||
self.cmd_msg = msg
|
||
self.cmd_msg.life_count %= 127
|
||
self.send_lock.release()
|
||
|
||
def place_marker(self, x, y, z, color, observe=False):
|
||
return None
|
||
return None
|
||
"""调用 MarkerRunner 放置标志物"""
|
||
if self.marker_runner is None or self.marker_runner.marker_client is None:
|
||
print("MarkerRunner 未初始化,无法放置标志物")
|
||
return None
|
||
try:
|
||
response = self.marker_runner.send_request(x, y, z, color, observe=observe)
|
||
print(f"放置标志物结果: {response.success}, 消息: {response.message}")
|
||
return response
|
||
except Exception as e:
|
||
print(f"放置标志物时发生异常: {e}")
|
||
return None
|
||
|
||
def quit(self):
|
||
self.runing = 0
|
||
self.rec_thread.join()
|
||
self.send_thread.join()
|
||
self.image_processor.destroy()
|
||
# 销毁 MarkerRunner
|
||
if hasattr(self, 'marker_runner') and self.marker_runner is not None:
|
||
try:
|
||
self.marker_runner.destroy()
|
||
except Exception as e:
|
||
print(f"MarkerRunner 销毁失败: {e}")
|
||
|
||
# Main function
|
||
if __name__ == '__main__':
|
||
main() |