diff --git a/agv_calib_brain/src/agv_calib_control_pb2.py b/agv_calib_brain/src/agv_calib_control_pb2.py new file mode 100644 index 0000000..edaf732 --- /dev/null +++ b/agv_calib_brain/src/agv_calib_control_pb2.py @@ -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) diff --git a/agv_calib_brain/src/agv_calib_control_pb2_grpc.py b/agv_calib_brain/src/agv_calib_control_pb2_grpc.py new file mode 100644 index 0000000..5dd44cb --- /dev/null +++ b/agv_calib_brain/src/agv_calib_control_pb2_grpc.py @@ -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) diff --git a/agv_calib_brain/src/agv_calib_core/CMakeLists.txt b/agv_calib_brain/src/agv_calib_core/CMakeLists.txt index 5d02b51..72631e0 100644 --- a/agv_calib_brain/src/agv_calib_core/CMakeLists.txt +++ b/agv_calib_brain/src/agv_calib_core/CMakeLists.txt @@ -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() \ No newline at end of file diff --git a/agv_calib_brain/src/agv_calib_core/behavior_trees/main_pipeline.xml b/agv_calib_brain/src/agv_calib_core/behavior_trees/main_pipeline.xml new file mode 100644 index 0000000..fce81fc --- /dev/null +++ b/agv_calib_brain/src/agv_calib_core/behavior_trees/main_pipeline.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/agv_calib_brain/src/agv_calib_core/package.xml b/agv_calib_brain/src/agv_calib_core/package.xml index a227b83..89b1683 100644 --- a/agv_calib_brain/src/agv_calib_core/package.xml +++ b/agv_calib_brain/src/agv_calib_core/package.xml @@ -9,6 +9,10 @@ ament_cmake + rclcpp + behaviortree_cpp_v3 + ament_index_cpp + ament_lint_auto ament_lint_common diff --git a/agv_calib_brain/src/agv_calib_core/src/bt_nodes/dummy_nodes.hpp b/agv_calib_brain/src/agv_calib_core/src/bt_nodes/dummy_nodes.hpp new file mode 100644 index 0000000..d41935d --- /dev/null +++ b/agv_calib_brain/src/agv_calib_core/src/bt_nodes/dummy_nodes.hpp @@ -0,0 +1,82 @@ +#pragma once +#include +#include +#include +#include + +// 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; + } +}; \ No newline at end of file diff --git a/agv_calib_brain/src/agv_calib_core/src/bt_nodes/real_grpc_nodes.hpp b/agv_calib_brain/src/agv_calib_core/src/bt_nodes/real_grpc_nodes.hpp new file mode 100644 index 0000000..d082732 --- /dev/null +++ b/agv_calib_brain/src/agv_calib_core/src/bt_nodes/real_grpc_nodes.hpp @@ -0,0 +1,54 @@ +#pragma once +#include +#include +// 引入 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 channel_; + std::unique_ptr stub_; +}; \ No newline at end of file diff --git a/agv_calib_brain/src/agv_calib_core/src/master_node.cpp b/agv_calib_brain/src/agv_calib_core/src/master_node.cpp index f637f03..6b6db01 100644 --- a/agv_calib_brain/src/agv_calib_core/src/master_node.cpp +++ b/agv_calib_brain/src/agv_calib_core/src/master_node.cpp @@ -1,12 +1,44 @@ #include +#include +#include + +// 引入刚才写的假节点 +#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"); + factory.registerNodeType("MockCallChassisAlgo"); + factory.registerNodeType("MockTuneControl"); + factory.registerNodeType("MockCallControlAlgo"); + factory.registerNodeType("MockMoveAndCapture"); + factory.registerNodeType("MockCallSensorAlgo"); + factory.registerNodeType("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; } \ No newline at end of file diff --git a/agv_calib_brain/src/agv_calib_core/test/agv_calib_control_pb2.py b/agv_calib_brain/src/agv_calib_core/test/agv_calib_control_pb2.py new file mode 100644 index 0000000..edaf732 --- /dev/null +++ b/agv_calib_brain/src/agv_calib_core/test/agv_calib_control_pb2.py @@ -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) diff --git a/agv_calib_brain/src/agv_calib_core/test/agv_calib_control_pb2_grpc.py b/agv_calib_brain/src/agv_calib_core/test/agv_calib_control_pb2_grpc.py new file mode 100644 index 0000000..5dd44cb --- /dev/null +++ b/agv_calib_brain/src/agv_calib_core/test/agv_calib_control_pb2_grpc.py @@ -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) diff --git a/agv_calib_brain/src/agv_calib_core/test/mock_agv_server.py b/agv_calib_brain/src/agv_calib_core/test/mock_agv_server.py new file mode 100644 index 0000000..b7f04bd --- /dev/null +++ b/agv_calib_brain/src/agv_calib_core/test/mock_agv_server.py @@ -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() \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/CMakeLists.txt b/agv_calib_brain/src/win_ubuntu_bridge/CMakeLists.txt new file mode 100644 index 0000000..38d19d6 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/CMakeLists.txt @@ -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 + "$" + "$" + ) + 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 "$" "${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 "$" "${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() \ No newline at end of file diff --git a/agv_calib_brain/src/agv_calib_core/README.md b/agv_calib_brain/src/win_ubuntu_bridge/README.md similarity index 100% rename from agv_calib_brain/src/agv_calib_core/README.md rename to agv_calib_brain/src/win_ubuntu_bridge/README.md diff --git a/agv_calib_brain/src/win_ubuntu_bridge/action/DownloadSensorData.action b/agv_calib_brain/src/win_ubuntu_bridge/action/DownloadSensorData.action new file mode 100644 index 0000000..eca3cfa --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/action/DownloadSensorData.action @@ -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 # 已下载的字节数 (供行为树监控是否卡死) \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/gateways/chassis_gateway_node.cpp b/agv_calib_brain/src/win_ubuntu_bridge/gateways/chassis_gateway_node.cpp new file mode 100644 index 0000000..74e9bae --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/gateways/chassis_gateway_node.cpp @@ -0,0 +1,227 @@ +#include +#include + +// 🚨 引入自定义的 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 // 紧急刹车可以直接用标准库的 Trigger + +// 引入 gRPC 自动生成的契约头文件 +#include +#include "agv_calib_chassis.grpc.pb.h" + +#include +#include + +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( + "~/set_diagnostic_mode", std::bind(&ChassisGatewayNode::cb_set_mode, this, std::placeholders::_1, std::placeholders::_2)); + + // 接口 2:最高优急停 (无参数,用 Trigger) + srv_estop_ = this->create_service( + "~/hardware_emergency_brake", std::bind(&ChassisGatewayNode::cb_emergency_brake, this, std::placeholders::_1, std::placeholders::_2)); + + // 接口 3:开环直行 (测轮径) + srv_raw_drive_ = this->create_service( + "~/execute_raw_drive", std::bind(&ChassisGatewayNode::cb_raw_drive, this, std::placeholders::_1, std::placeholders::_2)); + + // 接口 4:开环转向 (测死区/零位) + srv_raw_steer_ = this->create_service( + "~/execute_raw_steer", std::bind(&ChassisGatewayNode::cb_raw_steer, this, std::placeholders::_1, std::placeholders::_2)); + + // 接口 5:物理参数定稿固化 + srv_commit_kinematics_ = this->create_service( + "~/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("~/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 channel_; + std::unique_ptr stub_; + std::unique_ptr stream_context_; + + // ROS 2 接口句柄 + rclcpp::Service::SharedPtr srv_set_mode_; + rclcpp::Service::SharedPtr srv_estop_; + rclcpp::Service::SharedPtr srv_raw_drive_; + rclcpp::Service::SharedPtr srv_raw_steer_; + rclcpp::Service::SharedPtr srv_commit_kinematics_; + rclcpp::Publisher::SharedPtr pub_telemetry_; + + std::thread stream_thread_; + std::atomic stream_running_; + + // ---------------------------------------------------------------------- + // ⬇️ 以下全是完美的 1:1 翻译逻辑 (ROS Request -> gRPC -> ROS Response) ⬇️ + // ---------------------------------------------------------------------- + + // [翻译] 接口 1: SetDiagnosticMode + void cb_set_mode(const std::shared_ptr req, + std::shared_ptr 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 req, + std::shared_ptr 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 req, + std::shared_ptr 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 req, + std::shared_ptr 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 req, + std::shared_ptr 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(); + 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) \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/gateways/control_gateway_node.cpp b/agv_calib_brain/src/win_ubuntu_bridge/gateways/control_gateway_node.cpp new file mode 100644 index 0000000..1e45e90 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/gateways/control_gateway_node.cpp @@ -0,0 +1,235 @@ +#include +#include + +// 引入自定义的 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 + +// 引入 gRPC 网络契约 +#include +#include "agv_calib_control.grpc.pb.h" + +#include +#include + +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( + "~/set_control_mode", std::bind(&ControlGatewayNode::cb_set_mode, this, std::placeholders::_1, std::placeholders::_2)); + + srv_estop_ = this->create_service( + "~/emergency_stop", std::bind(&ControlGatewayNode::cb_estop, this, std::placeholders::_1, std::placeholders::_2)); + + srv_open_loop_ = this->create_service( + "~/execute_open_loop_cmd", std::bind(&ControlGatewayNode::cb_open_loop, this, std::placeholders::_1, std::placeholders::_2)); + + srv_follow_traj_ = this->create_service( + "~/follow_test_trajectory", std::bind(&ControlGatewayNode::cb_follow_traj, this, std::placeholders::_1, std::placeholders::_2)); + + srv_step_resp_ = this->create_service( + "~/execute_step_response", std::bind(&ControlGatewayNode::cb_step_resp, this, std::placeholders::_1, std::placeholders::_2)); + + srv_inject_params_ = this->create_service( + "~/inject_tuning_params", std::bind(&ControlGatewayNode::cb_inject_params, this, std::placeholders::_1, std::placeholders::_2)); + + srv_commit_params_ = this->create_service( + "~/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("~/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 channel_; + std::unique_ptr stub_; + std::unique_ptr stream_context_; + + // Service & Publisher 句柄 + rclcpp::Service::SharedPtr srv_set_mode_; + rclcpp::Service::SharedPtr srv_estop_; + rclcpp::Service::SharedPtr srv_open_loop_; + rclcpp::Service::SharedPtr srv_follow_traj_; + rclcpp::Service::SharedPtr srv_step_resp_; + rclcpp::Service::SharedPtr srv_inject_params_; + rclcpp::Service::SharedPtr srv_commit_params_; + + rclcpp::Publisher::SharedPtr pub_telemetry_; + + std::thread stream_thread_; + std::atomic stream_running_; + + // ---------------------------------------------------------------------- + // ⬇️ ROS 2 to gRPC 核心翻译逻辑 ⬇️ + // ---------------------------------------------------------------------- + + void cb_set_mode(const std::shared_ptr req, + std::shared_ptr res) { + ModeRequest grpc_req; + grpc_req.set_target_mode(static_cast(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 req, std::shared_ptr 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 req, std::shared_ptr 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 req, + std::shared_ptr 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 req, std::shared_ptr 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 req, std::shared_ptr 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 req, std::shared_ptr 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(); + 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) \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/gateways/sensor_gateway_node.cpp b/agv_calib_brain/src/win_ubuntu_bridge/gateways/sensor_gateway_node.cpp new file mode 100644 index 0000000..ee936f7 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/gateways/sensor_gateway_node.cpp @@ -0,0 +1,230 @@ +#include +#include +#include + +// 引入自定义 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 +#include "agv_calib_sensor.grpc.pb.h" + +// 文件 I/O 与多线程 +#include +#include +#include + +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; + + 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( + "~/move_to_pose", std::bind(&SensorGatewayNode::cb_move, this, std::placeholders::_1, std::placeholders::_2)); + + srv_trigger_ = this->create_service( + "~/trigger_sync_capture", std::bind(&SensorGatewayNode::cb_trigger, this, std::placeholders::_1, std::placeholders::_2)); + + srv_commit_ = this->create_service( + "~/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( + 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 channel_; + std::unique_ptr stub_; + + rclcpp::Service::SharedPtr srv_move_; + rclcpp::Service::SharedPtr srv_trigger_; + rclcpp::Service::SharedPtr srv_commit_; + rclcpp_action::Server::SharedPtr action_download_; + + // ========================================================== + // 瞬时服务翻译 (Service) + // ========================================================== + void cb_move(const std::shared_ptr req, + std::shared_ptr 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 req, + std::shared_ptr 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 req, + std::shared_ptr 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 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 goal_handle) { + (void)goal_handle; + RCLCPP_WARN(this->get_logger(), "🛑 行为树请求强行取消下载"); + return rclcpp_action::CancelResponse::ACCEPT; + } + + void handle_accepted(const std::shared_ptr 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 goal_handle) { + const auto goal = goal_handle->get_goal(); + auto result = std::make_shared(); + auto feedback = std::make_shared(); + + 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> 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) \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/msg/CameraIntrinsic.msg b/agv_calib_brain/src/win_ubuntu_bridge/msg/CameraIntrinsic.msg new file mode 100644 index 0000000..6470697 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/msg/CameraIntrinsic.msg @@ -0,0 +1,6 @@ +string camera_id +float64 fx +float64 fy +float64 cx +float64 cy +float64[] dist_coeffs \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/msg/ControlTelemetry.msg b/agv_calib_brain/src/win_ubuntu_bridge/msg/ControlTelemetry.msg new file mode 100644 index 0000000..7183a52 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/msg/ControlTelemetry.msg @@ -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 \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/msg/HardwareState.msg b/agv_calib_brain/src/win_ubuntu_bridge/msg/HardwareState.msg new file mode 100644 index 0000000..667ce78 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/msg/HardwareState.msg @@ -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 \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/msg/SensorExtrinsic.msg b/agv_calib_brain/src/win_ubuntu_bridge/msg/SensorExtrinsic.msg new file mode 100644 index 0000000..96df4a1 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/msg/SensorExtrinsic.msg @@ -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 \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/msg/TrajectoryPoint.msg b/agv_calib_brain/src/win_ubuntu_bridge/msg/TrajectoryPoint.msg new file mode 100644 index 0000000..1e42738 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/msg/TrajectoryPoint.msg @@ -0,0 +1,5 @@ +float64 x_m +float64 y_m +float64 yaw_rad +float64 target_speed_ms +float64 curvature \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/package.xml b/agv_calib_brain/src/win_ubuntu_bridge/package.xml new file mode 100644 index 0000000..9e57938 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/package.xml @@ -0,0 +1,28 @@ + + + + win_ubuntu_bridge + 1.0.0 + AGV 自动化标定车间跨平台网关与总控核心 + nvidia + Apache-2.0 + + ament_cmake + + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + + rclcpp + rclcpp_components + rclcpp_action + std_msgs + std_srvs + action_msgs + behaviortree_cpp_v3 ament_lint_auto + ament_lint_common + + + ament_cmake + + \ No newline at end of file diff --git a/agv_calib_brain/src/agv_calib_core/proto/agv_calib_chassis.proto b/agv_calib_brain/src/win_ubuntu_bridge/proto/agv_calib_chassis.proto similarity index 100% rename from agv_calib_brain/src/agv_calib_core/proto/agv_calib_chassis.proto rename to agv_calib_brain/src/win_ubuntu_bridge/proto/agv_calib_chassis.proto diff --git a/agv_calib_brain/src/agv_calib_core/proto/agv_calib_control.proto b/agv_calib_brain/src/win_ubuntu_bridge/proto/agv_calib_control.proto similarity index 100% rename from agv_calib_brain/src/agv_calib_core/proto/agv_calib_control.proto rename to agv_calib_brain/src/win_ubuntu_bridge/proto/agv_calib_control.proto diff --git a/agv_calib_brain/src/agv_calib_core/proto/agv_calib_sensor.proto b/agv_calib_brain/src/win_ubuntu_bridge/proto/agv_calib_sensor.proto similarity index 99% rename from agv_calib_brain/src/agv_calib_core/proto/agv_calib_sensor.proto rename to agv_calib_brain/src/win_ubuntu_bridge/proto/agv_calib_sensor.proto index e9d0a40..ffffb43 100644 --- a/agv_calib_brain/src/agv_calib_core/proto/agv_calib_sensor.proto +++ b/agv_calib_brain/src/win_ubuntu_bridge/proto/agv_calib_sensor.proto @@ -18,7 +18,7 @@ service SensorCalibrationService { // 🚙 [Windows 返回 -> Linux]:物理到位抱死刹车后,返回成功回执 // 🚨 业务潜台词:Linux 收到回执后,必须在代码里强制 sleep(0.5s) 等待避震悬挂平息,冻结物理空间! rpc MoveToObservationPose (PoseRequest) returns (StandardResponse); - + // --------------------------------------------------------- // 第二步:防延迟同步锁存 (停与拍) // --------------------------------------------------------- diff --git a/agv_calib_brain/src/agv_calib_core/src/brain_node.cpp b/agv_calib_brain/src/win_ubuntu_bridge/src/brain_node.cpp similarity index 100% rename from agv_calib_brain/src/agv_calib_core/src/brain_node.cpp rename to agv_calib_brain/src/win_ubuntu_bridge/src/brain_node.cpp diff --git a/agv_calib_brain/src/win_ubuntu_bridge/src/master_node.cpp b/agv_calib_brain/src/win_ubuntu_bridge/src/master_node.cpp new file mode 100644 index 0000000..f637f03 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/src/master_node.cpp @@ -0,0 +1,12 @@ +#include + +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; +} \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/CommitCalibrationResults.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/CommitCalibrationResults.srv new file mode 100644 index 0000000..0d80ab6 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/CommitCalibrationResults.srv @@ -0,0 +1,6 @@ +string task_id +win_ubuntu_bridge/CameraIntrinsic[] updated_intrinsics +win_ubuntu_bridge/SensorExtrinsic[] updated_extrinsics +--- +bool success +string message \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/CommitKinematics.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/CommitKinematics.srv new file mode 100644 index 0000000..a46ed2f --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/CommitKinematics.srv @@ -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 \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteOpenLoopCmd.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteOpenLoopCmd.srv new file mode 100644 index 0000000..95dcd1e --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteOpenLoopCmd.srv @@ -0,0 +1,7 @@ +float64 left_motor_cmd +float64 right_motor_cmd +float64 steering_angle +float64 duration_sec +--- +bool success +string message \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteRawDrive.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteRawDrive.srv new file mode 100644 index 0000000..e8d8a51 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteRawDrive.srv @@ -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 \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteRawSteer.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteRawSteer.srv new file mode 100644 index 0000000..5fb0175 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteRawSteer.srv @@ -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 \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteStepResponse.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteStepResponse.srv new file mode 100644 index 0000000..5619664 --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/ExecuteStepResponse.srv @@ -0,0 +1,5 @@ +float64 target_velocity_ms +float64 duration_sec +--- +bool success +string message \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/FollowTestTrajectory.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/FollowTestTrajectory.srv new file mode 100644 index 0000000..b6719fe --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/FollowTestTrajectory.srv @@ -0,0 +1,5 @@ +string test_case_id +win_ubuntu_bridge/TrajectoryPoint[] path +--- +bool success +string message \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/InjectTuningParams.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/InjectTuningParams.srv new file mode 100644 index 0000000..817488b --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/InjectTuningParams.srv @@ -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 \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/MoveToObservationPose.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/MoveToObservationPose.srv new file mode 100644 index 0000000..89c1bbc --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/MoveToObservationPose.srv @@ -0,0 +1,7 @@ +float64 target_x_m +float64 target_y_m +float64 target_yaw_deg +bool is_relative +--- +bool success +string message \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/SetControlMode.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/SetControlMode.srv new file mode 100644 index 0000000..59812fb --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/SetControlMode.srv @@ -0,0 +1,4 @@ +uint8 target_mode # 0: NORMAL, 1: OPEN_LOOP, 2: TUNING_MODE +--- +bool success +string message \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/SetDiagnosticMode.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/SetDiagnosticMode.srv new file mode 100644 index 0000000..7653f1c --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/SetDiagnosticMode.srv @@ -0,0 +1,4 @@ +uint8 target_mode # 0: NORMAL_KINEMATICS, 1: DIRECT_RAW_DRIVE +--- +bool success +string message \ No newline at end of file diff --git a/agv_calib_brain/src/win_ubuntu_bridge/srv/TriggerSyncCapture.srv b/agv_calib_brain/src/win_ubuntu_bridge/srv/TriggerSyncCapture.srv new file mode 100644 index 0000000..aa96d4e --- /dev/null +++ b/agv_calib_brain/src/win_ubuntu_bridge/srv/TriggerSyncCapture.srv @@ -0,0 +1,5 @@ +string[] sensor_ids +--- +bool success +int64 capture_timestamp_us # 🚨 极其关键的“取件码” +string error_message \ No newline at end of file diff --git a/全局架构图.md b/全局架构图.md new file mode 100644 index 0000000..73418f2 --- /dev/null +++ b/全局架构图.md @@ -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 ????????) \ No newline at end of file