feat: 已添加win_ubuntu_bridge

This commit is contained in:
li-shihao-code
2026-03-06 13:17:36 +08:00
parent 0f0669a70a
commit 1ea480eccd
41 changed files with 2380 additions and 70 deletions
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: agv_calib_control.proto
# Protobuf Python Version: 6.31.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
6,
31,
1,
'',
'agv_calib_control.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x61gv_calib_control.proto\x12\x17\x61gv.calibration.control\"\x07\n\x05\x45mpty\"4\n\x10StandardResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"\x8b\x01\n\x0bModeRequest\x12>\n\x0btarget_mode\x18\x01 \x01(\x0e\x32).agv.calibration.control.ModeRequest.Mode\"<\n\x04Mode\x12\x0f\n\x0bNORMAL_MODE\x10\x00\x12\x12\n\x0eOPEN_LOOP_MODE\x10\x01\x12\x0f\n\x0bTUNING_MODE\x10\x02\"p\n\x0fOpenLoopRequest\x12\x16\n\x0eleft_motor_cmd\x18\x01 \x01(\x01\x12\x17\n\x0fright_motor_cmd\x18\x02 \x01(\x01\x12\x16\n\x0esteering_angle\x18\x03 \x01(\x01\x12\x14\n\x0c\x64uration_sec\x18\x04 \x01(\x01\"h\n\x0fTrajectoryPoint\x12\x0b\n\x03x_m\x18\x01 \x01(\x01\x12\x0b\n\x03y_m\x18\x02 \x01(\x01\x12\x0f\n\x07yaw_rad\x18\x03 \x01(\x01\x12\x17\n\x0ftarget_speed_ms\x18\x04 \x01(\x01\x12\x11\n\tcurvature\x18\x05 \x01(\x01\"a\n\x11TrajectoryRequest\x12\x14\n\x0ctest_case_id\x18\x01 \x01(\t\x12\x36\n\x04path\x18\x02 \x03(\x0b\x32(.agv.calibration.control.TrajectoryPoint\"G\n\x13StepResponseRequest\x12\x1a\n\x12target_velocity_ms\x18\x01 \x01(\x01\x12\x14\n\x0c\x64uration_sec\x18\x02 \x01(\x01\"\xf9\x05\n\rControlParams\x12$\n\x17wheel_radius_left_ratio\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12%\n\x18wheel_radius_right_ratio\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12$\n\x17\x65\x66\x66\x65\x63tive_track_width_m\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12%\n\x18steering_zero_offset_deg\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x1b\n\x0epid_kp_lateral\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x1b\n\x0epid_ki_lateral\x18\x06 \x01(\x01H\x05\x88\x01\x01\x12\x1b\n\x0epid_kd_lateral\x18\x07 \x01(\x01H\x06\x88\x01\x01\x12\x1b\n\x0epid_kp_heading\x18\x08 \x01(\x01H\x07\x88\x01\x01\x12\x1b\n\x0epid_ki_heading\x18\t \x01(\x01H\x08\x88\x01\x01\x12\x1b\n\x0epid_kd_heading\x18\n \x01(\x01H\t\x88\x01\x01\x12%\n\x18pure_pursuit_lookahead_m\x18\x0b \x01(\x01H\n\x88\x01\x01\x12!\n\x14mpc_weight_q_lateral\x18\x0c \x01(\x01H\x0b\x88\x01\x01\x12\"\n\x15mpc_weight_r_steering\x18\r \x01(\x01H\x0c\x88\x01\x01\x42\x1a\n\x18_wheel_radius_left_ratioB\x1b\n\x19_wheel_radius_right_ratioB\x1a\n\x18_effective_track_width_mB\x1b\n\x19_steering_zero_offset_degB\x11\n\x0f_pid_kp_lateralB\x11\n\x0f_pid_ki_lateralB\x11\n\x0f_pid_kd_lateralB\x11\n\x0f_pid_kp_headingB\x11\n\x0f_pid_ki_headingB\x11\n\x0f_pid_kd_headingB\x1b\n\x19_pure_pursuit_lookahead_mB\x17\n\x15_mpc_weight_q_lateralB\x18\n\x16_mpc_weight_r_steering\"\xad\x02\n\rTelemetryData\x12\x1d\n\x15hardware_timestamp_us\x18\x01 \x01(\x03\x12\x10\n\x08odom_x_m\x18\x02 \x01(\x01\x12\x10\n\x08odom_y_m\x18\x03 \x01(\x01\x12\x14\n\x0codom_yaw_rad\x18\x04 \x01(\x01\x12\x1e\n\x16\x66\x65\x65\x64\x62\x61\x63k_linear_vel_ms\x18\x05 \x01(\x01\x12!\n\x19\x66\x65\x65\x64\x62\x61\x63k_angular_vel_rads\x18\x06 \x01(\x01\x12\x1e\n\x16left_motor_current_amp\x18\x07 \x01(\x01\x12\x1f\n\x17right_motor_current_amp\x18\x08 \x01(\x01\x12\"\n\x1asteering_motor_current_amp\x18\t \x01(\x01\x12\x1b\n\x13\x63md_steering_output\x18\n \x01(\x01\x32\xd1\x06\n\x16\x41gvCalibControlService\x12\x61\n\x0eSetControlMode\x12$.agv.calibration.control.ModeRequest\x1a).agv.calibration.control.StandardResponse\x12Z\n\rEmergencyStop\x12\x1e.agv.calibration.control.Empty\x1a).agv.calibration.control.StandardResponse\x12i\n\x12\x45xecuteOpenLoopCmd\x12(.agv.calibration.control.OpenLoopRequest\x1a).agv.calibration.control.StandardResponse\x12m\n\x14\x46ollowTestTrajectory\x12*.agv.calibration.control.TrajectoryRequest\x1a).agv.calibration.control.StandardResponse\x12n\n\x13\x45xecuteStepResponse\x12,.agv.calibration.control.StepResponseRequest\x1a).agv.calibration.control.StandardResponse\x12k\n\x16InjectTuningParameters\x12&.agv.calibration.control.ControlParams\x1a).agv.calibration.control.StandardResponse\x12\x64\n\x17\x43ommitControlParameters\x12\x1e.agv.calibration.control.Empty\x1a).agv.calibration.control.StandardResponse\x12[\n\x0fStreamTelemetry\x12\x1e.agv.calibration.control.Empty\x1a&.agv.calibration.control.TelemetryData0\x01\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'agv_calib_control_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_EMPTY']._serialized_start=52
_globals['_EMPTY']._serialized_end=59
_globals['_STANDARDRESPONSE']._serialized_start=61
_globals['_STANDARDRESPONSE']._serialized_end=113
_globals['_MODEREQUEST']._serialized_start=116
_globals['_MODEREQUEST']._serialized_end=255
_globals['_MODEREQUEST_MODE']._serialized_start=195
_globals['_MODEREQUEST_MODE']._serialized_end=255
_globals['_OPENLOOPREQUEST']._serialized_start=257
_globals['_OPENLOOPREQUEST']._serialized_end=369
_globals['_TRAJECTORYPOINT']._serialized_start=371
_globals['_TRAJECTORYPOINT']._serialized_end=475
_globals['_TRAJECTORYREQUEST']._serialized_start=477
_globals['_TRAJECTORYREQUEST']._serialized_end=574
_globals['_STEPRESPONSEREQUEST']._serialized_start=576
_globals['_STEPRESPONSEREQUEST']._serialized_end=647
_globals['_CONTROLPARAMS']._serialized_start=650
_globals['_CONTROLPARAMS']._serialized_end=1411
_globals['_TELEMETRYDATA']._serialized_start=1414
_globals['_TELEMETRYDATA']._serialized_end=1715
_globals['_AGVCALIBCONTROLSERVICE']._serialized_start=1718
_globals['_AGVCALIBCONTROLSERVICE']._serialized_end=2567
# @@protoc_insertion_point(module_scope)
@@ -0,0 +1,444 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
import agv_calib_control_pb2 as agv__calib__control__pb2
GRPC_GENERATED_VERSION = '1.78.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in agv_calib_control_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
class AgvCalibControlServiceStub(object):
"""=========================================================
核心服务:AGV 运控大脑(PID/MPC)参数自动化寻优调教代理
[部署端 Server]Windows车端 (满血保留自身算法,负责执行闭环追踪与高频汇报)
[调用端 Client]:Linux标定服务器 (上帝视角,负责发轨迹、看误差、AI打分与发新参数)
=========================================================
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.SetControlMode = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/SetControlMode',
request_serializer=agv__calib__control__pb2.ModeRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.EmergencyStop = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/EmergencyStop',
request_serializer=agv__calib__control__pb2.Empty.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.ExecuteOpenLoopCmd = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/ExecuteOpenLoopCmd',
request_serializer=agv__calib__control__pb2.OpenLoopRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.FollowTestTrajectory = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/FollowTestTrajectory',
request_serializer=agv__calib__control__pb2.TrajectoryRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.ExecuteStepResponse = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/ExecuteStepResponse',
request_serializer=agv__calib__control__pb2.StepResponseRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.InjectTuningParameters = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/InjectTuningParameters',
request_serializer=agv__calib__control__pb2.ControlParams.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.CommitControlParameters = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/CommitControlParameters',
request_serializer=agv__calib__control__pb2.Empty.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.StreamTelemetry = channel.unary_stream(
'/agv.calibration.control.AgvCalibControlService/StreamTelemetry',
request_serializer=agv__calib__control__pb2.Empty.SerializeToString,
response_deserializer=agv__calib__control__pb2.TelemetryData.FromString,
_registered_method=True)
class AgvCalibControlServiceServicer(object):
"""=========================================================
核心服务:AGV 运控大脑(PID/MPC)参数自动化寻优调教代理
[部署端 Server]Windows车端 (满血保留自身算法,负责执行闭环追踪与高频汇报)
[调用端 Client]:Linux标定服务器 (上帝视角,负责发轨迹、看误差、AI打分与发新参数)
=========================================================
"""
def SetControlMode(self, request, context):
"""---------------------------------------------------------
第一步:权限接管与生命周期安全管控
---------------------------------------------------------
💻 [Linux 发送 -> Windows]:要求切断避障,但保留底层 PID/MPC 算法就绪
🚙 [Windows 返回 -> Linux]:回复模式切换成功,准备好接考题
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def EmergencyStop(self, request, context):
"""💻 [Linux 发送 -> Windows]:断网或飞车时的最高级别急停,无视一切直接刹车
🚙 [Windows 返回 -> Linux]:返回底层抱死结果
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def ExecuteOpenLoopCmd(self, request, context):
"""---------------------------------------------------------
第二步:运动考题下发 (开环排雷 / 闭环寻优 / 波峰对齐)
---------------------------------------------------------
【场景A: 纯物理开环备用】
💻 [Linux 发送 -> Windows]:要求切断算法盲跑,多用于摸底或辅助验证
🚙 [Windows 返回 -> Linux]:确认已按指定 RPM/PWM 运转
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def FollowTestTrajectory(self, request, context):
"""【场景B: 算法闭环调优】
💻 [Linux 发送 -> Windows]:下发一条由几百个点组成的测试轨迹(如 S型贝塞尔曲线)
🚙 [Windows 返回 -> Linux]:收到轨迹后,车端立刻使用它自带的 PID/MPC 算法努力贴合轨迹跑圈
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def ExecuteStepResponse(self, request, context):
"""【场景C: 波峰时序对齐】
💻 [Linux 发送 -> Windows]:下发极短促的阶跃加速指令,人为制造绝对速度波峰
🚙 [Windows 返回 -> Linux]:确认加速。(Linux 借此波峰算出网络的绝对 Time Offset)
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def InjectTuningParameters(self, request, context):
"""---------------------------------------------------------
第三步:运控参数 AI 寻优:动态热注入与最终固化
---------------------------------------------------------
💻 [Linux 发送 -> Windows]Linux 发现上一圈跑得差,AI算出了新的 PID/前瞻距离,要求立即热注入
🚙 [Windows 返回 -> Linux]:车端将新参数瞬间覆写进运行内存(不重启系统),随时准备用新参数重跑
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def CommitControlParameters(self, request, context):
"""💻 [Linux 发送 -> Windows]Linux 判定误差极小,调优结束,命令固化目前内存里的最高分参数
🚙 [Windows 返回 -> Linux]:车端将这组完美参数永久覆写进硬盘的 config.yaml 或系统注册表
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def StreamTelemetry(self, request, context):
"""---------------------------------------------------------
第四步:高频数字孪生体感上报 (50Hz)
---------------------------------------------------------
💻 [Linux 发送 -> Windows]:空包触发,命令车端开始疯狂推流
🚙 [Windows 持续流式返回 -> Linux]:以 50Hz 频率,持续上报自己的里程计坐标、速度和单调时间戳
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_AgvCalibControlServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
'SetControlMode': grpc.unary_unary_rpc_method_handler(
servicer.SetControlMode,
request_deserializer=agv__calib__control__pb2.ModeRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'EmergencyStop': grpc.unary_unary_rpc_method_handler(
servicer.EmergencyStop,
request_deserializer=agv__calib__control__pb2.Empty.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'ExecuteOpenLoopCmd': grpc.unary_unary_rpc_method_handler(
servicer.ExecuteOpenLoopCmd,
request_deserializer=agv__calib__control__pb2.OpenLoopRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'FollowTestTrajectory': grpc.unary_unary_rpc_method_handler(
servicer.FollowTestTrajectory,
request_deserializer=agv__calib__control__pb2.TrajectoryRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'ExecuteStepResponse': grpc.unary_unary_rpc_method_handler(
servicer.ExecuteStepResponse,
request_deserializer=agv__calib__control__pb2.StepResponseRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'InjectTuningParameters': grpc.unary_unary_rpc_method_handler(
servicer.InjectTuningParameters,
request_deserializer=agv__calib__control__pb2.ControlParams.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'CommitControlParameters': grpc.unary_unary_rpc_method_handler(
servicer.CommitControlParameters,
request_deserializer=agv__calib__control__pb2.Empty.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'StreamTelemetry': grpc.unary_stream_rpc_method_handler(
servicer.StreamTelemetry,
request_deserializer=agv__calib__control__pb2.Empty.FromString,
response_serializer=agv__calib__control__pb2.TelemetryData.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'agv.calibration.control.AgvCalibControlService', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('agv.calibration.control.AgvCalibControlService', rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
class AgvCalibControlService(object):
"""=========================================================
核心服务:AGV 运控大脑(PID/MPC)参数自动化寻优调教代理
[部署端 Server]Windows车端 (满血保留自身算法,负责执行闭环追踪与高频汇报)
[调用端 Client]:Linux标定服务器 (上帝视角,负责发轨迹、看误差、AI打分与发新参数)
=========================================================
"""
@staticmethod
def SetControlMode(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/SetControlMode',
agv__calib__control__pb2.ModeRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def EmergencyStop(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/EmergencyStop',
agv__calib__control__pb2.Empty.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def ExecuteOpenLoopCmd(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/ExecuteOpenLoopCmd',
agv__calib__control__pb2.OpenLoopRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def FollowTestTrajectory(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/FollowTestTrajectory',
agv__calib__control__pb2.TrajectoryRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def ExecuteStepResponse(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/ExecuteStepResponse',
agv__calib__control__pb2.StepResponseRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def InjectTuningParameters(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/InjectTuningParameters',
agv__calib__control__pb2.ControlParams.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def CommitControlParameters(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/CommitControlParameters',
agv__calib__control__pb2.Empty.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def StreamTelemetry(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
'/agv.calibration.control.AgvCalibControlService/StreamTelemetry',
agv__calib__control__pb2.Empty.SerializeToString,
agv__calib__control__pb2.TelemetryData.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@@ -5,74 +5,19 @@ if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# 1. 寻找 ROS 2 核心依赖
# 1. 寻找 ROS 2 和 行为树 核心依赖
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(behaviortree_cpp_v3 REQUIRED)
find_package(ament_index_cpp REQUIRED)
# ==========================================================
# 🚨 核心排雷修复:放弃 find_package,改用 PkgConfig 寻找 gRPC
# ==========================================================
find_package(Protobuf REQUIRED) # Protobuf 依然可以用自带的找
find_package(PkgConfig REQUIRED) # 引入 Linux 底层 PkgConfig 工具
# 强制让系统去底层寻找 grpc++ 库,并映射为虚拟目标 PkgConfig::GRPC
pkg_check_modules(GRPC REQUIRED IMPORTED_TARGET grpc++)
# ==========================================================
# 2. 编译主节点
add_executable(master_node src/master_node.cpp)
target_include_directories(master_node PUBLIC src)
ament_target_dependencies(master_node rclcpp behaviortree_cpp_v3 ament_index_cpp)
# 3. 设置 Proto 文件夹路径与输出路径
set(PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/proto")
file(GLOB PROTO_FILES "${PROTO_DIR}/*.proto")
set(PROTO_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/grpc_gen")
file(MAKE_DIRECTORY ${PROTO_OUT_DIR})
# 4. 自动生成 gRPC C++ 源码
find_program(GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin REQUIRED)
set(PROTO_SRCS "")
set(PROTO_HDRS "")
foreach(PROTO_FILE ${PROTO_FILES})
get_filename_component(FIL_WE ${PROTO_FILE} NAME_WE)
list(APPEND PROTO_SRCS "${PROTO_OUT_DIR}/${FIL_WE}.pb.cc" "${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.cc")
list(APPEND PROTO_HDRS "${PROTO_OUT_DIR}/${FIL_WE}.pb.h" "${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.h")
add_custom_command(
OUTPUT "${PROTO_OUT_DIR}/${FIL_WE}.pb.cc"
"${PROTO_OUT_DIR}/${FIL_WE}.pb.h"
"${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.cc"
"${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.h"
COMMAND protoc
# ==========================================================
# 🚨 核心修复:强行解锁 proto3 的 optional 实验性特性语法
# ==========================================================
ARGS --experimental_allow_proto3_optional # <--- 🚨 就是加了这一行!
--proto_path="${PROTO_DIR}"
--cpp_out="${PROTO_OUT_DIR}"
--grpc_out="${PROTO_OUT_DIR}"
--plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN_EXECUTABLE}"
"${PROTO_FILE}"
DEPENDS "${PROTO_FILE}"
)
endforeach()
# 5. 将所有生成的网络源码统一打包成独立的 C++ 共享库
add_library(${PROJECT_NAME}_grpc_lib SHARED ${PROTO_SRCS})
target_include_directories(${PROJECT_NAME}_grpc_lib PUBLIC "${PROTO_OUT_DIR}")
# 🚨 核心修复 2:这里必须改成链接 PkgConfig::GRPC
target_link_libraries(${PROJECT_NAME}_grpc_lib
PkgConfig::GRPC
protobuf::libprotobuf
)
# 6. 编译具体的标定业务主控节点
add_executable(brain_node src/brain_node.cpp)
ament_target_dependencies(brain_node rclcpp)
# 必须链接刚才生成的协议库
target_link_libraries(brain_node ${PROJECT_NAME}_grpc_lib)
# 7. ROS 2 安装规则
install(TARGETS brain_node DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY proto/ DESTINATION share/${PROJECT_NAME}/proto)
# 3. 安装规则 (极其重要:把剧本和程序装到系统目录,让 ROS 2 能找到它)
install(TARGETS master_node DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY behavior_trees/ DESTINATION share/${PROJECT_NAME}/behavior_trees)
ament_package()
@@ -0,0 +1,23 @@
<root main_tree_to_execute="MainCalibrationFlow">
<BehaviorTree ID="MainCalibrationFlow">
<Sequence name="全自动标定主干">
<Sequence name="Phase_1_Chassis">
<MockConnectAGV />
<MockCallChassisAlgo />
</Sequence>
<Sequence name="Phase_2_Control">
<MockTuneControl />
<MockCallControlAlgo />
</Sequence>
<Sequence name="Phase_3_Sensor">
<MockMoveAndCapture />
<MockCallSensorAlgo />
<MockCommitAllParams />
</Sequence>
</Sequence>
</BehaviorTree>
</root>
@@ -9,6 +9,10 @@
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>rclcpp</depend>
<depend>behaviortree_cpp_v3</depend>
<depend>ament_index_cpp</depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
@@ -0,0 +1,82 @@
#pragma once
#include <behaviortree_cpp_v3/action_node.h>
#include <iostream>
#include <thread>
#include <chrono>
// 1. 假装连接车端并夺权
class MockConnectAGV : public BT::SyncActionNode {
public:
MockConnectAGV(const std::string& name) : BT::SyncActionNode(name, {}) {}
BT::NodeStatus tick() override {
std::cout << "💻 [gRPC 对外] 📡 正在连接 Windows 车端... 夺权成功!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 假装网络耗时 0.5 秒
return BT::NodeStatus::SUCCESS;
}
};
// 2. 假装呼叫底盘算法
class MockCallChassisAlgo : public BT::SyncActionNode {
public:
MockCallChassisAlgo(const std::string& name) : BT::SyncActionNode(name, {}) {}
BT::NodeStatus tick() override {
std::cout << "🧮 [Action 对内] 🚙 丢给底盘算法团队... 算好了!左轮径 0.098m。" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(800));
return BT::NodeStatus::SUCCESS;
}
};
// 3. 假装控制车子跑S型曲线
class MockTuneControl : public BT::SyncActionNode {
public:
MockTuneControl(const std::string& name) : BT::SyncActionNode(name, {}) {}
BT::NodeStatus tick() override {
std::cout << "💻 [gRPC 对外] 📈 正在下发S型曲线测试考题... 车端已跑完。" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
return BT::NodeStatus::SUCCESS;
}
};
// 4. 假装呼叫AI寻优算法
class MockCallControlAlgo : public BT::SyncActionNode {
public:
MockCallControlAlgo(const std::string& name) : BT::SyncActionNode(name, {}) {}
BT::NodeStatus tick() override {
std::cout << "🧮 [Action 对内] 🧠 AI贝叶斯打分完毕... PID 最优参数已锁定!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(800));
return BT::NodeStatus::SUCCESS;
}
};
// 5. 假装走停拍并下载大文件
class MockMoveAndCapture : public BT::SyncActionNode {
public:
MockMoveAndCapture(const std::string& name) : BT::SyncActionNode(name, {}) {}
BT::NodeStatus tick() override {
std::cout << "💻 [gRPC 对外] 🛑 刹车静止...咔嚓!5MB大文件已下载至 /tmp/cam.png" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(800));
return BT::NodeStatus::SUCCESS;
}
};
// 6. 假装调用外参标定视觉算法
class MockCallSensorAlgo : public BT::SyncActionNode {
public:
MockCallSensorAlgo(const std::string& name) : BT::SyncActionNode(name, {}) {}
BT::NodeStatus tick() override {
std::cout << "🧮 [Action 对内] 📷 视觉团队正在算矩阵... 拿到 4x4 外参矩阵!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(800));
return BT::NodeStatus::SUCCESS;
}
};
// 7. 假装出厂固化
class MockCommitAllParams : public BT::SyncActionNode {
public:
MockCommitAllParams(const std::string& name) : BT::SyncActionNode(name, {}) {}
BT::NodeStatus tick() override {
std::cout << "💻 [gRPC 对外] 💾 正在把所有完美参数烧录进 AGV... 标定闭环,可以出厂!\n" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(300));
return BT::NodeStatus::SUCCESS;
}
};
@@ -0,0 +1,54 @@
#pragma once
#include <behaviortree_cpp_v3/action_node.h>
#include <grpcpp/grpcpp.h>
// 引入 CMake 自动生成的 C++ 网络契约头文件!
#include "agv_calib_control.grpc.pb.h"
using namespace agv::calibration::control;
// =========================================================
// 🌟 真实网络积木:连接车端并夺取控制权
// =========================================================
class ConnectAGVNode : public BT::SyncActionNode {
public:
// 注意:构造函数这里多了一个 const BT::NodeConfiguration& config 参数
ConnectAGVNode(const std::string& name, const BT::NodeConfiguration& config) : BT::SyncActionNode(name, config) {
// 1. 初始化时,拨号连接到车端 (因为我们要自己测,所以先连本机 127.0.0.1 端口)
channel_ = grpc::CreateChannel("127.0.0.1:50051", grpc::InsecureChannelCredentials());
stub_ = AgvCalibControlService::NewStub(channel_);
}
// 行为树必须的静态函数 (定义端口)
static BT::PortsList providedPorts() { return {}; }
BT::NodeStatus tick() override {
std::cout << "\n💻 [行为树真节点] 正在通过 gRPC 向车端发起夺权请求..." << std::endl;
// 2. 准备发送载荷:要求进入调优模式
ModeRequest request;
request.set_target_mode(ModeRequest::TUNING_MODE);
StandardResponse response;
grpc::ClientContext context;
// 🚨 架构师防线:设置 2 秒网络超时!如果网络断了,绝不让主线程死锁卡住!
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
// 3. 发射真正的网络脉冲!
grpc::Status status = stub_->SetControlMode(&context, request, &response);
// 4. 根据网络回复,决定行为树这根树枝是亮绿灯还是红灯
if (status.ok() && response.success()) {
std::cout << "✅ [网络通信成功] 车端回执: " << response.message() << std::endl;
return BT::NodeStatus::SUCCESS; // 绿灯,允许行为树执行下一步
} else {
std::cerr << "❌ [网络通信失败] 错误码: " << status.error_code()
<< " 详情: " << status.error_message() << std::endl;
return BT::NodeStatus::FAILURE; // 红灯,触发行为树重试或报警
}
}
private:
std::shared_ptr<grpc::Channel> channel_;
std::unique_ptr<AgvCalibControlService::Stub> stub_;
};
@@ -1,12 +1,44 @@
#include <rclcpp/rclcpp.hpp>
#include <behaviortree_cpp_v3/bt_factory.h>
#include <ament_index_cpp/get_package_share_directory.hpp>
// 引入刚才写的假节点
#include "bt_nodes/dummy_nodes.hpp"
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("master_node");
RCLCPP_INFO(node->get_logger(), "🚀 AGV 标定主控大脑编译成功,gRPC 契约已挂载!");
rclcpp::spin(node);
std::cout << "\n=========================================" << std::endl;
std::cout << "🚀 AGV 标定车间中央大脑 [空转测试版] 启动!" << std::endl;
std::cout << "=========================================\n" << std::endl;
BT::BehaviorTreeFactory factory;
// 1. 把 C++ 类注册到工厂,名字必须和 XML 里的一模一样!
factory.registerNodeType<MockConnectAGV>("MockConnectAGV");
factory.registerNodeType<MockCallChassisAlgo>("MockCallChassisAlgo");
factory.registerNodeType<MockTuneControl>("MockTuneControl");
factory.registerNodeType<MockCallControlAlgo>("MockCallControlAlgo");
factory.registerNodeType<MockMoveAndCapture>("MockMoveAndCapture");
factory.registerNodeType<MockCallSensorAlgo>("MockCallSensorAlgo");
factory.registerNodeType<MockCommitAllParams>("MockCommitAllParams");
try {
// 2. 动态获取 XML 剧本的绝对路径 (防止你运行程序时路径不对找不到文件)
std::string pkg_path = ament_index_cpp::get_package_share_directory("agv_calib_core");
std::string xml_file = pkg_path + "/behavior_trees/main_pipeline.xml";
auto tree = factory.createTreeFromFile(xml_file);
std::cout << "📜 行为树剧本加载完毕,开始全自动流水线...\n" << std::endl;
// 3. 开始执行总控流!
tree.tickRoot();
} catch (const std::exception& e) {
std::cerr << "❌ 加载 XML 失败: " << e.what() << std::endl;
}
std::cout << "🎉 全流程执行完毕,完美收工!\n" << std::endl;
rclcpp::shutdown();
return 0;
}
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: agv_calib_control.proto
# Protobuf Python Version: 6.31.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
6,
31,
1,
'',
'agv_calib_control.proto'
)
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x61gv_calib_control.proto\x12\x17\x61gv.calibration.control\"\x07\n\x05\x45mpty\"4\n\x10StandardResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x0f\n\x07message\x18\x02 \x01(\t\"\x8b\x01\n\x0bModeRequest\x12>\n\x0btarget_mode\x18\x01 \x01(\x0e\x32).agv.calibration.control.ModeRequest.Mode\"<\n\x04Mode\x12\x0f\n\x0bNORMAL_MODE\x10\x00\x12\x12\n\x0eOPEN_LOOP_MODE\x10\x01\x12\x0f\n\x0bTUNING_MODE\x10\x02\"p\n\x0fOpenLoopRequest\x12\x16\n\x0eleft_motor_cmd\x18\x01 \x01(\x01\x12\x17\n\x0fright_motor_cmd\x18\x02 \x01(\x01\x12\x16\n\x0esteering_angle\x18\x03 \x01(\x01\x12\x14\n\x0c\x64uration_sec\x18\x04 \x01(\x01\"h\n\x0fTrajectoryPoint\x12\x0b\n\x03x_m\x18\x01 \x01(\x01\x12\x0b\n\x03y_m\x18\x02 \x01(\x01\x12\x0f\n\x07yaw_rad\x18\x03 \x01(\x01\x12\x17\n\x0ftarget_speed_ms\x18\x04 \x01(\x01\x12\x11\n\tcurvature\x18\x05 \x01(\x01\"a\n\x11TrajectoryRequest\x12\x14\n\x0ctest_case_id\x18\x01 \x01(\t\x12\x36\n\x04path\x18\x02 \x03(\x0b\x32(.agv.calibration.control.TrajectoryPoint\"G\n\x13StepResponseRequest\x12\x1a\n\x12target_velocity_ms\x18\x01 \x01(\x01\x12\x14\n\x0c\x64uration_sec\x18\x02 \x01(\x01\"\xf9\x05\n\rControlParams\x12$\n\x17wheel_radius_left_ratio\x18\x01 \x01(\x01H\x00\x88\x01\x01\x12%\n\x18wheel_radius_right_ratio\x18\x02 \x01(\x01H\x01\x88\x01\x01\x12$\n\x17\x65\x66\x66\x65\x63tive_track_width_m\x18\x03 \x01(\x01H\x02\x88\x01\x01\x12%\n\x18steering_zero_offset_deg\x18\x04 \x01(\x01H\x03\x88\x01\x01\x12\x1b\n\x0epid_kp_lateral\x18\x05 \x01(\x01H\x04\x88\x01\x01\x12\x1b\n\x0epid_ki_lateral\x18\x06 \x01(\x01H\x05\x88\x01\x01\x12\x1b\n\x0epid_kd_lateral\x18\x07 \x01(\x01H\x06\x88\x01\x01\x12\x1b\n\x0epid_kp_heading\x18\x08 \x01(\x01H\x07\x88\x01\x01\x12\x1b\n\x0epid_ki_heading\x18\t \x01(\x01H\x08\x88\x01\x01\x12\x1b\n\x0epid_kd_heading\x18\n \x01(\x01H\t\x88\x01\x01\x12%\n\x18pure_pursuit_lookahead_m\x18\x0b \x01(\x01H\n\x88\x01\x01\x12!\n\x14mpc_weight_q_lateral\x18\x0c \x01(\x01H\x0b\x88\x01\x01\x12\"\n\x15mpc_weight_r_steering\x18\r \x01(\x01H\x0c\x88\x01\x01\x42\x1a\n\x18_wheel_radius_left_ratioB\x1b\n\x19_wheel_radius_right_ratioB\x1a\n\x18_effective_track_width_mB\x1b\n\x19_steering_zero_offset_degB\x11\n\x0f_pid_kp_lateralB\x11\n\x0f_pid_ki_lateralB\x11\n\x0f_pid_kd_lateralB\x11\n\x0f_pid_kp_headingB\x11\n\x0f_pid_ki_headingB\x11\n\x0f_pid_kd_headingB\x1b\n\x19_pure_pursuit_lookahead_mB\x17\n\x15_mpc_weight_q_lateralB\x18\n\x16_mpc_weight_r_steering\"\xad\x02\n\rTelemetryData\x12\x1d\n\x15hardware_timestamp_us\x18\x01 \x01(\x03\x12\x10\n\x08odom_x_m\x18\x02 \x01(\x01\x12\x10\n\x08odom_y_m\x18\x03 \x01(\x01\x12\x14\n\x0codom_yaw_rad\x18\x04 \x01(\x01\x12\x1e\n\x16\x66\x65\x65\x64\x62\x61\x63k_linear_vel_ms\x18\x05 \x01(\x01\x12!\n\x19\x66\x65\x65\x64\x62\x61\x63k_angular_vel_rads\x18\x06 \x01(\x01\x12\x1e\n\x16left_motor_current_amp\x18\x07 \x01(\x01\x12\x1f\n\x17right_motor_current_amp\x18\x08 \x01(\x01\x12\"\n\x1asteering_motor_current_amp\x18\t \x01(\x01\x12\x1b\n\x13\x63md_steering_output\x18\n \x01(\x01\x32\xd1\x06\n\x16\x41gvCalibControlService\x12\x61\n\x0eSetControlMode\x12$.agv.calibration.control.ModeRequest\x1a).agv.calibration.control.StandardResponse\x12Z\n\rEmergencyStop\x12\x1e.agv.calibration.control.Empty\x1a).agv.calibration.control.StandardResponse\x12i\n\x12\x45xecuteOpenLoopCmd\x12(.agv.calibration.control.OpenLoopRequest\x1a).agv.calibration.control.StandardResponse\x12m\n\x14\x46ollowTestTrajectory\x12*.agv.calibration.control.TrajectoryRequest\x1a).agv.calibration.control.StandardResponse\x12n\n\x13\x45xecuteStepResponse\x12,.agv.calibration.control.StepResponseRequest\x1a).agv.calibration.control.StandardResponse\x12k\n\x16InjectTuningParameters\x12&.agv.calibration.control.ControlParams\x1a).agv.calibration.control.StandardResponse\x12\x64\n\x17\x43ommitControlParameters\x12\x1e.agv.calibration.control.Empty\x1a).agv.calibration.control.StandardResponse\x12[\n\x0fStreamTelemetry\x12\x1e.agv.calibration.control.Empty\x1a&.agv.calibration.control.TelemetryData0\x01\x62\x06proto3')
_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'agv_calib_control_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_EMPTY']._serialized_start=52
_globals['_EMPTY']._serialized_end=59
_globals['_STANDARDRESPONSE']._serialized_start=61
_globals['_STANDARDRESPONSE']._serialized_end=113
_globals['_MODEREQUEST']._serialized_start=116
_globals['_MODEREQUEST']._serialized_end=255
_globals['_MODEREQUEST_MODE']._serialized_start=195
_globals['_MODEREQUEST_MODE']._serialized_end=255
_globals['_OPENLOOPREQUEST']._serialized_start=257
_globals['_OPENLOOPREQUEST']._serialized_end=369
_globals['_TRAJECTORYPOINT']._serialized_start=371
_globals['_TRAJECTORYPOINT']._serialized_end=475
_globals['_TRAJECTORYREQUEST']._serialized_start=477
_globals['_TRAJECTORYREQUEST']._serialized_end=574
_globals['_STEPRESPONSEREQUEST']._serialized_start=576
_globals['_STEPRESPONSEREQUEST']._serialized_end=647
_globals['_CONTROLPARAMS']._serialized_start=650
_globals['_CONTROLPARAMS']._serialized_end=1411
_globals['_TELEMETRYDATA']._serialized_start=1414
_globals['_TELEMETRYDATA']._serialized_end=1715
_globals['_AGVCALIBCONTROLSERVICE']._serialized_start=1718
_globals['_AGVCALIBCONTROLSERVICE']._serialized_end=2567
# @@protoc_insertion_point(module_scope)
@@ -0,0 +1,444 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import warnings
import agv_calib_control_pb2 as agv__calib__control__pb2
GRPC_GENERATED_VERSION = '1.78.0'
GRPC_VERSION = grpc.__version__
_version_not_supported = False
try:
from grpc._utilities import first_version_is_lower
_version_not_supported = first_version_is_lower(GRPC_VERSION, GRPC_GENERATED_VERSION)
except ImportError:
_version_not_supported = True
if _version_not_supported:
raise RuntimeError(
f'The grpc package installed is at version {GRPC_VERSION},'
+ ' but the generated code in agv_calib_control_pb2_grpc.py depends on'
+ f' grpcio>={GRPC_GENERATED_VERSION}.'
+ f' Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}'
+ f' or downgrade your generated code using grpcio-tools<={GRPC_VERSION}.'
)
class AgvCalibControlServiceStub(object):
"""=========================================================
核心服务:AGV 运控大脑(PID/MPC)参数自动化寻优调教代理
[部署端 Server]Windows车端 (满血保留自身算法,负责执行闭环追踪与高频汇报)
[调用端 Client]:Linux标定服务器 (上帝视角,负责发轨迹、看误差、AI打分与发新参数)
=========================================================
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.SetControlMode = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/SetControlMode',
request_serializer=agv__calib__control__pb2.ModeRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.EmergencyStop = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/EmergencyStop',
request_serializer=agv__calib__control__pb2.Empty.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.ExecuteOpenLoopCmd = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/ExecuteOpenLoopCmd',
request_serializer=agv__calib__control__pb2.OpenLoopRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.FollowTestTrajectory = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/FollowTestTrajectory',
request_serializer=agv__calib__control__pb2.TrajectoryRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.ExecuteStepResponse = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/ExecuteStepResponse',
request_serializer=agv__calib__control__pb2.StepResponseRequest.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.InjectTuningParameters = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/InjectTuningParameters',
request_serializer=agv__calib__control__pb2.ControlParams.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.CommitControlParameters = channel.unary_unary(
'/agv.calibration.control.AgvCalibControlService/CommitControlParameters',
request_serializer=agv__calib__control__pb2.Empty.SerializeToString,
response_deserializer=agv__calib__control__pb2.StandardResponse.FromString,
_registered_method=True)
self.StreamTelemetry = channel.unary_stream(
'/agv.calibration.control.AgvCalibControlService/StreamTelemetry',
request_serializer=agv__calib__control__pb2.Empty.SerializeToString,
response_deserializer=agv__calib__control__pb2.TelemetryData.FromString,
_registered_method=True)
class AgvCalibControlServiceServicer(object):
"""=========================================================
核心服务:AGV 运控大脑(PID/MPC)参数自动化寻优调教代理
[部署端 Server]Windows车端 (满血保留自身算法,负责执行闭环追踪与高频汇报)
[调用端 Client]:Linux标定服务器 (上帝视角,负责发轨迹、看误差、AI打分与发新参数)
=========================================================
"""
def SetControlMode(self, request, context):
"""---------------------------------------------------------
第一步:权限接管与生命周期安全管控
---------------------------------------------------------
💻 [Linux 发送 -> Windows]:要求切断避障,但保留底层 PID/MPC 算法就绪
🚙 [Windows 返回 -> Linux]:回复模式切换成功,准备好接考题
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def EmergencyStop(self, request, context):
"""💻 [Linux 发送 -> Windows]:断网或飞车时的最高级别急停,无视一切直接刹车
🚙 [Windows 返回 -> Linux]:返回底层抱死结果
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def ExecuteOpenLoopCmd(self, request, context):
"""---------------------------------------------------------
第二步:运动考题下发 (开环排雷 / 闭环寻优 / 波峰对齐)
---------------------------------------------------------
【场景A: 纯物理开环备用】
💻 [Linux 发送 -> Windows]:要求切断算法盲跑,多用于摸底或辅助验证
🚙 [Windows 返回 -> Linux]:确认已按指定 RPM/PWM 运转
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def FollowTestTrajectory(self, request, context):
"""【场景B: 算法闭环调优】
💻 [Linux 发送 -> Windows]:下发一条由几百个点组成的测试轨迹(如 S型贝塞尔曲线)
🚙 [Windows 返回 -> Linux]:收到轨迹后,车端立刻使用它自带的 PID/MPC 算法努力贴合轨迹跑圈
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def ExecuteStepResponse(self, request, context):
"""【场景C: 波峰时序对齐】
💻 [Linux 发送 -> Windows]:下发极短促的阶跃加速指令,人为制造绝对速度波峰
🚙 [Windows 返回 -> Linux]:确认加速。(Linux 借此波峰算出网络的绝对 Time Offset)
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def InjectTuningParameters(self, request, context):
"""---------------------------------------------------------
第三步:运控参数 AI 寻优:动态热注入与最终固化
---------------------------------------------------------
💻 [Linux 发送 -> Windows]Linux 发现上一圈跑得差,AI算出了新的 PID/前瞻距离,要求立即热注入
🚙 [Windows 返回 -> Linux]:车端将新参数瞬间覆写进运行内存(不重启系统),随时准备用新参数重跑
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def CommitControlParameters(self, request, context):
"""💻 [Linux 发送 -> Windows]Linux 判定误差极小,调优结束,命令固化目前内存里的最高分参数
🚙 [Windows 返回 -> Linux]:车端将这组完美参数永久覆写进硬盘的 config.yaml 或系统注册表
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def StreamTelemetry(self, request, context):
"""---------------------------------------------------------
第四步:高频数字孪生体感上报 (50Hz)
---------------------------------------------------------
💻 [Linux 发送 -> Windows]:空包触发,命令车端开始疯狂推流
🚙 [Windows 持续流式返回 -> Linux]:以 50Hz 频率,持续上报自己的里程计坐标、速度和单调时间戳
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_AgvCalibControlServiceServicer_to_server(servicer, server):
rpc_method_handlers = {
'SetControlMode': grpc.unary_unary_rpc_method_handler(
servicer.SetControlMode,
request_deserializer=agv__calib__control__pb2.ModeRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'EmergencyStop': grpc.unary_unary_rpc_method_handler(
servicer.EmergencyStop,
request_deserializer=agv__calib__control__pb2.Empty.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'ExecuteOpenLoopCmd': grpc.unary_unary_rpc_method_handler(
servicer.ExecuteOpenLoopCmd,
request_deserializer=agv__calib__control__pb2.OpenLoopRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'FollowTestTrajectory': grpc.unary_unary_rpc_method_handler(
servicer.FollowTestTrajectory,
request_deserializer=agv__calib__control__pb2.TrajectoryRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'ExecuteStepResponse': grpc.unary_unary_rpc_method_handler(
servicer.ExecuteStepResponse,
request_deserializer=agv__calib__control__pb2.StepResponseRequest.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'InjectTuningParameters': grpc.unary_unary_rpc_method_handler(
servicer.InjectTuningParameters,
request_deserializer=agv__calib__control__pb2.ControlParams.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'CommitControlParameters': grpc.unary_unary_rpc_method_handler(
servicer.CommitControlParameters,
request_deserializer=agv__calib__control__pb2.Empty.FromString,
response_serializer=agv__calib__control__pb2.StandardResponse.SerializeToString,
),
'StreamTelemetry': grpc.unary_stream_rpc_method_handler(
servicer.StreamTelemetry,
request_deserializer=agv__calib__control__pb2.Empty.FromString,
response_serializer=agv__calib__control__pb2.TelemetryData.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'agv.calibration.control.AgvCalibControlService', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
server.add_registered_method_handlers('agv.calibration.control.AgvCalibControlService', rpc_method_handlers)
# This class is part of an EXPERIMENTAL API.
class AgvCalibControlService(object):
"""=========================================================
核心服务:AGV 运控大脑(PID/MPC)参数自动化寻优调教代理
[部署端 Server]Windows车端 (满血保留自身算法,负责执行闭环追踪与高频汇报)
[调用端 Client]:Linux标定服务器 (上帝视角,负责发轨迹、看误差、AI打分与发新参数)
=========================================================
"""
@staticmethod
def SetControlMode(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/SetControlMode',
agv__calib__control__pb2.ModeRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def EmergencyStop(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/EmergencyStop',
agv__calib__control__pb2.Empty.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def ExecuteOpenLoopCmd(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/ExecuteOpenLoopCmd',
agv__calib__control__pb2.OpenLoopRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def FollowTestTrajectory(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/FollowTestTrajectory',
agv__calib__control__pb2.TrajectoryRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def ExecuteStepResponse(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/ExecuteStepResponse',
agv__calib__control__pb2.StepResponseRequest.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def InjectTuningParameters(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/InjectTuningParameters',
agv__calib__control__pb2.ControlParams.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def CommitControlParameters(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(
request,
target,
'/agv.calibration.control.AgvCalibControlService/CommitControlParameters',
agv__calib__control__pb2.Empty.SerializeToString,
agv__calib__control__pb2.StandardResponse.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@staticmethod
def StreamTelemetry(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_stream(
request,
target,
'/agv.calibration.control.AgvCalibControlService/StreamTelemetry',
agv__calib__control__pb2.Empty.SerializeToString,
agv__calib__control__pb2.TelemetryData.FromString,
options,
channel_credentials,
insecure,
call_credentials,
compression,
wait_for_ready,
timeout,
metadata,
_registered_method=True)
@@ -0,0 +1,26 @@
import grpc
from concurrent import futures
import time
import agv_calib_control_pb2 as pb2
import agv_calib_control_pb2_grpc as pb2_grpc
# 扮演 Windows 车端的角色
class FakeAgvServer(pb2_grpc.AgvCalibControlServiceServicer):
def SetControlMode(self, request, context):
print(f"\n[🚙 Windows 假车端] 收到 Linux 夺权指令! 目标模式: {request.target_mode}")
time.sleep(0.5) # 假装底层继电器切换花了点时间
print("[🚙 Windows 假车端] 避障已切断,乖乖交出控制权!")
# 返回成功回执给 Linux
return pb2.StandardResponse(success=True, message="Windows: 已交出底盘控制权!")
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
pb2_grpc.add_AgvCalibControlServiceServicer_to_server(FakeAgvServer(), server)
server.add_insecure_port('[::]:50051')
print("🚀 [Windows 假车端] 已启动,正在监听 50051 端口,等待 Linux 大脑连接...")
server.start()
server.wait_for_termination()
if __name__ == '__main__':
serve()
@@ -0,0 +1,159 @@
cmake_minimum_required(VERSION 3.8)
project(win_ubuntu_bridge)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# ==========================================================
# 1. 寻找 ROS 2 核心库与消息生成器
# ==========================================================
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(std_msgs REQUIRED)
find_package(std_srvs REQUIRED)
find_package(action_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(behaviortree_cpp_v3 REQUIRED)
# ==========================================================
# 2. 编译 ROS 2 自定义接口 (Msg / Srv / Action)
# ==========================================================
# 🚨 已经严格按照你截图中的目录结构为你写好
set(interface_files
"msg/HardwareState.msg"
"msg/ControlTelemetry.msg"
"msg/TrajectoryPoint.msg"
"msg/CameraIntrinsic.msg"
"msg/SensorExtrinsic.msg"
# chassis
"srv/CommitKinematics.srv"
"srv/ExecuteRawDrive.srv"
"srv/ExecuteRawSteer.srv"
"srv/SetDiagnosticMode.srv"
# control
"srv/ExecuteOpenLoopCmd.srv"
"srv/ExecuteStepResponse.srv"
"srv/FollowTestTrajectory.srv"
"srv/InjectTuningParams.srv"
"srv/SetControlMode.srv"
# sensor
"srv/CommitCalibrationResults.srv"
"srv/MoveToObservationPose.srv"
"srv/TriggerSyncCapture.srv"
"action/DownloadSensorData.action"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${interface_files}
DEPENDENCIES std_msgs std_srvs action_msgs
)
# ==========================================================
# 3. 寻找系统底层的 gRPC 并编译 .proto 文件
# ==========================================================
find_package(Protobuf REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GRPC REQUIRED IMPORTED_TARGET grpc++)
set(PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/proto")
file(GLOB PROTO_FILES "${PROTO_DIR}/*.proto")
set(PROTO_OUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/grpc_gen")
file(MAKE_DIRECTORY ${PROTO_OUT_DIR})
find_program(GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin REQUIRED)
set(PROTO_SRCS "")
set(PROTO_HDRS "")
foreach(PROTO_FILE ${PROTO_FILES})
get_filename_component(FIL_WE ${PROTO_FILE} NAME_WE)
list(APPEND PROTO_SRCS "${PROTO_OUT_DIR}/${FIL_WE}.pb.cc" "${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.cc")
list(APPEND PROTO_HDRS "${PROTO_OUT_DIR}/${FIL_WE}.pb.h" "${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.h")
add_custom_command(
OUTPUT "${PROTO_OUT_DIR}/${FIL_WE}.pb.cc" "${PROTO_OUT_DIR}/${FIL_WE}.pb.h"
"${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.cc" "${PROTO_OUT_DIR}/${FIL_WE}.grpc.pb.h"
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
"--experimental_allow_proto3_optional"
"--proto_path=${PROTO_DIR}"
"--cpp_out=${PROTO_OUT_DIR}"
"--grpc_out=${PROTO_OUT_DIR}"
"--plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN_EXECUTABLE}"
"${PROTO_FILE}"
DEPENDS "${PROTO_FILE}"
COMMENT "🚀 正在翻译 ${FIL_WE}.proto 到 C++ 源码..."
)
endforeach()
# 打包 gRPC 协议库
add_library(${PROJECT_NAME}_grpc_lib SHARED ${PROTO_SRCS})
target_include_directories(${PROJECT_NAME}_grpc_lib PUBLIC "${PROTO_OUT_DIR}")
target_link_libraries(${PROJECT_NAME}_grpc_lib PkgConfig::GRPC protobuf::libprotobuf)
# ==========================================================
# 4. 编译三大 Gateway 组件 (ROS 2 Plugins)
# ==========================================================
# 💡 定义一个宏来简化三个网关的编译代码,让 CMake 极度整洁!
macro(build_gateway_component target_name source_file plugin_class)
add_library(${target_name} SHARED ${source_file})
target_include_directories(${target_name} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${PROTO_OUT_DIR}>"
)
ament_target_dependencies(${target_name} rclcpp rclcpp_components rclcpp_action std_msgs std_srvs action_msgs)
target_link_libraries(${target_name} ${PROJECT_NAME}_grpc_lib)
# 🚨 极度关键:因为接口在同一个包里,必须让 C++ 代码等待 Msg 编译完成后才能编译!
rosidl_target_interfaces(${target_name} ${PROJECT_NAME} "rosidl_typesupport_cpp")
rclcpp_components_register_node(${target_name}
PLUGIN "${plugin_class}"
EXECUTABLE ${target_name}_exe
)
endmacro()
# ⚠️ 注意:基于你截图的拼写 gatways。如果你改名了,记得同步改掉这里的路径!
build_gateway_component(chassis_gateway_node "gateways/chassis_gateway_node.cpp" "win_ubuntu_bridge::ChassisGatewayNode")
build_gateway_component(control_gateway_node "gateways/control_gateway_node.cpp" "win_ubuntu_bridge::ControlGatewayNode")
build_gateway_component(sensor_gateway_node "gateways/sensor_gateway_node.cpp" "win_ubuntu_bridge::SensorGatewayNode")
# ==========================================================
# 5. 编译其他主节点 (master_node 和 brain_node)
# ==========================================================
add_executable(master_node src/master_node.cpp)
target_include_directories(master_node PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>" "${PROTO_OUT_DIR}")
ament_target_dependencies(master_node rclcpp behaviortree_cpp_v3)
target_link_libraries(master_node ${PROJECT_NAME}_grpc_lib)
rosidl_target_interfaces(master_node ${PROJECT_NAME} "rosidl_typesupport_cpp")
add_executable(brain_node src/brain_node.cpp)
target_include_directories(brain_node PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>" "${PROTO_OUT_DIR}")
ament_target_dependencies(brain_node rclcpp)
target_link_libraries(brain_node ${PROJECT_NAME}_grpc_lib)
rosidl_target_interfaces(brain_node ${PROJECT_NAME} "rosidl_typesupport_cpp")
# ==========================================================
# 6. 安装规则 (Install)
# ==========================================================
install(TARGETS
${PROJECT_NAME}_grpc_lib
chassis_gateway_node chassis_gateway_node_exe
control_gateway_node control_gateway_node_exe
sensor_gateway_node sensor_gateway_node_exe
master_node brain_node
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib/${PROJECT_NAME}
RUNTIME DESTINATION lib/${PROJECT_NAME}
)
install(DIRECTORY proto/ DESTINATION share/${PROJECT_NAME}/proto)
# 若有 launch 或 behavior_trees 文件,随时解开下面这行的注释
# install(DIRECTORY launch/ DESTINATION share/${PROJECT_NAME}/launch)
ament_package()
@@ -0,0 +1,15 @@
# === [Goal] 行为树给网关下发的下载任务 ===
int64 capture_timestamp_us # 刚才拿到的取件码
string sensor_id # 例如 "cam_front"
uint8 DATA_TYPE_IMAGE = 0
uint8 DATA_TYPE_POINTCLOUD = 1
uint8 data_type # 告诉网关下图片还是下点云
string save_directory # 保存的 Ubuntu 目录,如 "/tmp/calib_data"
---
# === [Result] 网关下完后返回给行为树的结果 ===
bool success
string saved_file_path # 🚨 终极目的:返回存好的绝对路径 (如 /tmp/calib_data/cam_front_167888.png)
string error_message
---
# === [Feedback] 网关实时汇报的下载进度 ===
uint64 downloaded_bytes # 已下载的字节数 (供行为树监控是否卡死)
@@ -0,0 +1,227 @@
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_components/register_node_macro.hpp>
// 🚨 引入自定义的 ROS 2 接口头文件 (由 win_ubuntu_bridge 包生成)
#include "win_ubuntu_bridge/msg/hardware_state.hpp"
#include "win_ubuntu_bridge/srv/set_diagnostic_mode.hpp"
#include "win_ubuntu_bridge/srv/execute_raw_drive.hpp"
#include "win_ubuntu_bridge/srv/execute_raw_steer.hpp"
#include "win_ubuntu_bridge/srv/commit_kinematics.hpp"
#include <std_srvs/srv/trigger.hpp> // 紧急刹车可以直接用标准库的 Trigger
// 引入 gRPC 自动生成的契约头文件
#include <grpcpp/grpcpp.h>
#include "agv_calib_chassis.grpc.pb.h"
#include <thread>
#include <atomic>
using namespace agv::calibration::chassis;
namespace win_ubuntu_bridge {
class ChassisGatewayNode : public rclcpp::Node {
public:
explicit ChassisGatewayNode(const rclcpp::NodeOptions & options)
: Node("chassis_gateway", options), stream_running_(false) {
RCLCPP_INFO(this->get_logger(), "🔌 底盘 gRPC 网关插件启动中... 正在挂载 6 大完整接口!");
// 1. 拨号连接 Windows 车端
std::string target_ip = this->declare_parameter("agv_ip", "127.0.0.1:50051");
channel_ = grpc::CreateChannel(target_ip, grpc::InsecureChannelCredentials());
stub_ = AgvCalibChassisService::NewStub(channel_);
// ==========================================================
// 2. 注册 5 个 ROS 2 服务 (拦截内部行为树的请求,翻译给车端)
// ==========================================================
// 接口 1:夺权
srv_set_mode_ = this->create_service<win_ubuntu_bridge::srv::SetDiagnosticMode>(
"~/set_diagnostic_mode", std::bind(&ChassisGatewayNode::cb_set_mode, this, std::placeholders::_1, std::placeholders::_2));
// 接口 2:最高优急停 (无参数,用 Trigger)
srv_estop_ = this->create_service<std_srvs::srv::Trigger>(
"~/hardware_emergency_brake", std::bind(&ChassisGatewayNode::cb_emergency_brake, this, std::placeholders::_1, std::placeholders::_2));
// 接口 3:开环直行 (测轮径)
srv_raw_drive_ = this->create_service<win_ubuntu_bridge::srv::ExecuteRawDrive>(
"~/execute_raw_drive", std::bind(&ChassisGatewayNode::cb_raw_drive, this, std::placeholders::_1, std::placeholders::_2));
// 接口 4:开环转向 (测死区/零位)
srv_raw_steer_ = this->create_service<win_ubuntu_bridge::srv::ExecuteRawSteer>(
"~/execute_raw_steer", std::bind(&ChassisGatewayNode::cb_raw_steer, this, std::placeholders::_1, std::placeholders::_2));
// 接口 5:物理参数定稿固化
srv_commit_kinematics_ = this->create_service<win_ubuntu_bridge::srv::CommitKinematics>(
"~/commit_kinematics", std::bind(&ChassisGatewayNode::cb_commit_kinematics, this, std::placeholders::_1, std::placeholders::_2));
// ==========================================================
// 3. 注册 1 个 ROS 2 话题 (发布从车端抽上来的 50Hz 流数据)
// ==========================================================
pub_telemetry_ = this->create_publisher<win_ubuntu_bridge::msg::HardwareState>("~/hardware_telemetry", 50);
// 4. 启动异步抽水机线程
stream_running_ = true;
stream_thread_ = std::thread(&ChassisGatewayNode::grpc_stream_to_ros2_topic, this);
}
~ChassisGatewayNode() {
stream_running_ = false;
if (stream_context_) stream_context_->TryCancel(); // 唤醒阻塞的 gRPC
if (stream_thread_.joinable()) stream_thread_.join();
}
private:
std::shared_ptr<grpc::Channel> channel_;
std::unique_ptr<AgvCalibChassisService::Stub> stub_;
std::unique_ptr<grpc::ClientContext> stream_context_;
// ROS 2 接口句柄
rclcpp::Service<win_ubuntu_bridge::srv::SetDiagnosticMode>::SharedPtr srv_set_mode_;
rclcpp::Service<std_srvs::srv::Trigger>::SharedPtr srv_estop_;
rclcpp::Service<win_ubuntu_bridge::srv::ExecuteRawDrive>::SharedPtr srv_raw_drive_;
rclcpp::Service<win_ubuntu_bridge::srv::ExecuteRawSteer>::SharedPtr srv_raw_steer_;
rclcpp::Service<win_ubuntu_bridge::srv::CommitKinematics>::SharedPtr srv_commit_kinematics_;
rclcpp::Publisher<win_ubuntu_bridge::msg::HardwareState>::SharedPtr pub_telemetry_;
std::thread stream_thread_;
std::atomic<bool> stream_running_;
// ----------------------------------------------------------------------
// ⬇️ 以下全是完美的 1:1 翻译逻辑 (ROS Request -> gRPC -> ROS Response) ⬇️
// ----------------------------------------------------------------------
// [翻译] 接口 1: SetDiagnosticMode
void cb_set_mode(const std::shared_ptr<win_ubuntu_bridge::srv::SetDiagnosticMode::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::SetDiagnosticMode::Response> res) {
DiagnosticModeRequest grpc_req;
grpc_req.set_target_mode(req->target_mode == 1 ? DiagnosticModeRequest::DIRECT_RAW_DRIVE : DiagnosticModeRequest::NORMAL_KINEMATICS);
StandardResponse grpc_reply;
grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
grpc::Status status = stub_->SetDiagnosticMode(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success();
res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// [翻译] 接口 2: HardwareEmergencyBrake
void cb_emergency_brake(const std::shared_ptr<std_srvs::srv::Trigger::Request> req,
std::shared_ptr<std_srvs::srv::Trigger::Response> res) {
(void)req; // 忽略未使用变量
Empty grpc_req; StandardResponse grpc_reply;
grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(1)); // 急停超时设为1秒
grpc::Status status = stub_->HardwareEmergencyBrake(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success();
res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// [翻译] 接口 3: ExecuteRawDriveCommand
void cb_raw_drive(const std::shared_ptr<win_ubuntu_bridge::srv::ExecuteRawDrive::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::ExecuteRawDrive::Response> res) {
RawDriveRequest grpc_req;
grpc_req.set_test_case_id(req->test_case_id);
grpc_req.set_fl_motor_rpm(req->fl_motor_rpm);
grpc_req.set_fr_motor_rpm(req->fr_motor_rpm);
grpc_req.set_rl_motor_rpm(req->rl_motor_rpm);
grpc_req.set_rr_motor_rpm(req->rr_motor_rpm);
grpc_req.set_duration_sec(req->duration_sec);
StandardResponse grpc_reply;
grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
grpc::Status status = stub_->ExecuteRawDriveCommand(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success();
res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// [翻译] 接口 4: ExecuteRawSteerCommand
void cb_raw_steer(const std::shared_ptr<win_ubuntu_bridge::srv::ExecuteRawSteer::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::ExecuteRawSteer::Response> res) {
RawSteerRequest grpc_req;
grpc_req.set_test_case_id(req->test_case_id);
grpc_req.set_front_steer_angle_deg(req->front_steer_angle_deg);
grpc_req.set_rear_steer_angle_deg(req->rear_steer_angle_deg);
grpc_req.set_duration_sec(req->duration_sec);
// 处理 proto 的 optional 字段 (依靠 ROS 2 传来的 bool 判断)
if (req->has_sweep) {
grpc_req.set_sweep_amplitude_deg(req->sweep_amplitude_deg);
grpc_req.set_sweep_frequency_hz(req->sweep_frequency_hz);
}
StandardResponse grpc_reply;
grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
grpc::Status status = stub_->ExecuteRawSteerCommand(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success();
res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// [翻译] 接口 5: CommitKinematicParameters
void cb_commit_kinematics(const std::shared_ptr<win_ubuntu_bridge::srv::CommitKinematics::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::CommitKinematics::Response> res) {
KinematicParams grpc_req;
// ROS 2 请求中依靠 bool 标志位来判断是否 set 进 grpc (实现真正的 optional)
if (req->has_wheel_radius_fl) grpc_req.set_wheel_radius_fl_m(req->wheel_radius_fl_m);
if (req->has_wheel_radius_fr) grpc_req.set_wheel_radius_fr_m(req->wheel_radius_fr_m);
if (req->has_wheel_radius_rl) grpc_req.set_wheel_radius_rl_m(req->wheel_radius_rl_m);
if (req->has_wheel_radius_rr) grpc_req.set_wheel_radius_rr_m(req->wheel_radius_rr_m);
if (req->has_steer_zero_offset_front) grpc_req.set_steer_zero_offset_front_deg(req->steer_zero_offset_front_deg);
if (req->has_steer_zero_offset_rear) grpc_req.set_steer_zero_offset_rear_deg(req->steer_zero_offset_rear_deg);
if (req->has_effective_track_width) grpc_req.set_effective_track_width_m(req->effective_track_width_m);
if (req->has_effective_wheel_base) grpc_req.set_effective_wheel_base_m(req->effective_wheel_base_m);
if (req->has_icr_offset_x) grpc_req.set_icr_offset_x_m(req->icr_offset_x_m);
if (req->has_icr_offset_y) grpc_req.set_icr_offset_y_m(req->icr_offset_y_m);
StandardResponse grpc_reply;
grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(3));
grpc::Status status = stub_->CommitKinematicParameters(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success();
res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// ----------------------------------------------------------------------
// 🌊 接口 6 [异步水龙头]: 后台读取 50Hz 流并发布 ROS 2 Topic
// ----------------------------------------------------------------------
void grpc_stream_to_ros2_topic() {
while (stream_running_ && rclcpp::ok()) {
stream_context_ = std::make_unique<grpc::ClientContext>();
Empty grpc_req;
auto reader = stub_->StreamHardwareTelemetry(stream_context_.get(), grpc_req);
win_ubuntu_bridge::msg::HardwareState ros_msg;
HardwareState grpc_state;
RCLCPP_INFO(this->get_logger(), "🌊 车端底盘 50Hz 裸数据流连接已建立,开始广播 ROS 2 Topic...");
// 只要网不断,Read() 就会以 50Hz 的频率一直被触发
while (stream_running_ && reader->Read(&grpc_state) && rclcpp::ok()) {
// 完美 1:1 结构体拷贝
ros_msg.hardware_timestamp_us = grpc_state.hardware_timestamp_us();
ros_msg.encoder_ticks_fl = grpc_state.encoder_ticks_fl();
ros_msg.encoder_ticks_fr = grpc_state.encoder_ticks_fr();
ros_msg.encoder_ticks_rl = grpc_state.encoder_ticks_rl();
ros_msg.encoder_ticks_rr = grpc_state.encoder_ticks_rr();
ros_msg.actual_steer_angle_front_deg = grpc_state.actual_steer_angle_front_deg();
ros_msg.actual_steer_angle_rear_deg = grpc_state.actual_steer_angle_rear_deg();
ros_msg.current_fl_amp = grpc_state.current_fl_amp();
ros_msg.current_fr_amp = grpc_state.current_fr_amp();
ros_msg.current_rl_amp = grpc_state.current_rl_amp();
ros_msg.current_rr_amp = grpc_state.current_rr_amp();
ros_msg.current_steer_front_amp = grpc_state.current_steer_front_amp();
ros_msg.driver_error_code = grpc_state.driver_error_code();
// 发布到 ROS 2 网络中!
pub_telemetry_->publish(ros_msg);
}
// 如果走到这里,说明网络断了或者发生了异常,稍等 1 秒后自动重试
if (stream_running_) {
RCLCPP_WARN(this->get_logger(), "⚠️ gRPC 到底端的流意外中断!尝试重连...");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
}
};
} // namespace agv_calib_core
RCLCPP_COMPONENTS_REGISTER_NODE(win_ubuntu_bridge::ChassisGatewayNode)
@@ -0,0 +1,235 @@
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_components/register_node_macro.hpp>
// 引入自定义的 ROS 2 接口头文件
#include "win_ubuntu_bridge/msg/trajectory_point.hpp"
#include "win_ubuntu_bridge/msg/control_telemetry.hpp"
#include "win_ubuntu_bridge/srv/set_control_mode.hpp"
#include "win_ubuntu_bridge/srv/execute_open_loop_cmd.hpp"
#include "win_ubuntu_bridge/srv/follow_test_trajectory.hpp"
#include "win_ubuntu_bridge/srv/execute_step_response.hpp"
#include "win_ubuntu_bridge/srv/inject_tuning_params.hpp"
#include <std_srvs/srv/trigger.hpp>
// 引入 gRPC 网络契约
#include <grpcpp/grpcpp.h>
#include "agv_calib_control.grpc.pb.h"
#include <thread>
#include <atomic>
using namespace agv::calibration::control;
namespace win_ubuntu_bridge {
class ControlGatewayNode : public rclcpp::Node {
public:
explicit ControlGatewayNode(const rclcpp::NodeOptions & options)
: Node("control_gateway", options), stream_running_(false) {
RCLCPP_INFO(this->get_logger(), "🧠 运控 gRPC 网关插件启动中... 正在挂载闭环调优 7 大接口!");
std::string target_ip = this->declare_parameter("agv_ip", "127.0.0.1:50051");
channel_ = grpc::CreateChannel(target_ip, grpc::InsecureChannelCredentials());
stub_ = AgvCalibControlService::NewStub(channel_);
// ==========================================================
// 1. 挂载 7 个 ROS 2 翻译服务
// ==========================================================
srv_set_mode_ = this->create_service<win_ubuntu_bridge::srv::SetControlMode>(
"~/set_control_mode", std::bind(&ControlGatewayNode::cb_set_mode, this, std::placeholders::_1, std::placeholders::_2));
srv_estop_ = this->create_service<std_srvs::srv::Trigger>(
"~/emergency_stop", std::bind(&ControlGatewayNode::cb_estop, this, std::placeholders::_1, std::placeholders::_2));
srv_open_loop_ = this->create_service<win_ubuntu_bridge::srv::ExecuteOpenLoopCmd>(
"~/execute_open_loop_cmd", std::bind(&ControlGatewayNode::cb_open_loop, this, std::placeholders::_1, std::placeholders::_2));
srv_follow_traj_ = this->create_service<win_ubuntu_bridge::srv::FollowTestTrajectory>(
"~/follow_test_trajectory", std::bind(&ControlGatewayNode::cb_follow_traj, this, std::placeholders::_1, std::placeholders::_2));
srv_step_resp_ = this->create_service<win_ubuntu_bridge::srv::ExecuteStepResponse>(
"~/execute_step_response", std::bind(&ControlGatewayNode::cb_step_resp, this, std::placeholders::_1, std::placeholders::_2));
srv_inject_params_ = this->create_service<win_ubuntu_bridge::srv::InjectTuningParams>(
"~/inject_tuning_params", std::bind(&ControlGatewayNode::cb_inject_params, this, std::placeholders::_1, std::placeholders::_2));
srv_commit_params_ = this->create_service<std_srvs::srv::Trigger>(
"~/commit_control_parameters", std::bind(&ControlGatewayNode::cb_commit_params, this, std::placeholders::_1, std::placeholders::_2));
// ==========================================================
// 2. 挂载 1 个 ROS 2 遥测发布者 (50Hz)
// ==========================================================
pub_telemetry_ = this->create_publisher<win_ubuntu_bridge::msg::ControlTelemetry>("~/control_telemetry", 50);
// 3. 启动独立推流抽水机线程
stream_running_ = true;
stream_thread_ = std::thread(&ControlGatewayNode::grpc_stream_to_ros2_topic, this);
}
~ControlGatewayNode() {
stream_running_ = false;
if (stream_context_) stream_context_->TryCancel();
if (stream_thread_.joinable()) stream_thread_.join();
}
private:
std::shared_ptr<grpc::Channel> channel_;
std::unique_ptr<AgvCalibControlService::Stub> stub_;
std::unique_ptr<grpc::ClientContext> stream_context_;
// Service & Publisher 句柄
rclcpp::Service<win_ubuntu_bridge::srv::SetControlMode>::SharedPtr srv_set_mode_;
rclcpp::Service<std_srvs::srv::Trigger>::SharedPtr srv_estop_;
rclcpp::Service<win_ubuntu_bridge::srv::ExecuteOpenLoopCmd>::SharedPtr srv_open_loop_;
rclcpp::Service<win_ubuntu_bridge::srv::FollowTestTrajectory>::SharedPtr srv_follow_traj_;
rclcpp::Service<win_ubuntu_bridge::srv::ExecuteStepResponse>::SharedPtr srv_step_resp_;
rclcpp::Service<win_ubuntu_bridge::srv::InjectTuningParams>::SharedPtr srv_inject_params_;
rclcpp::Service<std_srvs::srv::Trigger>::SharedPtr srv_commit_params_;
rclcpp::Publisher<win_ubuntu_bridge::msg::ControlTelemetry>::SharedPtr pub_telemetry_;
std::thread stream_thread_;
std::atomic<bool> stream_running_;
// ----------------------------------------------------------------------
// ⬇️ ROS 2 to gRPC 核心翻译逻辑 ⬇️
// ----------------------------------------------------------------------
void cb_set_mode(const std::shared_ptr<win_ubuntu_bridge::srv::SetControlMode::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::SetControlMode::Response> res) {
ModeRequest grpc_req;
grpc_req.set_target_mode(static_cast<ModeRequest::Mode>(req->target_mode));
StandardResponse grpc_reply;
grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
grpc::Status status = stub_->SetControlMode(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
void cb_estop(const std::shared_ptr<std_srvs::srv::Trigger::Request> req, std::shared_ptr<std_srvs::srv::Trigger::Response> res) {
(void)req; Empty grpc_req; StandardResponse grpc_reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(1));
grpc::Status status = stub_->EmergencyStop(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
void cb_open_loop(const std::shared_ptr<win_ubuntu_bridge::srv::ExecuteOpenLoopCmd::Request> req, std::shared_ptr<win_ubuntu_bridge::srv::ExecuteOpenLoopCmd::Response> res) {
OpenLoopRequest grpc_req;
grpc_req.set_left_motor_cmd(req->left_motor_cmd); grpc_req.set_right_motor_cmd(req->right_motor_cmd);
grpc_req.set_steering_angle(req->steering_angle); grpc_req.set_duration_sec(req->duration_sec);
StandardResponse grpc_reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
grpc::Status status = stub_->ExecuteOpenLoopCmd(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// 🚨 核心难点 1:翻译不定长数组 (轨迹考卷下发) + 超长超时时间
void cb_follow_traj(const std::shared_ptr<win_ubuntu_bridge::srv::FollowTestTrajectory::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::FollowTestTrajectory::Response> res) {
TrajectoryRequest grpc_req;
grpc_req.set_test_case_id(req->test_case_id);
// 遍历 ROS 2 的变长数组,依次 Add 进 gRPC 结构体 (使用 proto 的 add_ 方法分配内存)
for (const auto& ros_pt : req->path) {
TrajectoryPoint* grpc_pt = grpc_req.add_path();
grpc_pt->set_x_m(ros_pt.x_m);
grpc_pt->set_y_m(ros_pt.y_m);
grpc_pt->set_yaw_rad(ros_pt.yaw_rad);
grpc_pt->set_target_speed_ms(ros_pt.target_speed_ms);
grpc_pt->set_curvature(ros_pt.curvature);
}
RCLCPP_INFO(this->get_logger(), "正在下发包含 %zu 个轨迹点的考卷...", req->path.size());
StandardResponse grpc_reply;
grpc::ClientContext context;
// 🚨 [架构师级防线]:车辆跑一圈可能需要 120 秒!这里的超时时间决不能像其他指令那样设为 2 秒,否则必死锁报错!
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(180));
grpc::Status status = stub_->FollowTestTrajectory(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
void cb_step_resp(const std::shared_ptr<win_ubuntu_bridge::srv::ExecuteStepResponse::Request> req, std::shared_ptr<win_ubuntu_bridge::srv::ExecuteStepResponse::Response> res) {
StepResponseRequest grpc_req;
grpc_req.set_target_velocity_ms(req->target_velocity_ms); grpc_req.set_duration_sec(req->duration_sec);
StandardResponse grpc_reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(10)); // 阶跃可能耗时几秒
grpc::Status status = stub_->ExecuteStepResponse(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// 🚨 核心难点 2:解决 Optional 热更新映射
void cb_inject_params(const std::shared_ptr<win_ubuntu_bridge::srv::InjectTuningParams::Request> req, std::shared_ptr<win_ubuntu_bridge::srv::InjectTuningParams::Response> res) {
ControlParams grpc_req;
// 只有当 ROS 2 里的 has_ 标志位为 true 时,才向 gRPC 里 set 值
if (req->has_wheel_radius_left_ratio) grpc_req.set_wheel_radius_left_ratio(req->wheel_radius_left_ratio);
if (req->has_wheel_radius_right_ratio) grpc_req.set_wheel_radius_right_ratio(req->wheel_radius_right_ratio);
if (req->has_effective_track_width_m) grpc_req.set_effective_track_width_m(req->effective_track_width_m);
if (req->has_steering_zero_offset_deg) grpc_req.set_steering_zero_offset_deg(req->steering_zero_offset_deg);
if (req->has_pid_kp_lateral) grpc_req.set_pid_kp_lateral(req->pid_kp_lateral);
if (req->has_pid_ki_lateral) grpc_req.set_pid_ki_lateral(req->pid_ki_lateral);
if (req->has_pid_kd_lateral) grpc_req.set_pid_kd_lateral(req->pid_kd_lateral);
if (req->has_pid_kp_heading) grpc_req.set_pid_kp_heading(req->pid_kp_heading);
if (req->has_pid_ki_heading) grpc_req.set_pid_ki_heading(req->pid_ki_heading);
if (req->has_pid_kd_heading) grpc_req.set_pid_kd_heading(req->pid_kd_heading);
if (req->has_pure_pursuit_lookahead_m) grpc_req.set_pure_pursuit_lookahead_m(req->pure_pursuit_lookahead_m);
if (req->has_mpc_weight_q_lateral) grpc_req.set_mpc_weight_q_lateral(req->mpc_weight_q_lateral);
if (req->has_mpc_weight_r_steering) grpc_req.set_mpc_weight_r_steering(req->mpc_weight_r_steering);
StandardResponse grpc_reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(2));
grpc::Status status = stub_->InjectTuningParameters(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
void cb_commit_params(const std::shared_ptr<std_srvs::srv::Trigger::Request> req, std::shared_ptr<std_srvs::srv::Trigger::Response> res) {
(void)req; Empty grpc_req; StandardResponse grpc_reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(3));
grpc::Status status = stub_->CommitControlParameters(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// ==========================================================
// 🌊 异步抽水机:读取 50Hz 运控状态 -> 转化为 ROS 2 Topic
// ==========================================================
void grpc_stream_to_ros2_topic() {
while (stream_running_ && rclcpp::ok()) {
stream_context_ = std::make_unique<grpc::ClientContext>();
Empty grpc_req;
auto reader = stub_->StreamTelemetry(stream_context_.get(), grpc_req);
win_ubuntu_bridge::msg::ControlTelemetry ros_msg;
TelemetryData grpc_state;
RCLCPP_INFO(this->get_logger(), "🌊 车端运控 50Hz ODOM 与体感数据流已建立,开始广播...");
while (stream_running_ && reader->Read(&grpc_state) && rclcpp::ok()) {
// 将 gRPC Odom 与速度赋值给 ROS msg
ros_msg.hardware_timestamp_us = grpc_state.hardware_timestamp_us();
ros_msg.odom_x_m = grpc_state.odom_x_m();
ros_msg.odom_y_m = grpc_state.odom_y_m();
ros_msg.odom_yaw_rad = grpc_state.odom_yaw_rad();
ros_msg.feedback_linear_vel_ms = grpc_state.feedback_linear_vel_ms();
ros_msg.feedback_angular_vel_rads = grpc_state.feedback_angular_vel_rads();
ros_msg.left_motor_current_amp = grpc_state.left_motor_current_amp();
ros_msg.right_motor_current_amp = grpc_state.right_motor_current_amp();
ros_msg.steering_motor_current_amp = grpc_state.steering_motor_current_amp();
ros_msg.cmd_steering_output = grpc_state.cmd_steering_output();
// 广播供行为树和 AI 节点白嫖!
pub_telemetry_->publish(ros_msg);
}
if (stream_running_) {
RCLCPP_WARN(this->get_logger(), "⚠️ 运控推流中断!1秒后重连...");
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
}
};
} // namespace agv_calib_core
// 🚨 注册为 ROS 2 组件
RCLCPP_COMPONENTS_REGISTER_NODE(win_ubuntu_bridge::ControlGatewayNode)
@@ -0,0 +1,230 @@
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_components/register_node_macro.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
// 引入自定义 ROS 2 接口
#include "win_ubuntu_bridge/msg/camera_intrinsic.hpp"
#include "win_ubuntu_bridge/msg/sensor_extrinsic.hpp"
#include "win_ubuntu_bridge/srv/move_to_observation_pose.hpp"
#include "win_ubuntu_bridge/srv/trigger_sync_capture.hpp"
#include "win_ubuntu_bridge/srv/commit_calibration_results.hpp"
#include "win_ubuntu_bridge/action/download_sensor_data.hpp"
// 引入 gRPC 契约
#include <grpcpp/grpcpp.h>
#include "agv_calib_sensor.grpc.pb.h"
// 文件 I/O 与多线程
#include <fstream>
#include <filesystem>
#include <thread>
using namespace agv::calibration::sensor;
namespace fs = std::filesystem;
namespace win_ubuntu_bridge {
class SensorGatewayNode : public rclcpp::Node {
public:
using DownloadAction = win_ubuntu_bridge::action::DownloadSensorData;
using GoalHandleDownload = rclcpp_action::ServerGoalHandle<DownloadAction>;
explicit SensorGatewayNode(const rclcpp::NodeOptions & options)
: Node("sensor_gateway", options) {
RCLCPP_INFO(this->get_logger(), "📷 传感器 gRPC 网关插件启动中... 准备接管大文件流式拉取引擎!");
std::string target_ip = this->declare_parameter("agv_ip", "127.0.0.1:50051");
channel_ = grpc::CreateChannel(target_ip, grpc::InsecureChannelCredentials());
stub_ = SensorCalibrationService::NewStub(channel_);
// 1. 挂载 3 个瞬间动作服务 (Service)
srv_move_ = this->create_service<win_ubuntu_bridge::srv::MoveToObservationPose>(
"~/move_to_pose", std::bind(&SensorGatewayNode::cb_move, this, std::placeholders::_1, std::placeholders::_2));
srv_trigger_ = this->create_service<win_ubuntu_bridge::srv::TriggerSyncCapture>(
"~/trigger_sync_capture", std::bind(&SensorGatewayNode::cb_trigger, this, std::placeholders::_1, std::placeholders::_2));
srv_commit_ = this->create_service<win_ubuntu_bridge::srv::CommitCalibrationResults>(
"~/commit_calibration_results", std::bind(&SensorGatewayNode::cb_commit, this, std::placeholders::_1, std::placeholders::_2));
// 2. 🌟 挂载 1 个耗时动作服务器 (Action Server) —— 专治大文件流
action_download_ = rclcpp_action::create_server<DownloadAction>(
this,
"~/download_sensor_data",
std::bind(&SensorGatewayNode::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
std::bind(&SensorGatewayNode::handle_cancel, this, std::placeholders::_1),
std::bind(&SensorGatewayNode::handle_accepted, this, std::placeholders::_1));
}
private:
std::shared_ptr<grpc::Channel> channel_;
std::unique_ptr<SensorCalibrationService::Stub> stub_;
rclcpp::Service<win_ubuntu_bridge::srv::MoveToObservationPose>::SharedPtr srv_move_;
rclcpp::Service<win_ubuntu_bridge::srv::TriggerSyncCapture>::SharedPtr srv_trigger_;
rclcpp::Service<win_ubuntu_bridge::srv::CommitCalibrationResults>::SharedPtr srv_commit_;
rclcpp_action::Server<DownloadAction>::SharedPtr action_download_;
// ==========================================================
// 瞬时服务翻译 (Service)
// ==========================================================
void cb_move(const std::shared_ptr<win_ubuntu_bridge::srv::MoveToObservationPose::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::MoveToObservationPose::Response> res) {
PoseRequest grpc_req;
grpc_req.set_target_x_m(req->target_x_m); grpc_req.set_target_y_m(req->target_y_m);
grpc_req.set_target_yaw_deg(req->target_yaw_deg); grpc_req.set_is_relative(req->is_relative);
StandardResponse grpc_reply; grpc::ClientContext context;
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(60)); // 走位可能需要较长时间
grpc::Status status = stub_->MoveToObservationPose(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
void cb_trigger(const std::shared_ptr<win_ubuntu_bridge::srv::TriggerSyncCapture::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::TriggerSyncCapture::Response> res) {
CaptureRequest grpc_req;
for (const auto& id : req->sensor_ids) { grpc_req.add_sensor_ids(id); }
CaptureResponse grpc_reply; grpc::ClientContext context;
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(3));
grpc::Status status = stub_->TriggerSyncCapture(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success();
res->capture_timestamp_us = grpc_reply.capture_timestamp_us(); // 🚨 取回取件码
res->error_message = status.ok() ? grpc_reply.error_message() : status.error_message();
}
void cb_commit(const std::shared_ptr<win_ubuntu_bridge::srv::CommitCalibrationResults::Request> req,
std::shared_ptr<win_ubuntu_bridge::srv::CommitCalibrationResults::Response> res) {
CalibrationPayload grpc_req;
grpc_req.set_task_id(req->task_id);
// 映射结构体数组
for (const auto& ros_in : req->updated_intrinsics) {
auto* grpc_in = grpc_req.add_updated_intrinsics();
grpc_in->set_camera_id(ros_in.camera_id); grpc_in->set_fx(ros_in.fx); grpc_in->set_fy(ros_in.fy);
grpc_in->set_cx(ros_in.cx); grpc_in->set_cy(ros_in.cy);
for (auto d : ros_in.dist_coeffs) grpc_in->add_dist_coeffs(d);
}
for (const auto& ros_ex : req->updated_extrinsics) {
auto* grpc_ex = grpc_req.add_updated_extrinsics();
grpc_ex->set_source_frame(ros_ex.source_frame); grpc_ex->set_target_frame(ros_ex.target_frame);
grpc_ex->set_trans_x_mm(ros_ex.trans_x_mm); grpc_ex->set_trans_y_mm(ros_ex.trans_y_mm); grpc_ex->set_trans_z_mm(ros_ex.trans_z_mm);
grpc_ex->set_roll_deg(ros_ex.roll_deg); grpc_ex->set_pitch_deg(ros_ex.pitch_deg); grpc_ex->set_yaw_deg(ros_ex.yaw_deg);
}
StandardResponse grpc_reply; grpc::ClientContext context; context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(5));
grpc::Status status = stub_->CommitCalibrationResults(&context, grpc_req, &grpc_reply);
res->success = status.ok() && grpc_reply.success(); res->message = status.ok() ? grpc_reply.message() : status.error_message();
}
// ==========================================================
// 🚨 灵魂逻辑:大文件流式下载 转换为 ROS 2 Action 落盘
// ==========================================================
rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID & uuid, std::shared_ptr<const DownloadAction::Goal> goal) {
(void)uuid;
RCLCPP_INFO(this->get_logger(), "📥 收到大文件下载工单! 凭证: %ld, 传感器: %s", goal->capture_timestamp_us, goal->sensor_id.c_str());
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse handle_cancel(const std::shared_ptr<GoalHandleDownload> goal_handle) {
(void)goal_handle;
RCLCPP_WARN(this->get_logger(), "🛑 行为树请求强行取消下载");
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted(const std::shared_ptr<GoalHandleDownload> goal_handle) {
// 🚨 必须在独立线程中执行阻塞的网络流读取,否则会死锁整个 ROS 2 容器!
std::thread{std::bind(&SensorGatewayNode::execute_download, this, std::placeholders::_1), goal_handle}.detach();
}
// 后台真实下载线程
void execute_download(const std::shared_ptr<GoalHandleDownload> goal_handle) {
const auto goal = goal_handle->get_goal();
auto result = std::make_shared<DownloadAction::Result>();
auto feedback = std::make_shared<DownloadAction::Feedback>();
DataFetchRequest grpc_req;
grpc_req.set_capture_timestamp_us(goal->capture_timestamp_us);
grpc_req.set_sensor_id(goal->sensor_id);
grpc::ClientContext context;
context.set_deadline(std::chrono::system_clock::now() + std::chrono::seconds(60)); // 大文件给足 60 秒传输时间
std::unique_ptr<grpc::ClientReader<FileChunk>> reader;
if (goal->data_type == DownloadAction::Goal::DATA_TYPE_IMAGE) {
reader = stub_->DownloadImage(&context, grpc_req);
} else {
reader = stub_->DownloadPointCloud(&context, grpc_req);
}
FileChunk chunk;
std::ofstream outfile;
std::string final_file_path;
uint64_t total_bytes = 0;
bool is_first_chunk = true;
RCLCPP_INFO(this->get_logger(), "🚀 开始从车端流式拉取传感器大文件...");
// 🌊 像蚂蚁搬家一样,收到一块,就用二进制追加写入硬盘一块!
while (reader->Read(&chunk) && rclcpp::ok()) {
// 1. 检查行为树是否发出了取消指令
if (goal_handle->is_canceling()) {
context.TryCancel(); // 物理打断 gRPC 传输
if (outfile.is_open()) outfile.close();
if (!final_file_path.empty() && fs::exists(final_file_path)) fs::remove(final_file_path); // 删掉下了一半的残次品
result->success = false; result->error_message = "下载被系统强制中止";
goal_handle->canceled(result);
return;
}
// 2. 如果是第一包数据,创建目录并打开硬盘文件
if (is_first_chunk) {
if (!fs::exists(goal->save_directory)) { fs::create_directories(goal->save_directory); }
std::string ext = chunk.format_ext().empty() ? "bin" : chunk.format_ext(); // 动态获取文件后缀
final_file_path = goal->save_directory + "/" + goal->sensor_id + "_" + std::to_string(goal->capture_timestamp_us) + "." + ext;
// 🚨 以纯二进制 (binary) 和截断清空 (trunc) 模式打开硬盘文件!
outfile.open(final_file_path, std::ios::out | std::ios::binary | std::ios::trunc);
if (!outfile.is_open()) {
result->success = false; result->error_message = "无法在 Ubuntu 硬盘创建文件!权限不足?";
goal_handle->abort(result);
return;
}
is_first_chunk = false;
}
// 3. 把网络收到的内存碎块直接写入硬盘!(内存占用极低,绝对不会爆内存)
outfile.write(chunk.chunk_data().data(), chunk.chunk_data().size());
total_bytes += chunk.chunk_data().size();
// 4. 每收到一块,通过 Action Feedback 汇报一次进度
feedback->downloaded_bytes = total_bytes;
goal_handle->publish_feedback(feedback);
if (chunk.is_last_chunk()) break;
}
grpc::Status status = reader->Finish();
if (outfile.is_open()) outfile.close();
// 5. 善后汇报
if (status.ok() && !is_first_chunk) {
RCLCPP_INFO(this->get_logger(), "✅ 下载落盘成功!共计 %.2f MB -> %s", total_bytes / 1024.0 / 1024.0, final_file_path.c_str());
result->success = true;
result->saved_file_path = final_file_path; // 🚨 核心:将绝对路径交还给行为树!
goal_handle->succeed(result);
} else {
if (!final_file_path.empty() && fs::exists(final_file_path)) fs::remove(final_file_path); // 失败清理垃圾
RCLCPP_ERROR(this->get_logger(), "❌ 下载大文件失败: %s", status.error_message().c_str());
result->success = false;
result->error_message = "网络传输中断: " + status.error_message();
goal_handle->abort(result);
}
}
};
} // namespace agv_calib_core
// 注册为 ROS 2 组件
RCLCPP_COMPONENTS_REGISTER_NODE(win_ubuntu_bridge::SensorGatewayNode)
@@ -0,0 +1,6 @@
string camera_id
float64 fx
float64 fy
float64 cx
float64 cy
float64[] dist_coeffs
@@ -0,0 +1,10 @@
int64 hardware_timestamp_us
float64 odom_x_m
float64 odom_y_m
float64 odom_yaw_rad
float64 feedback_linear_vel_ms
float64 feedback_angular_vel_rads
float64 left_motor_current_amp
float64 right_motor_current_amp
float64 steering_motor_current_amp
float64 cmd_steering_output
@@ -0,0 +1,13 @@
int64 hardware_timestamp_us
int64 encoder_ticks_fl
int64 encoder_ticks_fr
int64 encoder_ticks_rl
int64 encoder_ticks_rr
float64 actual_steer_angle_front_deg
float64 actual_steer_angle_rear_deg
float64 current_fl_amp
float64 current_fr_amp
float64 current_rl_amp
float64 current_rr_amp
float64 current_steer_front_amp
uint32 driver_error_code
@@ -0,0 +1,8 @@
string source_frame
string target_frame
float64 trans_x_mm
float64 trans_y_mm
float64 trans_z_mm
float64 roll_deg
float64 pitch_deg
float64 yaw_deg
@@ -0,0 +1,5 @@
float64 x_m
float64 y_m
float64 yaw_rad
float64 target_speed_ms
float64 curvature
@@ -0,0 +1,28 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>win_ubuntu_bridge</name>
<version>1.0.0</version>
<description>AGV 自动化标定车间跨平台网关与总控核心</description>
<maintainer email="2469171725@qq.com">nvidia</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<depend>rclcpp</depend>
<depend>rclcpp_components</depend>
<depend>rclcpp_action</depend>
<depend>std_msgs</depend>
<depend>std_srvs</depend>
<depend>action_msgs</depend>
<depend>behaviortree_cpp_v3</depend> <test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
@@ -18,7 +18,7 @@ service SensorCalibrationService {
// 🚙 [Windows -> Linux]
// 🚨 Linux sleep(0.5s)
rpc MoveToObservationPose (PoseRequest) returns (StandardResponse);
// ---------------------------------------------------------
// ()
// ---------------------------------------------------------
@@ -0,0 +1,12 @@
#include <rclcpp/rclcpp.hpp>
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("master_node");
RCLCPP_INFO(node->get_logger(), "🚀 AGV 标定主控大脑编译成功,gRPC 契约已挂载!");
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
@@ -0,0 +1,6 @@
string task_id
win_ubuntu_bridge/CameraIntrinsic[] updated_intrinsics
win_ubuntu_bridge/SensorExtrinsic[] updated_extrinsics
---
bool success
string message
@@ -0,0 +1,26 @@
bool has_wheel_radius_fl
float64 wheel_radius_fl_m
bool has_wheel_radius_fr
float64 wheel_radius_fr_m
bool has_wheel_radius_rl
float64 wheel_radius_rl_m
bool has_wheel_radius_rr
float64 wheel_radius_rr_m
bool has_steer_zero_offset_front
float64 steer_zero_offset_front_deg
bool has_steer_zero_offset_rear
float64 steer_zero_offset_rear_deg
bool has_effective_track_width
float64 effective_track_width_m
bool has_effective_wheel_base
float64 effective_wheel_base_m
bool has_icr_offset_x
float64 icr_offset_x_m
bool has_icr_offset_y
float64 icr_offset_y_m
---
bool success
string message
@@ -0,0 +1,7 @@
float64 left_motor_cmd
float64 right_motor_cmd
float64 steering_angle
float64 duration_sec
---
bool success
string message
@@ -0,0 +1,9 @@
string test_case_id
float64 fl_motor_rpm
float64 fr_motor_rpm
float64 rl_motor_rpm
float64 rr_motor_rpm
float64 duration_sec
---
bool success
string message
@@ -0,0 +1,10 @@
string test_case_id
float64 front_steer_angle_deg
float64 rear_steer_angle_deg
bool has_sweep
float64 sweep_amplitude_deg
float64 sweep_frequency_hz
float64 duration_sec
---
bool success
string message
@@ -0,0 +1,5 @@
float64 target_velocity_ms
float64 duration_sec
---
bool success
string message
@@ -0,0 +1,5 @@
string test_case_id
win_ubuntu_bridge/TrajectoryPoint[] path
---
bool success
string message
@@ -0,0 +1,32 @@
bool has_wheel_radius_left_ratio
float64 wheel_radius_left_ratio
bool has_wheel_radius_right_ratio
float64 wheel_radius_right_ratio
bool has_effective_track_width_m
float64 effective_track_width_m
bool has_steering_zero_offset_deg
float64 steering_zero_offset_deg
bool has_pid_kp_lateral
float64 pid_kp_lateral
bool has_pid_ki_lateral
float64 pid_ki_lateral
bool has_pid_kd_lateral
float64 pid_kd_lateral
bool has_pid_kp_heading
float64 pid_kp_heading
bool has_pid_ki_heading
float64 pid_ki_heading
bool has_pid_kd_heading
float64 pid_kd_heading
bool has_pure_pursuit_lookahead_m
float64 pure_pursuit_lookahead_m
bool has_mpc_weight_q_lateral
float64 mpc_weight_q_lateral
bool has_mpc_weight_r_steering
float64 mpc_weight_r_steering
---
bool success
string message
@@ -0,0 +1,7 @@
float64 target_x_m
float64 target_y_m
float64 target_yaw_deg
bool is_relative
---
bool success
string message
@@ -0,0 +1,4 @@
uint8 target_mode # 0: NORMAL, 1: OPEN_LOOP, 2: TUNING_MODE
---
bool success
string message
@@ -0,0 +1,4 @@
uint8 target_mode # 0: NORMAL_KINEMATICS, 1: DIRECT_RAW_DRIVE
---
bool success
string message
@@ -0,0 +1,5 @@
string[] sensor_ids
---
bool success
int64 capture_timestamp_us # 🚨 极其关键的“取件码”
string error_message
+76
View File
@@ -0,0 +1,76 @@
===================================================================================================
[?????????] (??????????) [???Ubuntu ???? (?)] [???Windows??] (????)
===================================================================================================
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ?? ??????? ³ <--- main_pipeline.xml
³ BehaviorTree.CPP ?? ³ (???????????)
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
³
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ?? 1.???????? ³ ³ ?? BT ????? (C++) ³ ³ ?? AGV ???? ³
³ (?????????) ³<~~~~~³ - ?????? API ³------->³ (agv_calib_chassis) ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ API 1³ - ?????? API ³ gRPC 1 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
(ROS 2 Action Server) ³ - ???????? API ³
³ ³
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ?? 2.???????? ³ ³ ³ ³ ?? AGV ???? ³
³ (?????? PID) ³<~~~~~³ ------------------------- ³------->³ (agv_calib_control) ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ API 2³ ³ gRPC 2 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
(ROS 2 Action Server) ³ ??? ??????? (/tmp/) ³
³ - ??? .csv ???? ³
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ - ??? .png / .pcd ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ?? 3.???????? ³ ³ ³ ³ ?? AGV ????? ³
³ (Ceres ? 4x4 ??) ³<~~~~~³ ------------------------- ³------->³ (sensor_calibration) ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ API 3ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ gRPC 3 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
(ROS 2 Action Server)
===================================================================================================
=====================================================================================================
[??? ROS 2 ?? (?? & ???)] [??? ROS 2 ???? (??????)] [????? (gRPC)]
=====================================================================================================
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ?? ???????³ ³ ?? ChassisGatewayComponent ³ [Wi-Fi 6]
³ (?? ROS 2 Srv) ³<-- ROS Srv -->³ (?? agv_calib_chassis.proto) ³<--- gRPC ---> ?? ????
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ?????????? gRPC ?? ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ?? ?????? ³<-- ROS Topic--³ ?? ControlGatewayComponent ³ [Wi-Fi 6]
³ (???? Topic) ³ ³ (?? agv_calib_control.proto) ³<--- gRPC ---> ?? ????
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ<-- ROS Action-³ ?????? gRPC ??? Topic ³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿
³ ?? ?????? ³ ³ ?? SensorGatewayComponent ³ [Wi-Fi 6]
³ (???????) ³<-- ROS Action-³ (?? sensor_calibration.proto) ³<--- gRPC ---> ?? ???
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ?????????Action????³
ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ
=====================================================================================================
[?? Windows ????]
³
³ (C++ ?????? Ticks ? ??)
?
[?? Windows gRPC Server]
³
³ (? Wi-Fi 6 ? 50Hz Stream ???????)
?
==================== ?????? ====================
³
[?? Linux ChassisGatewayNode (???????)]
³ (?? gRPC Client ?? reader->Read() ???)
³
³ (1:1 ?????? ROS 2 ? msg ???)
?
[?? Linux ChassisGatewayNode (ROS 2 Publisher)]
³
³ (?? pub_telemetry_->publish() ? ROS 2 ????)
?
================= ROS 2 ?????? =================
³
ÃÄ? [?? ????????] (?? Subscriber ?????????)
ÃÄ? [?? ????????] (?? Subscriber ??????? > 20A ????)
ÃÄ? [?? ???????] (?? Subscriber ?????????)
ÀÄ? [?? ROS Bag ???] (????????? 50Hz ????????)