215 lines
8.0 KiB
Python
215 lines
8.0 KiB
Python
![]() |
#!/usr/bin/env python3
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
|
|||
|
import rclpy
|
|||
|
from rclpy.node import Node
|
|||
|
from protocol.msg import HeadTofPayload, RearTofPayload, SingleTofPayload
|
|||
|
from std_msgs.msg import String
|
|||
|
from std_srvs.srv import Trigger
|
|||
|
import time
|
|||
|
import numpy as np
|
|||
|
import csv
|
|||
|
import os
|
|||
|
from datetime import datetime
|
|||
|
|
|||
|
class TofInteractive(Node):
|
|||
|
def __init__(self):
|
|||
|
super().__init__('tof_interactive_node')
|
|||
|
self.get_logger().info("TOF交互节点启动...")
|
|||
|
|
|||
|
# 当前状态:'loading' 配货中, 'unloading' 卸货中, 'transporting' 运输中
|
|||
|
self.current_state = 'loading' # 初始状态设为配货状态
|
|||
|
|
|||
|
# 触摸检测参数
|
|||
|
self.obstacle_distance = 0.3 # 检测障碍物的距离阈值 (米)
|
|||
|
self.obstacle_percentage = 0.8 # 检测到障碍物的点比例阈值
|
|||
|
self.sensitivity_s = 1.0 # 检测灵敏度 (秒)
|
|||
|
self.last_detection_time = time.time() - self.sensitivity_s
|
|||
|
|
|||
|
# 存储TOF数据
|
|||
|
self.head_tof_data = None
|
|||
|
self.rear_tof_data = None
|
|||
|
|
|||
|
# 订阅头部和尾部TOF数据
|
|||
|
self.head_sub = self.create_subscription(
|
|||
|
HeadTofPayload,
|
|||
|
'head_tof_payload',
|
|||
|
self.head_callback,
|
|||
|
10)
|
|||
|
|
|||
|
self.rear_sub = self.create_subscription(
|
|||
|
RearTofPayload,
|
|||
|
'rear_tof_payload',
|
|||
|
self.rear_callback,
|
|||
|
10)
|
|||
|
|
|||
|
# 发布状态消息
|
|||
|
self.status_pub = self.create_publisher(String, 'interactive_status', 10)
|
|||
|
|
|||
|
# 创建完成配货/卸货的服务
|
|||
|
self.complete_loading_srv = self.create_service(
|
|||
|
Trigger,
|
|||
|
'complete_loading',
|
|||
|
self.complete_loading_callback)
|
|||
|
|
|||
|
self.complete_unloading_srv = self.create_service(
|
|||
|
Trigger,
|
|||
|
'complete_unloading',
|
|||
|
self.complete_unloading_callback)
|
|||
|
|
|||
|
# 定时器用于状态检测
|
|||
|
self.timer = self.create_timer(0.1, self.detection_loop)
|
|||
|
|
|||
|
self.get_logger().info(f"初始状态: {self.current_state}")
|
|||
|
self.publish_status()
|
|||
|
|
|||
|
def head_callback(self, msg):
|
|||
|
"""头部TOF数据回调"""
|
|||
|
self.get_logger().info("head")
|
|||
|
self.head_tof_data = msg
|
|||
|
# self.get_logger().info(f"头部TOF数据接收: L={len(msg.left_head.data)}, R={len(msg.right_head.data)}")
|
|||
|
|
|||
|
def rear_callback(self, msg):
|
|||
|
"""尾部TOF数据回调"""
|
|||
|
self.get_logger().info("rear")
|
|||
|
self.rear_tof_data = msg
|
|||
|
# self.get_logger().info(f"尾部TOF数据接收: L={len(msg.left_rear.data)}, R={len(msg.right_rear.data)}")
|
|||
|
|
|||
|
def detect_obstacle(self, single_tof):
|
|||
|
"""检测单个TOF传感器是否有障碍物"""
|
|||
|
self.get_logger().info("obstacle")
|
|||
|
if not single_tof.data_available or len(single_tof.data) == 0:
|
|||
|
return False
|
|||
|
|
|||
|
obstacle_point = 0
|
|||
|
for ranging in single_tof.data:
|
|||
|
if 0.15 <= ranging <= 0.66 and ranging < self.obstacle_distance:
|
|||
|
obstacle_point += 1
|
|||
|
|
|||
|
obstacle_percentage = obstacle_point / len(single_tof.data)
|
|||
|
return obstacle_percentage >= self.obstacle_percentage
|
|||
|
|
|||
|
def detection_loop(self):
|
|||
|
"""检测循环 - 检查触摸事件"""
|
|||
|
self.get_logger().info("loop!")
|
|||
|
current_time = time.time()
|
|||
|
if current_time - self.last_detection_time < self.sensitivity_s:
|
|||
|
return
|
|||
|
self.get_logger().info("lllll!")
|
|||
|
if self.head_tof_data is None:
|
|||
|
return
|
|||
|
self.get_logger().info("ppppp!")
|
|||
|
# 检测左右头部TOF
|
|||
|
left_detected = self.detect_obstacle(self.head_tof_data.left_head)
|
|||
|
right_detected = self.detect_obstacle(self.head_tof_data.right_head)
|
|||
|
|
|||
|
# 检测到触摸事件
|
|||
|
if left_detected or right_detected:
|
|||
|
self.last_detection_time = current_time
|
|||
|
self.get_logger().info("检测到触摸下巴事件!")
|
|||
|
|
|||
|
# 记录TOF数据
|
|||
|
self.record_tof_data()
|
|||
|
|
|||
|
# 根据当前状态处理
|
|||
|
if self.current_state == 'loading':
|
|||
|
self.get_logger().info("配货完成!")
|
|||
|
self.current_state = 'transporting'
|
|||
|
self.play_sound("配货完成,开始运输")
|
|||
|
|
|||
|
elif self.current_state == 'unloading':
|
|||
|
self.get_logger().info("卸货完成!")
|
|||
|
self.current_state = 'loading' # 回到配货状态
|
|||
|
self.play_sound("卸货完成,准备下一次配货")
|
|||
|
|
|||
|
self.publish_status()
|
|||
|
|
|||
|
def record_tof_data(self):
|
|||
|
"""记录当前的TOF数据"""
|
|||
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|||
|
filename = f"tof_data_{timestamp}.csv"
|
|||
|
|
|||
|
with open(filename, 'w', newline='') as file:
|
|||
|
writer = csv.writer(file)
|
|||
|
writer.writerow(['sensor_position', 'index', 'distance', 'intensity'])
|
|||
|
|
|||
|
# 记录头部TOF数据
|
|||
|
if self.head_tof_data:
|
|||
|
self.write_single_tof(writer, 'left_head', self.head_tof_data.left_head)
|
|||
|
self.write_single_tof(writer, 'right_head', self.head_tof_data.right_head)
|
|||
|
|
|||
|
# 记录尾部TOF数据
|
|||
|
if self.rear_tof_data:
|
|||
|
self.write_single_tof(writer, 'left_rear', self.rear_tof_data.left_rear)
|
|||
|
self.write_single_tof(writer, 'right_rear', self.rear_tof_data.right_rear)
|
|||
|
|
|||
|
self.get_logger().info(f"TOF数据已保存到文件: {filename}")
|
|||
|
|
|||
|
def write_single_tof(self, writer, position_name, single_tof):
|
|||
|
"""将单个TOF传感器的数据写入CSV"""
|
|||
|
if not single_tof.data_available:
|
|||
|
return
|
|||
|
|
|||
|
# 如果intensity数据不可用,创建一个相同长度的零列表
|
|||
|
if len(single_tof.intensity) != len(single_tof.data):
|
|||
|
intensity_list = [0.0] * len(single_tof.data)
|
|||
|
else:
|
|||
|
intensity_list = single_tof.intensity
|
|||
|
|
|||
|
for i, (distance, intensity) in enumerate(zip(single_tof.data, intensity_list)):
|
|||
|
writer.writerow([position_name, i, distance, intensity])
|
|||
|
|
|||
|
def play_sound(self, text):
|
|||
|
"""播放提示音 (模拟)"""
|
|||
|
self.get_logger().info(f"播放声音: {text}")
|
|||
|
# 实际实现中应调用机器狗的音频播放接口
|
|||
|
# 例如: self.audio_pub.publish(text)
|
|||
|
|
|||
|
def publish_status(self):
|
|||
|
"""发布当前状态"""
|
|||
|
msg = String()
|
|||
|
msg.data = self.current_state
|
|||
|
self.status_pub.publish(msg)
|
|||
|
self.get_logger().info(f"状态更新: {self.current_state}")
|
|||
|
|
|||
|
def complete_loading_callback(self, request, response):
|
|||
|
"""完成配货服务回调"""
|
|||
|
if self.current_state == 'loading':
|
|||
|
self.get_logger().info("通过服务调用完成配货")
|
|||
|
self.current_state = 'transporting'
|
|||
|
self.publish_status()
|
|||
|
response.success = True
|
|||
|
response.message = "配货完成,开始运输"
|
|||
|
else:
|
|||
|
response.success = False
|
|||
|
response.message = f"当前状态 {self.current_state} 不允许配货"
|
|||
|
return response
|
|||
|
|
|||
|
def complete_unloading_callback(self, request, response):
|
|||
|
"""完成卸货服务回调"""
|
|||
|
if self.current_state == 'unloading':
|
|||
|
self.get_logger().info("通过服务调用完成卸货")
|
|||
|
self.current_state = 'loading'
|
|||
|
self.publish_status()
|
|||
|
response.success = True
|
|||
|
response.message = "卸货完成,准备下一次配货"
|
|||
|
else:
|
|||
|
response.success = False
|
|||
|
response.message = f"当前状态 {self.current_state} 不允许卸货"
|
|||
|
return response
|
|||
|
|
|||
|
|
|||
|
def main(args=None):
|
|||
|
rclpy.init(args=args)
|
|||
|
node = TofInteractive()
|
|||
|
try:
|
|||
|
rclpy.spin(node)
|
|||
|
except KeyboardInterrupt:
|
|||
|
pass
|
|||
|
finally:
|
|||
|
node.destroy_node()
|
|||
|
rclpy.shutdown()
|
|||
|
|
|||
|
|
|||
|
if __name__ == '__main__':
|
|||
|
main()
|