Ver Fonte

实现ipad启动取消事件

sousenw há 2 meses atrás
pai
commit
4bea8d40c0

+ 26 - 0
recharge2/interface/CMakeLists.txt

@@ -0,0 +1,26 @@
+cmake_minimum_required(VERSION 3.8)
+project(interface)
+
+if(NOT CMAKE_CXX_STANDARD)
+  set(CMAKE_CXX_STANDARD 17)
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+  add_compile_options(-Wall -Wextra -Wpedantic)
+endif()
+
+# find dependencies
+find_package(ament_cmake REQUIRED)
+find_package(std_msgs REQUIRED)
+find_package(rosidl_default_generators REQUIRED)
+
+# 生成接口 - 这个命令会自动处理所有需要的导出
+rosidl_generate_interfaces(${PROJECT_NAME}
+  "srv/SceneControls.srv"
+  DEPENDENCIES std_msgs
+)
+
+# 对于简单的接口包,只需要这个就够了
+# rosidl_generate_interfaces 会自动处理头文件安装和导出
+
+ament_package()

+ 1 - 0
recharge2/interface/msg/Empty.msg

@@ -0,0 +1 @@
+# 空消息,可用于简单触发

+ 24 - 0
recharge2/interface/package.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
+<package format="3">
+  <name>interface</name>
+  <version>0.0.0</version>
+  <description>Custom interface package for SceneControls service</description>
+  <maintainer email="zhangkaifeng@users.noreply.github.com">zkf</maintainer>
+  <license>Apache-2.0</license>
+
+  <buildtool_depend>ament_cmake</buildtool_depend>
+  <buildtool_depend>rosidl_default_generators</buildtool_depend>
+
+  <depend>std_msgs</depend>
+  <depend>rosidl_default_runtime</depend>
+  
+  <member_of_group>rosidl_interface_packages</member_of_group>
+  
+  <test_depend>ament_lint_auto</test_depend>
+  <test_depend>ament_lint_common</test_depend>
+
+  <export>
+    <build_type>ament_cmake</build_type>
+  </export>
+</package>

+ 4 - 0
recharge2/interface/srv/SceneControls.srv

@@ -0,0 +1,4 @@
+int32 start
+---
+int32 code
+string message

+ 61 - 0
recharge2/wheelchair_state_machine/CMakeLists.txt

@@ -0,0 +1,61 @@
+cmake_minimum_required(VERSION 3.16)
+project(wheelchair_state_machine)
+
+# 默认使用C++17
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+
+# 查找依赖包
+find_package(ament_cmake REQUIRED)
+find_package(rclcpp REQUIRED)
+find_package(std_msgs REQUIRED)
+find_package(geometry_msgs REQUIRED)
+find_package(sensor_msgs REQUIRED)
+find_package(visualization_msgs REQUIRED)
+find_package(tf2 REQUIRED)
+find_package(tf2_geometry_msgs REQUIRED)
+find_package(Eigen3 REQUIRED)
+find_package(interface REQUIRED)
+
+# 创建可执行文件
+add_executable(wheelchair_state_machine
+  src/wheelchair_state_machine.cpp
+  src/main.cpp
+)
+
+# 使用 ament_target_dependencies 自动处理ROS2依赖
+ament_target_dependencies(wheelchair_state_machine
+  rclcpp
+  std_msgs
+  geometry_msgs
+  sensor_msgs
+  visualization_msgs
+  tf2
+  tf2_geometry_msgs
+  interface  # 关键:这里添加interface依赖
+)
+
+# 链接Eigen3库(不是ROS2包,需要单独处理)
+target_include_directories(wheelchair_state_machine PRIVATE
+  ${Eigen3_INCLUDE_DIRS}
+)
+target_link_libraries(wheelchair_state_machine
+  Eigen3::Eigen
+)
+
+# 添加自定义头文件目录
+target_include_directories(wheelchair_state_machine PRIVATE
+  include
+)
+
+# 安装可执行文件
+install(TARGETS wheelchair_state_machine
+  RUNTIME DESTINATION lib/${PROJECT_NAME}
+)
+
+# 安装头文件(如果有需要)
+install(DIRECTORY include/
+  DESTINATION include/${PROJECT_NAME}
+)
+
+ament_package()

+ 202 - 0
recharge2/wheelchair_state_machine/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 299 - 0
recharge2/wheelchair_state_machine/include/wheelchair_state_machine/wheelchair_state_machine.hpp

@@ -0,0 +1,299 @@
+// wheelchair_state_machine.hpp
+#ifndef WHEELCHAIR_STATE_MACHINE_HPP
+#define WHEELCHAIR_STATE_MACHINE_HPP
+
+#include <rclcpp/rclcpp.hpp>
+#include <string>
+#include <map>
+#include <memory>
+#include <vector>
+#include <Eigen/Dense>
+#include <sensor_msgs/msg/laser_scan.hpp>
+#include <geometry_msgs/msg/twist.hpp>
+#include <visualization_msgs/msg/marker_array.hpp>
+#include <std_msgs/msg/string.hpp>
+#include <std_msgs/msg/bool.hpp>
+#include <std_msgs/msg/int32.hpp>
+#include <std_msgs/msg/float32.hpp>
+#include <sensor_msgs/msg/battery_state.hpp>
+#include <cmath>
+#include <deque>
+#include <numeric>
+#include <thread>
+#include <algorithm>
+#include <limits>
+
+// 轮椅状态枚举
+enum class WheelchairState
+{
+    READY,     // 就绪中
+    WALKING,   // 行走中
+    SEARCHING, // 搜索中
+    CHARGING   // 充电中
+};
+
+// 充电状态枚举
+enum class ChargingState
+{
+    IDLE,
+    NAVIGATING,
+    CHARGING,
+    UNAVAILABLE,
+    COMPLETED
+};
+
+// 电池状态枚举
+enum class BatteryState
+{
+    NORMAL,      // 正常
+    LOW,         // 低电量
+    CRITICAL,    // 严重低电量
+    CHARGING,    // 充电中
+    FULL         // 充满
+};
+
+// ==================== 数据结构 ====================
+struct IndexPoint
+{
+    double x;
+    double y;
+    int index;
+
+    IndexPoint(double x = 0.0, double y = 0.0, int index = 0)
+        : x(x), y(y), index(index) {}
+};
+
+struct StraightLine
+{
+    double k;
+    double b;
+
+    StraightLine(double k = 0.0, double b = 0.0) : k(k), b(b) {}
+};
+
+struct ScanFilterParam
+{
+    int min_index = 292;
+    int max_index = 382;
+    double min_intensities = 120.0;
+    double max_intensities = 220.0;
+    double min_distance = 0.28;
+    double max_distance = 3.5;
+    int min_count = 4;
+    double dock_length = 0.42;
+    int baselink_back_index = 337;
+};
+
+struct RechargeResult
+{
+    int frameid;
+    sensor_msgs::msg::LaserScan::SharedPtr laserScanData;
+    std::vector<IndexPoint> points;
+    std::vector<IndexPoint> left_points;
+    std::vector<IndexPoint> right_points;
+    IndexPoint middle_point;
+    StraightLine middle_line;
+    StraightLine left_line;
+    StraightLine right_line;
+};
+
+// ==================== 工具类 ====================
+class RechargeTool
+{
+public:
+    static double angleBetween(const IndexPoint &p1, const IndexPoint &p2);
+    static double angleDiff(double a1, double a2);
+    static double averageAngle(const std::vector<double> &angles);
+    
+    static std::vector<std::vector<IndexPoint>> splitPointsToSegments(
+        const std::vector<IndexPoint> &points,
+        double angle_threshold_deg = 15.0,
+        int history_window_size = 3);
+    
+    static std::pair<double, double> calculateAngleBisector(double k1, double b1, double k2, double b2);
+    static std::pair<double, double> fitLineLeastSquares(const Eigen::MatrixXd &points);
+    static std::pair<double, double> computeDockingVelocity(double dx, double dy, double yaw_error);
+    
+    // 直线拟合(简化版,用于基础功能)
+    static std::pair<double, double> simpleFitLine(const std::vector<IndexPoint> &points);
+};
+
+// ==================== 主状态机类 ====================
+class WheelchairStateMachine : public rclcpp::Node
+{
+public:
+    // 构造函数
+    WheelchairStateMachine();
+    
+    // 状态机核心接口
+    bool handleEvent(const std::string &event);
+    std::string getCurrentState() const;
+    void setState(WheelchairState new_state);
+    
+    // 回充控制接口
+    void startRecharge();
+    void cancelRecharge();
+    bool isRechargeActive() const { return recharge_active_; }
+    
+    // 充电检测接口
+    void setChargingVoltage(bool value);
+    bool getChargingVoltage() const;
+
+    void displaySystemStatus();
+private:
+    // ==================== 状态机成员 ====================
+    WheelchairState current_state_;
+    std::map<std::string, std::map<WheelchairState, bool>> transition_table_;
+    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr state_publisher_;
+    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_vel_publisher_;
+    rclcpp::TimerBase::SharedPtr search_timeout_timer_;
+    bool interface_active_;
+    
+    // ==================== 回充功能成员 ====================
+    bool recharge_active_;
+    ChargingState charging_state_;
+    
+    // ROS2 订阅者和发布者
+    rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr scan_sub_;
+    rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr pub_ctrl_;
+    rclcpp::Subscription<std_msgs::msg::Bool>::SharedPtr sub_cmd_enable_;
+    rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr pub_rviz_;
+    rclcpp::TimerBase::SharedPtr recharge_timer_;
+    rclcpp::Publisher<sensor_msgs::msg::LaserScan>::SharedPtr pub_filtered_;
+    rclcpp::Publisher<sensor_msgs::msg::LaserScan>::SharedPtr pub_line_;
+    
+    // 电池状态相关
+    rclcpp::Subscription<sensor_msgs::msg::BatteryState>::SharedPtr battery_sub_;
+    BatteryState battery_state_;
+    float battery_percentage_;
+    float low_battery_threshold_;      // 低电量阈值(默认20%)
+    float critical_battery_threshold_; // 严重低电量阈值(默认10%)
+    
+    // 回充控制变量
+    geometry_msgs::msg::Twist ctrl_twist_;
+    rclcpp::Time ctrl_twist_time_;
+    ScanFilterParam param_;
+    double distance_threshold_;
+    int fit_window_size_;
+    double fit_residual_threshold_;
+    int frame_index_;
+    rclcpp::Time detect_station_time_;
+    bool cmd_enable_;
+    
+    // 充电检测相关
+    bool detect_charging_voltage_;
+    rclcpp::Time detect_charging_voltage_time_;
+    std::string last_mode_;
+    
+    // ==================== 私有方法 ====================
+    
+    // 状态机初始化
+    void initializeTransitionTable();
+    void executeEvent(const std::string &event);
+    void checkStateTransition(const std::string &event);
+    
+    // 状态转移函数
+    void transitionToWalking();
+    void transitionToSearching();
+    void transitionToCharging();
+    void transitionToReady();
+    
+    // 定时器相关
+    void startSearchTimeoutTimer();
+    void stopSearchTimeoutTimer();
+    void searchTimeoutCallback();
+    
+    // 事件处理函数
+    void handleIpadPhoneStart();
+    void handleIpadPhoneCancel();
+    void handleBluetoothDisconnected();
+    void handleBluetoothConnected();
+    void handleBaseStationPowerOff();
+    void handleLowBatteryWarning();
+    void handleLockVehicle();
+    void handleUnlockVehicle();
+    void handleJoystickPullBack();
+    void handleJoystickStop();
+    void handlePushStart();
+    void handlePushStop();
+    void handleBatteryStartCharging();
+    void handleBatteryStopCharging();
+    void handleBatteryFull();
+    void handleErrorCode();
+    void handleBaseStationLost();
+    void handleSearchTimeout();
+    
+    // ==================== 电池状态处理 ====================
+    void initializeBatteryMonitor();
+    void batteryStateCallback(const sensor_msgs::msg::BatteryState::SharedPtr msg);
+    void updateBatteryState(float percentage);
+    void checkBatteryThresholds();
+    void triggerLowBatteryAction();
+    
+    // ==================== 回充功能方法 ====================
+    void initializeRecharge();
+    void scanCallback(const sensor_msgs::msg::LaserScan::SharedPtr msg);
+    void onCmdEnable(const std_msgs::msg::Bool::SharedPtr msg);
+    void rechargeTimerCallback();
+    
+    // 激光数据处理
+    void processLaserScan(const sensor_msgs::msg::LaserScan::SharedPtr msg);
+    bool processSegmentData(const sensor_msgs::msg::LaserScan::SharedPtr msg, 
+                           int begin_index, int end_index);
+    void controlRechargeMotion(const RechargeResult &result);
+    void publishRechargeVisualization(const RechargeResult &result);
+    
+    // 过滤函数
+    void filterScanModify0(sensor_msgs::msg::LaserScan::SharedPtr msg);
+    void filterScanModify1(sensor_msgs::msg::LaserScan::SharedPtr msg);
+    void filterScanModify2(sensor_msgs::msg::LaserScan::SharedPtr msg);
+    void filterScanModify3(sensor_msgs::msg::LaserScan::SharedPtr msg);
+    std::pair<int, int> filterScanModify4(sensor_msgs::msg::LaserScan::SharedPtr msg);
+    
+    // 坐标转换
+    std::map<int, std::pair<double, double>> laserScanToXY(
+        const sensor_msgs::msg::LaserScan::SharedPtr scan_msg);
+    std::map<int, std::pair<double, double>> laserScanToXY1(
+        int begin_index, int end_index,
+        const sensor_msgs::msg::LaserScan::SharedPtr scan_msg);
+    
+    // 分段处理
+    std::map<int, std::tuple<int, int, double, double, double, double, double, int>> 
+    getSegments(const std::map<int, std::pair<double, double>> &xy_points_map);
+    
+    std::map<int, std::tuple<int, int, double, double, double, double, double, int>> 
+    filterSegments(const std::map<int, std::tuple<int, int, double, double, double, double, double, int>> &turn_segments);
+    
+    // 辅助函数
+    std::vector<std::pair<int, int>> getSections(
+        int begin_index, int end_index,
+        const sensor_msgs::msg::LaserScan::SharedPtr msg,
+        const std::map<int, std::pair<double, double>> &xy_list);
+    
+    Eigen::MatrixXd getPointList(const std::vector<IndexPoint> &points);
+    void procData(RechargeResult &result);
+    void rvizShowResult(const RechargeResult &result);
+    void publishFilteredScan(const sensor_msgs::msg::LaserScan::SharedPtr original_msg,
+                            const std::vector<float> &filtered_ranges);
+    sensor_msgs::msg::LaserScan::SharedPtr resetLaserData(
+        sensor_msgs::msg::LaserScan::SharedPtr msg,
+        int b_index, int e_index);
+    
+    // 工具函数
+    bool isValid(float value) const;
+    int getFixedIndex(const sensor_msgs::msg::LaserScan::SharedPtr msg, int i) const;
+    int getFixed() const { return 0; }
+    
+    // ==================== 辅助函数 ====================
+    std::string stateToString(WheelchairState state) const;
+    std::string chargingStateToString(ChargingState state) const;
+    std::string batteryStateToString(BatteryState state) const;
+    
+    void publishStateUpdate(const std::string &state_str, const std::string &message = "");
+    
+    // 模式管理
+    void setMode(const std::string &mode);
+    std::string getMode() const;
+};
+
+#endif // WHEELCHAIR_STATE_MACHINE_HPP

+ 27 - 0
recharge2/wheelchair_state_machine/package.xml

@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
+<package format="3">
+  <name>wheelchair_state_machine</name>
+  <version>0.0.0</version>
+  <description>智能轮椅自主回充系统状态机</description>
+  <maintainer email="zhangkaifeng@users.noreply.github.com">zkf</maintainer>
+  <license>Apache-2.0</license>
+
+  <buildtool_depend>ament_cmake</buildtool_depend>
+
+  <depend>rclcpp</depend>
+  <depend>std_msgs</depend>
+  <depend>geometry_msgs</depend>
+  <depend>sensor_msgs</depend>
+  <depend>visualization_msgs</depend>
+  <depend>tf2</depend>
+  <depend>tf2_geometry_msgs</depend>
+  <depend>interface</depend>
+  
+  <test_depend>ament_lint_auto</test_depend>
+  <test_depend>ament_lint_common</test_depend>
+
+  <export>
+    <build_type>ament_cmake</build_type>
+  </export>
+</package>

+ 21 - 0
recharge2/wheelchair_state_machine/src/main.cpp

@@ -0,0 +1,21 @@
+// main.cpp
+#include "wheelchair_state_machine/wheelchair_state_machine.hpp"
+#include <rclcpp/rclcpp.hpp>
+#include <memory>
+
+int main(int argc, char **argv)
+{
+    rclcpp::init(argc, argv);
+    
+    auto state_machine = std::make_shared<WheelchairStateMachine>();
+    
+    
+    // 显示系统状态
+    state_machine->displaySystemStatus();
+    
+    // 保持节点运行
+    rclcpp::spin(state_machine);
+    rclcpp::shutdown();
+    
+    return 0;
+}

+ 2366 - 0
recharge2/wheelchair_state_machine/src/wheelchair_state_machine.cpp

@@ -0,0 +1,2366 @@
+// wheelchair_state_machine.cpp
+#include "wheelchair_state_machine/wheelchair_state_machine.hpp"
+#include <rclcpp/logging.hpp>
+#include <rclcpp/qos.hpp>
+#include <tf2/LinearMath/Quaternion.h>
+#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
+#include <cmath>
+#include <limits>
+#include <deque>
+#include <numeric>
+#include <thread>
+#include <Eigen/Dense>
+
+using namespace std::chrono_literals;
+using std::placeholders::_1;
+
+// ==================== 工具类实现 ====================
+
+double RechargeTool::angleBetween(const IndexPoint &p1, const IndexPoint &p2)
+{
+    return atan2(p2.y - p1.y, p2.x - p1.x);
+}
+
+double RechargeTool::angleDiff(double a1, double a2)
+{
+    double diff = fabs(a1 - a2);
+    return std::min(diff, 2 * M_PI - diff) * 180.0 / M_PI;
+}
+
+double RechargeTool::averageAngle(const std::vector<double> &angles)
+{
+    double sin_sum = 0.0, cos_sum = 0.0;
+    for (double a : angles)
+    {
+        sin_sum += sin(a);
+        cos_sum += cos(a);
+    }
+    return atan2(sin_sum, cos_sum);
+}
+
+std::vector<std::vector<IndexPoint>> RechargeTool::splitPointsToSegments(
+    const std::vector<IndexPoint> &points,
+    double angle_threshold_deg,
+    int history_window_size)
+{
+    std::vector<std::vector<IndexPoint>> segments;
+    if (points.size() < 2)
+    {
+        segments.push_back(points);
+        return segments;
+    }
+
+    std::vector<IndexPoint> current_segment;
+    std::deque<double> angle_history;
+
+    current_segment.push_back(points[0]);
+
+    for (size_t i = 1; i < points.size(); i++)
+    {
+        const IndexPoint &p_prev = points[i - 1];
+        const IndexPoint &p_curr = points[i];
+        double curr_angle = angleBetween(p_prev, p_curr);
+
+        // 更新方向历史
+        angle_history.push_back(curr_angle);
+        if (angle_history.size() > history_window_size)
+        {
+            angle_history.pop_front();
+        }
+
+        // 若历史方向足够,开始判断角度差
+        if (angle_history.size() >= history_window_size)
+        {
+            // 计算历史方向平均值(排除当前角度)
+            std::vector<double> hist(angle_history.begin(), angle_history.end() - 1);
+            double avg_angle = averageAngle(hist);
+            double diff = angleDiff(avg_angle, curr_angle);
+
+            if (diff > angle_threshold_deg)
+            {
+                // 发生显著拐弯,分段
+                current_segment.push_back(p_prev);
+                segments.push_back(current_segment);
+
+                // 开始新分段,以当前点为起点
+                current_segment.clear();
+                current_segment.push_back(p_curr);
+
+                // 重置方向历史,仅保留当前角度
+                angle_history.clear();
+                angle_history.push_back(curr_angle);
+            }
+        }
+    }
+
+    current_segment.push_back(points.back());
+    segments.push_back(current_segment);
+
+    return segments;
+}
+
+std::pair<double, double> RechargeTool::calculateAngleBisector(double k1, double b1, double k2, double b2)
+{
+    // 计算两条直线的夹角
+    double angle1 = atan(k1);
+    double angle2 = atan(k2);
+
+    // 计算两条直线之间的夹角
+    double delta_angle = fabs(angle1 - angle2);
+
+    // 计算中线角度
+    double bisector_angle1;
+    if (angle1 < angle2)
+    {
+        bisector_angle1 = angle1 + delta_angle / 2;
+    }
+    else
+    {
+        bisector_angle1 = angle2 + delta_angle / 2;
+    }
+
+    // 处理平行线(无交点)的情况
+    if (fabs(k1 - k2) < 1e-10)
+    {
+        // 平行线没有交点,返回原直线
+        return {k1, b1};
+    }
+
+    // 计算交点坐标
+    double x_intersect = (b2 - b1) / (k1 - k2);
+    double y_intersect = k1 * x_intersect + b1;
+
+    // 计算平分线的斜率
+    double k3 = tan(bisector_angle1);
+    double b3 = y_intersect - k3 * x_intersect;
+
+    return {k3, b3};
+}
+
+std::pair<double, double> RechargeTool::fitLineLeastSquares(const Eigen::MatrixXd &points)
+{
+    int n = points.rows();
+    Eigen::VectorXd x = points.col(0);
+    Eigen::VectorXd y = points.col(1);
+
+    // 最小二乘法拟合 y = kx + b
+    double x_mean = x.mean();
+    double y_mean = y.mean();
+
+    double numerator = 0.0;
+    double denominator = 0.0;
+
+    for (int i = 0; i < n; i++)
+    {
+        numerator += (x(i) - x_mean) * (y(i) - y_mean);
+        denominator += (x(i) - x_mean) * (x(i) - x_mean);
+    }
+
+    double k = numerator / denominator;
+    double b = y_mean - k * x_mean;
+
+    return {k, b};
+}
+
+std::pair<double, double> RechargeTool::computeDockingVelocity(double dx, double dy, double yaw_error)
+{
+    double max_linear_speed = 0.12;
+    double max_angular_speed = 0.16;
+
+    double Kp_linear = 0.5;
+    double Kp_angular = 0.5;
+    double Kp_lateral = 0.6;
+
+    // 线速度控制
+    double linear_velocity = Kp_linear * std::max(0.1, dx - 0.6);
+    linear_velocity = std::clamp(linear_velocity, -max_linear_speed, max_linear_speed);
+    linear_velocity = std::max(0.08, linear_velocity);
+
+    // 角速度控制
+    double angular_velocity = Kp_angular * yaw_error + Kp_lateral * dy;
+    angular_velocity = std::clamp(angular_velocity, -max_angular_speed, max_angular_speed);
+
+    return {-linear_velocity, -angular_velocity};
+}
+
+std::pair<double, double> RechargeTool::simpleFitLine(const std::vector<IndexPoint> &points)
+{
+    if (points.size() < 2)
+    {
+        return {0.0, 0.0};
+    }
+
+    double sum_x = 0.0, sum_y = 0.0, sum_xy = 0.0, sum_xx = 0.0;
+    int n = points.size();
+
+    for (const auto &p : points)
+    {
+        sum_x += p.x;
+        sum_y += p.y;
+        sum_xy += p.x * p.y;
+        sum_xx += p.x * p.x;
+    }
+
+    double k = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
+    double b = (sum_y - k * sum_x) / n;
+
+    return {k, b};
+}
+
+// ==================== 状态机实现 ====================
+
+WheelchairStateMachine::WheelchairStateMachine()
+    : Node("wheelchair_state_machine"),
+      current_state_(WheelchairState::READY),
+      interface_active_(false),
+      recharge_active_(false),
+      charging_state_(ChargingState::IDLE),
+      battery_state_(BatteryState::NORMAL),
+      battery_percentage_(100.0),
+      low_battery_threshold_(20.0),      // 默认低电量阈值20%
+      critical_battery_threshold_(10.0), // 默认严重低电量阈值10%
+      frame_index_(0),
+      cmd_enable_(false),
+      detect_charging_voltage_(false),
+      last_mode_("")
+{
+    // 声明电池阈值参数
+    this->declare_parameter<float>("low_battery_threshold", 20.0);
+    this->declare_parameter<float>("critical_battery_threshold", 10.0);
+
+    // 获取电池阈值参数
+    low_battery_threshold_ = this->get_parameter("low_battery_threshold").as_double();
+    critical_battery_threshold_ = this->get_parameter("critical_battery_threshold").as_double();
+
+    // 初始化状态转移表
+    initializeTransitionTable();
+
+    // 创建状态发布者
+    state_publisher_ = this->create_publisher<std_msgs::msg::String>(
+        "wheelchair/state", 10);
+
+    cmd_vel_publisher_ = this->create_publisher<geometry_msgs::msg::Twist>(
+        "cmd_vel", 10);
+
+    // 初始化电池监控
+    initializeBatteryMonitor();
+
+    // 初始化回充系统
+    initializeRecharge();
+
+    RCLCPP_INFO(this->get_logger(), "智能轮椅状态机(集成回充功能)已启动");
+    RCLCPP_INFO(this->get_logger(), "当前状态: %s", getCurrentState().c_str());
+    RCLCPP_INFO(this->get_logger(), "电池阈值 - 低电量: %.1f%%, 严重低电量: %.1f%%",
+                low_battery_threshold_, critical_battery_threshold_);
+}
+
+void WheelchairStateMachine::initializeBatteryMonitor()
+{
+    // 创建电池状态订阅者
+    battery_sub_ = this->create_subscription<sensor_msgs::msg::BatteryState>(
+        "battery_state", 10,
+        std::bind(&WheelchairStateMachine::batteryStateCallback, this, _1));
+
+    RCLCPP_INFO(this->get_logger(), "电池状态监控已初始化");
+}
+
+void WheelchairStateMachine::initializeRecharge()
+{
+    // 声明参数
+    this->declare_parameter<double>("distance_threshold", 0.5);
+    this->declare_parameter<int>("fit_window_size", 10);
+    this->declare_parameter<double>("fit_residual_threshold", 0.09);
+
+    // 获取参数
+    distance_threshold_ = this->get_parameter("distance_threshold").as_double();
+    fit_window_size_ = this->get_parameter("fit_window_size").as_int();
+    fit_residual_threshold_ = this->get_parameter("fit_residual_threshold").as_double();
+
+    // QoS配置
+    auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10))
+                           .best_effort()
+                           .durability_volatile();
+
+    // 创建激光雷达订阅者
+    scan_sub_ = this->create_subscription<sensor_msgs::msg::LaserScan>(
+        "/scan/back", qos_profile,
+        std::bind(&WheelchairStateMachine::scanCallback, this, _1));
+
+    // 创建控制发布者
+    pub_ctrl_ = this->create_publisher<geometry_msgs::msg::Twist>("/cmd_vel_raw", 10);
+
+    // 创建命令使能订阅者
+    sub_cmd_enable_ = this->create_subscription<std_msgs::msg::Bool>(
+        "/recharge/cmd_enable", 10,
+        std::bind(&WheelchairStateMachine::onCmdEnable, this, _1));
+
+    // 创建可视化发布者
+    pub_rviz_ = this->create_publisher<visualization_msgs::msg::MarkerArray>(
+        "/line_marker_array", 10);
+
+    // 创建过滤后的激光数据发布者
+    pub_filtered_ = this->create_publisher<sensor_msgs::msg::LaserScan>("/scan/out", 10);
+    pub_line_ = this->create_publisher<sensor_msgs::msg::LaserScan>("/scan/out_line", 10);
+
+    // 初始化控制消息
+    ctrl_twist_.linear.x = 0.0;
+    ctrl_twist_.angular.z = 0.0;
+    ctrl_twist_time_ = this->now();
+
+    // 创建回充定时器
+    recharge_timer_ = this->create_wall_timer(
+        100ms, std::bind(&WheelchairStateMachine::rechargeTimerCallback, this));
+
+    detect_station_time_ = this->now() - rclcpp::Duration(100.0, 0);
+    detect_charging_voltage_time_ = this->now();
+}
+
+// ==================== 电池状态处理 ====================
+
+void WheelchairStateMachine::batteryStateCallback(const sensor_msgs::msg::BatteryState::SharedPtr msg)
+{
+    // 更新电池百分比
+    if (msg->percentage >= 0.0 && msg->percentage <= 1.0)
+    {
+        battery_percentage_ = msg->percentage * 100.0;
+    }
+    else if (msg->voltage > 0.0)
+    {
+        // 如果percentage无效,根据电压估算(示例)
+        float voltage = msg->voltage;
+        // 简单线性估算:假设12V为满电,10V为没电
+        battery_percentage_ = std::clamp((voltage - 10.0f) / 2.0f * 100.0f, 0.0f, 100.0f);
+    }
+    else
+    {
+        battery_percentage_ = 100.0f; // 默认值
+    }
+
+    // 检查充电状态
+    if (msg->power_supply_status == sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_CHARGING)
+    {
+        // 如果正在充电,更新状态
+        if (current_state_ != WheelchairState::CHARGING)
+        {
+            RCLCPP_INFO(this->get_logger(), "检测到电池正在充电");
+            handleBatteryStartCharging();
+        }
+        battery_state_ = BatteryState::CHARGING;
+    }
+    else if (msg->power_supply_status == sensor_msgs::msg::BatteryState::POWER_SUPPLY_STATUS_FULL)
+    {
+        // 电池已充满
+        if (current_state_ == WheelchairState::CHARGING)
+        {
+            RCLCPP_INFO(this->get_logger(), "电池已充满");
+            handleBatteryFull();
+        }
+        battery_state_ = BatteryState::FULL;
+    }
+    else
+    {
+        // 更新电池状态
+        updateBatteryState(battery_percentage_);
+    }
+
+    // 检查电池阈值并触发相应动作
+    checkBatteryThresholds();
+
+    // 定期记录电池状态(每10%记录一次)
+    static int last_logged_percentage = -1;
+    int current_10percent = static_cast<int>(battery_percentage_ / 10.0);
+    if (current_10percent != last_logged_percentage)
+    {
+        RCLCPP_INFO(this->get_logger(), "电池状态: %.1f%% [%s]",
+                    battery_percentage_, batteryStateToString(battery_state_).c_str());
+        last_logged_percentage = current_10percent;
+    }
+}
+
+void WheelchairStateMachine::updateBatteryState(float percentage)
+{
+    BatteryState old_state = battery_state_;
+
+    if (percentage <= critical_battery_threshold_)
+    {
+        battery_state_ = BatteryState::CRITICAL;
+    }
+    else if (percentage <= low_battery_threshold_)
+    {
+        battery_state_ = BatteryState::LOW;
+    }
+    else
+    {
+        battery_state_ = BatteryState::NORMAL;
+    }
+
+    // 状态变化时记录
+    if (old_state != battery_state_)
+    {
+        RCLCPP_WARN(this->get_logger(), "电池状态变化: %s -> %s (%.1f%%)",
+                    batteryStateToString(old_state).c_str(),
+                    batteryStateToString(battery_state_).c_str(),
+                    percentage);
+    }
+}
+
+void WheelchairStateMachine::checkBatteryThresholds()
+{
+    static bool low_battery_triggered = false;
+    static bool critical_battery_triggered = false;
+
+    // 检查低电量警告
+    if (battery_state_ == BatteryState::LOW && !low_battery_triggered)
+    {
+        RCLCPP_WARN(this->get_logger(), "低电量警告: %.1f%% (阈值: %.1f%%)",
+                    battery_percentage_, low_battery_threshold_);
+        handleLowBatteryWarning();
+        low_battery_triggered = true;
+        critical_battery_triggered = false; // 重置严重低电量触发
+    }
+
+    // 检查严重低电量警告
+    if (battery_state_ == BatteryState::CRITICAL && !critical_battery_triggered)
+    {
+        RCLCPP_ERROR(this->get_logger(), "严重低电量警告: %.1f%% (阈值: %.1f%%)",
+                     battery_percentage_, critical_battery_threshold_);
+        triggerLowBatteryAction();
+        critical_battery_triggered = true;
+    }
+
+    // 如果电池恢复到正常水平,重置触发标志
+    if (battery_state_ == BatteryState::NORMAL)
+    {
+        low_battery_triggered = false;
+        critical_battery_triggered = false;
+    }
+}
+
+void WheelchairStateMachine::triggerLowBatteryAction()
+{
+    // 根据当前状态采取不同行动
+    switch (current_state_)
+    {
+    case WheelchairState::READY:
+        // 在就绪状态下,自动启动回充搜索
+        RCLCPP_WARN(this->get_logger(), "严重低电量,自动启动回充搜索");
+        if (handleEvent("low_battery_warning"))
+        {
+            RCLCPP_INFO(this->get_logger(), "已自动启动回充搜索");
+        }
+        break;
+
+    case WheelchairState::WALKING:
+        // 在行走状态下,发出警告但不自动切换
+        RCLCPP_WARN(this->get_logger(), "严重低电量警告,请尽快启动回充");
+        publishStateUpdate("CRITICAL_BATTERY_WARNING", "请尽快启动回充");
+        break;
+
+    case WheelchairState::SEARCHING:
+        // 已经在搜索中,提高搜索优先级
+        RCLCPP_WARN(this->get_logger(), "严重低电量,提高回充搜索优先级");
+        publishStateUpdate("CRITICAL_BATTERY_PRIORITY", "提高回充搜索优先级");
+        break;
+
+    case WheelchairState::CHARGING:
+        // 已经在充电中,继续充电
+        RCLCPP_INFO(this->get_logger(), "严重低电量,正在充电中");
+        break;
+    }
+}
+
+// ==================== 状态机核心方法 ====================
+
+void WheelchairStateMachine::initializeTransitionTable()
+{
+    transition_table_.clear();
+
+    // 1. ipad&phone界面启动: 就绪中✅, 其他❌
+    std::map<WheelchairState, bool> ipad_start_perms = {
+        {WheelchairState::READY, true},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["ipad_phone_interface_start"] = ipad_start_perms;
+
+    // 2. iPad&phone界面取消: 所有状态✅
+    std::map<WheelchairState, bool> ipad_cancel_perms = {
+        {WheelchairState::READY, true},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["ipad_phone_interface_cancel"] = ipad_cancel_perms;
+
+    // 3. 蓝牙断开: 就绪中❌, 其他✅
+    std::map<WheelchairState, bool> bluetooth_disconnect_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["bluetooth_disconnected"] = bluetooth_disconnect_perms;
+
+    // 4. 蓝牙已连接: 就绪中✅, 其他❌
+    std::map<WheelchairState, bool> bluetooth_connect_perms = {
+        {WheelchairState::READY, true},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["bluetooth_connected"] = bluetooth_connect_perms;
+
+    // 5. 基站断电: 就绪中❌, 其他✅
+    std::map<WheelchairState, bool> base_power_off_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["base_station_power_off"] = base_power_off_perms;
+
+    // 6. 低电量警告: 就绪中✅, 其他❌
+    std::map<WheelchairState, bool> low_battery_perms = {
+        {WheelchairState::READY, true},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["low_battery_warning"] = low_battery_perms;
+
+    // 7. 锁车: 就绪中✅, 其他❌
+    std::map<WheelchairState, bool> lock_vehicle_perms = {
+        {WheelchairState::READY, true},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["lock_vehicle"] = lock_vehicle_perms;
+
+    // 8. 解锁: 行走中✅, 其他❌
+    std::map<WheelchairState, bool> unlock_vehicle_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["unlock_vehicle"] = unlock_vehicle_perms;
+
+    // 9. 摇杆后拉: 搜索中✅, 其他❌
+    std::map<WheelchairState, bool> joystick_pull_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["joystick_pull_back"] = joystick_pull_perms;
+
+    // 10. 摇杆停止: 行走中✅, 搜索中✅, 其他❌
+    std::map<WheelchairState, bool> joystick_stop_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["joystick_stop"] = joystick_stop_perms;
+
+    // 11. 推行启动: 充电中✅, 其他❌
+    std::map<WheelchairState, bool> push_start_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["push_start"] = push_start_perms;
+
+    // 12. 推行关闭: 行走中✅, 搜索中✅, 其他❌
+    std::map<WheelchairState, bool> push_stop_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["push_stop"] = push_stop_perms;
+
+    // 13. 电池-开始充电: 搜索中✅, 充电中✅, 其他❌
+    std::map<WheelchairState, bool> battery_start_charging_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["battery_start_charging"] = battery_start_charging_perms;
+
+    // 14. 电池-断开充电: 充电中✅, 其他❌
+    std::map<WheelchairState, bool> battery_stop_charging_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["battery_stop_charging"] = battery_stop_charging_perms;
+
+    // 15. 电池-电量满: 充电中✅, 其他❌
+    std::map<WheelchairState, bool> battery_full_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["battery_full"] = battery_full_perms;
+
+    // 16. 错误码处理: 所有状态✅
+    std::map<WheelchairState, bool> error_code_perms = {
+        {WheelchairState::READY, true},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, true}};
+    transition_table_["error_code_handling"] = error_code_perms;
+
+    // 17. 基站检测丢失: 行走中✅, 其他❌
+    std::map<WheelchairState, bool> base_station_lost_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, true},
+        {WheelchairState::SEARCHING, false},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["base_station_lost"] = base_station_lost_perms;
+
+    // 18. 搜索30s超时: 搜索中✅, 其他❌
+    std::map<WheelchairState, bool> search_timeout_perms = {
+        {WheelchairState::READY, false},
+        {WheelchairState::WALKING, false},
+        {WheelchairState::SEARCHING, true},
+        {WheelchairState::CHARGING, false}};
+    transition_table_["search_30s_timeout"] = search_timeout_perms;
+
+    RCLCPP_INFO(this->get_logger(), "状态转移表已初始化完成");
+}
+
+bool WheelchairStateMachine::handleEvent(const std::string &event)
+{
+    if (transition_table_.find(event) == transition_table_.end())
+    {
+        RCLCPP_WARN(this->get_logger(), "未知事件: %s", event.c_str());
+        return false;
+    }
+
+    std::map<WheelchairState, bool> state_permissions = transition_table_[event];
+
+    if (state_permissions.find(current_state_) != state_permissions.end() &&
+        state_permissions[current_state_])
+    {
+        executeEvent(event);
+        return true;
+    }
+    else
+    {
+        RCLCPP_WARN(this->get_logger(),
+                    "事件 '%s' 在当前状态 '%s' 下不允许执行",
+                    event.c_str(),
+                    getCurrentState().c_str());
+        return false;
+    }
+}
+
+void WheelchairStateMachine::executeEvent(const std::string &event)
+{
+    RCLCPP_INFO(this->get_logger(), "执行事件: %s", event.c_str());
+
+    if (event == "ipad_phone_interface_start")
+    {
+        handleIpadPhoneStart();
+    }
+    else if (event == "ipad_phone_interface_cancel")
+    {
+        handleIpadPhoneCancel();
+    }
+    else if (event == "bluetooth_disconnected")
+    {
+        handleBluetoothDisconnected();
+    }
+    else if (event == "bluetooth_connected")
+    {
+        handleBluetoothConnected();
+    }
+    else if (event == "base_station_power_off")
+    {
+        handleBaseStationPowerOff();
+    }
+    else if (event == "low_battery_warning")
+    {
+        handleLowBatteryWarning();
+    }
+    else if (event == "lock_vehicle")
+    {
+        handleLockVehicle();
+    }
+    else if (event == "unlock_vehicle")
+    {
+        handleUnlockVehicle();
+    }
+    else if (event == "joystick_pull_back")
+    {
+        handleJoystickPullBack();
+    }
+    else if (event == "joystick_stop")
+    {
+        handleJoystickStop();
+    }
+    else if (event == "push_start")
+    {
+        handlePushStart();
+    }
+    else if (event == "push_stop")
+    {
+        handlePushStop();
+    }
+    else if (event == "battery_start_charging")
+    {
+        handleBatteryStartCharging();
+    }
+    else if (event == "battery_stop_charging")
+    {
+        handleBatteryStopCharging();
+    }
+    else if (event == "battery_full")
+    {
+        handleBatteryFull();
+    }
+    else if (event == "error_code_handling")
+    {
+        handleErrorCode();
+    }
+    else if (event == "base_station_lost")
+    {
+        handleBaseStationLost();
+    }
+    else if (event == "search_30s_timeout")
+    {
+        handleSearchTimeout();
+    }
+
+    // 根据事件可能触发状态转移
+    checkStateTransition(event);
+}
+
+void WheelchairStateMachine::checkStateTransition(const std::string &event)
+{
+    // 根据事件检查是否需要状态转移
+    switch (current_state_)
+    {
+    case WheelchairState::READY:
+        if (event == "low_battery_warning" || event == "ipad_phone_interface_start")
+        {
+            transitionToSearching();
+        }
+        break;
+
+    case WheelchairState::WALKING:
+        if (event == "joystick_stop" || event == "push_stop" || event == "lock_vehicle")
+        {
+            transitionToReady();
+        }
+        else if (event == "base_station_lost")
+        {
+            transitionToSearching();
+        }
+        else if (event == "battery_start_charging")
+        {
+            transitionToCharging();
+        }
+        break;
+
+    case WheelchairState::SEARCHING:
+        if (event == "search_30s_timeout" || event == "base_station_lost")
+        {
+            transitionToReady();
+        }
+        else if (event == "battery_start_charging" || getChargingVoltage())
+        {
+            transitionToCharging();
+        }
+        break;
+
+    case WheelchairState::CHARGING:
+        if (event == "battery_full" ||
+            event == "battery_stop_charging" ||
+            event == "base_station_power_off")
+        {
+            transitionToReady();
+        }
+        break;
+    }
+}
+
+std::string WheelchairStateMachine::getCurrentState() const
+{
+    return stateToString(current_state_);
+}
+
+void WheelchairStateMachine::setState(WheelchairState new_state)
+{
+    std::string old_state = getCurrentState();
+    current_state_ = new_state;
+
+    RCLCPP_INFO(this->get_logger(),
+                "状态转移: %s -> %s",
+                old_state.c_str(),
+                getCurrentState().c_str());
+
+    // 发布状态话题
+    auto msg = std_msgs::msg::String();
+    msg.data = getCurrentState();
+    state_publisher_->publish(msg);
+}
+
+// ==================== 状态转移函数 ====================
+
+void WheelchairStateMachine::transitionToWalking()
+{
+    if (current_state_ == WheelchairState::READY ||
+        current_state_ == WheelchairState::SEARCHING)
+    {
+        setState(WheelchairState::WALKING);
+        RCLCPP_INFO(this->get_logger(), "轮椅开始行走");
+
+        // 发布状态通知
+        publishStateUpdate("WHEELCHAIR_WALKING");
+    }
+}
+
+void WheelchairStateMachine::transitionToSearching()
+{
+    if (current_state_ == WheelchairState::READY)
+    {
+        setState(WheelchairState::SEARCHING);
+        RCLCPP_INFO(this->get_logger(), "开始搜索充电站");
+
+        // 激活回充系统
+        recharge_active_ = true;
+        charging_state_ = ChargingState::NAVIGATING;
+        last_mode_ = "recharge";
+
+        // 发布搜索开始通知
+        publishStateUpdate("SEARCH_CHARGING_STATION_START");
+
+        // 启动30秒超时定时器
+        startSearchTimeoutTimer();
+    }
+}
+
+void WheelchairStateMachine::transitionToCharging()
+{
+    if (current_state_ == WheelchairState::SEARCHING || current_state_ == WheelchairState::WALKING)
+    {
+        setState(WheelchairState::CHARGING);
+        RCLCPP_INFO(this->get_logger(), "开始充电");
+
+        // 停止运动
+        ctrl_twist_.linear.x = 0.0;
+        ctrl_twist_.angular.z = 0.0;
+        ctrl_twist_time_ = this->now();
+
+        // 停止搜索超时定时器
+        stopSearchTimeoutTimer();
+
+        // 更新充电状态
+        charging_state_ = ChargingState::CHARGING;
+
+        // 设置充电电压检测
+        setChargingVoltage(true);
+
+        // 发布充电开始通知
+        publishStateUpdate("CHARGING_START");
+    }
+}
+
+void WheelchairStateMachine::transitionToReady()
+{
+    // 可以从所有状态返回到就绪状态
+    std::string previous_state = getCurrentState();
+
+    // 停止回充系统
+    recharge_active_ = false;
+    charging_state_ = ChargingState::IDLE;
+    last_mode_ = "";
+
+    // 停止所有运动
+    ctrl_twist_.linear.x = 0.0;
+    ctrl_twist_.angular.z = 0.0;
+    ctrl_twist_time_ = this->now();
+
+    // 停止定时器
+    stopSearchTimeoutTimer();
+
+    setState(WheelchairState::READY);
+
+    // 根据之前的状态执行清理操作
+    if (previous_state == "搜索中")
+    {
+        RCLCPP_INFO(this->get_logger(), "停止搜索,返回到就绪状态");
+    }
+    else if (previous_state == "充电中")
+    {
+        RCLCPP_INFO(this->get_logger(), "停止充电,返回到就绪状态");
+        detect_charging_voltage_ = false;
+    }
+    else if (previous_state == "行走中")
+    {
+        RCLCPP_INFO(this->get_logger(), "停止行走,返回到就绪状态");
+    }
+
+    RCLCPP_INFO(this->get_logger(), "返回到就绪状态");
+
+    // 发布就绪状态通知
+    publishStateUpdate("WHEELCHAIR_READY");
+}
+
+// ==================== 定时器相关函数 ====================
+
+void WheelchairStateMachine::startSearchTimeoutTimer()
+{
+    // 如果已经有定时器,先取消
+    if (search_timeout_timer_)
+    {
+        search_timeout_timer_->cancel();
+    }
+
+    // 创建30秒定时器
+    search_timeout_timer_ = this->create_wall_timer(
+        std::chrono::seconds(30),
+        [this]()
+        {
+            this->searchTimeoutCallback();
+        });
+
+    RCLCPP_INFO(this->get_logger(), "搜索超时定时器已启动");
+}
+
+void WheelchairStateMachine::stopSearchTimeoutTimer()
+{
+    if (search_timeout_timer_)
+    {
+        search_timeout_timer_->cancel();
+        search_timeout_timer_.reset();
+        RCLCPP_INFO(this->get_logger(), "搜索超时定时器已停止");
+    }
+}
+
+void WheelchairStateMachine::searchTimeoutCallback()
+{
+    if (current_state_ == WheelchairState::SEARCHING)
+    {
+        RCLCPP_WARN(this->get_logger(), "搜索充电站超时30秒");
+        handleSearchTimeout();
+    }
+}
+
+// ==================== iPad事件处理 ====================
+
+void WheelchairStateMachine::handleIpadPhoneStart()
+{
+    RCLCPP_INFO(this->get_logger(), "处理iPad自主回充按钮点击");
+
+    // 1. 检查当前状态
+    std::string current_state_str = getCurrentState();
+    RCLCPP_INFO(this->get_logger(), "当前状态: %s", current_state_str.c_str());
+
+    // 2. 用户通过iPad点击了自主回充按钮
+    RCLCPP_INFO(this->get_logger(), "用户通过iPad启动自主回充功能");
+
+    // 3. 发布自主回充启动通知
+    publishStateUpdate("AUTO_RECHARGE_INITIATED_BY_IPAD");
+
+    // 4. 开始搜索充电站
+    if (current_state_ == WheelchairState::READY)
+    {
+        // 直接进入搜索状态
+        transitionToSearching();
+
+        // 发布搜索开始的通知
+        publishStateUpdate("AUTO_RECHARGE_SEARCH_STARTED");
+
+        RCLCPP_INFO(this->get_logger(), "iPad自主回充已启动,开始搜索充电站");
+
+        // 显示系统状态
+        displaySystemStatus();
+    }
+    else
+    {
+        RCLCPP_WARN(this->get_logger(), "当前不是就绪状态(%s),无法启动自主回充",
+                    current_state_str.c_str());
+
+        // 发布错误通知
+        publishStateUpdate("AUTO_RECHARGE_FAILED_NOT_READY");
+    }
+}
+
+void WheelchairStateMachine::handleIpadPhoneCancel()
+{
+    RCLCPP_INFO(this->get_logger(), "处理iPad自主回充取消");
+
+    // 记录取消前的状态
+    std::string previous_state = getCurrentState();
+
+    // 根据当前状态进行不同的取消处理
+    switch (current_state_)
+    {
+    case WheelchairState::SEARCHING:
+    {
+        RCLCPP_INFO(this->get_logger(), "取消自主回充搜索");
+
+        // 停止搜索,返回到就绪状态
+        transitionToReady();
+
+        // 发布取消通知
+        publishStateUpdate("AUTO_RECHARGE_CANCELLED_IN_SEARCH");
+        break;
+    }
+    case WheelchairState::WALKING:
+    {
+        RCLCPP_WARN(this->get_logger(), "在行走状态下取消自主回充");
+        // 行走状态下取消,保持行走状态
+        break;
+    }
+    default:
+        break;
+    }
+
+    // 发布取消通知
+    publishStateUpdate("AUTO_RECHARGE_CANCELLED");
+
+    RCLCPP_INFO(this->get_logger(), "自主回充已取消,前状态: %s", previous_state.c_str());
+}
+
+// ==================== 电池相关事件处理 ====================
+
+void WheelchairStateMachine::handleLowBatteryWarning()
+{
+    RCLCPP_INFO(this->get_logger(), "处理低电量警告 - 当前电量: %.1f%%", battery_percentage_);
+
+    // 根据当前电池状态采取行动
+    if (battery_state_ == BatteryState::CRITICAL)
+    {
+        RCLCPP_ERROR(this->get_logger(), "严重低电量! 自动启动回充搜索");
+        publishStateUpdate("CRITICAL_BATTERY_EMERGENCY", "自动启动回充");
+
+        // 紧急情况下,无论当前状态如何都尝试启动回充
+        if (current_state_ == WheelchairState::READY)
+        {
+            transitionToSearching();
+        }
+        else if (current_state_ == WheelchairState::WALKING)
+        {
+            // 如果是行走状态,先停止行走再搜索
+            RCLCPP_WARN(this->get_logger(), "严重低电量,停止行走并启动回充搜索");
+            transitionToReady();
+            rclcpp::sleep_for(std::chrono::seconds(1));
+            transitionToSearching();
+        }
+    }
+    else if (battery_state_ == BatteryState::LOW)
+    {
+        RCLCPP_WARN(this->get_logger(), "低电量警告,建议启动回充");
+        publishStateUpdate("LOW_BATTERY_WARNING", "建议启动回充");
+
+        // 如果当前是就绪状态,自动启动回充
+        if (current_state_ == WheelchairState::READY)
+        {
+            RCLCPP_INFO(this->get_logger(), "自动启动回充搜索");
+            transitionToSearching();
+        }
+    }
+}
+
+void WheelchairStateMachine::handleBatteryStartCharging()
+{
+    RCLCPP_INFO(this->get_logger(), "开始充电 - 当前电量: %.1f%%", battery_percentage_);
+
+    // 设置充电电压检测
+    setChargingVoltage(true);
+
+    // 更新电池状态
+    battery_state_ = BatteryState::CHARGING;
+
+    // 如果当前在搜索状态,转移到充电状态
+    if (current_state_ == WheelchairState::SEARCHING)
+    {
+        transitionToCharging();
+    }
+
+    // 发布充电开始通知
+    publishStateUpdate("BATTERY_START_CHARGING");
+}
+
+void WheelchairStateMachine::handleBatteryStopCharging()
+{
+    RCLCPP_INFO(this->get_logger(), "停止充电");
+    setChargingVoltage(false);
+
+    // 更新电池状态
+    if (battery_percentage_ >= 95.0)
+    {
+        battery_state_ = BatteryState::FULL;
+    }
+    else
+    {
+        updateBatteryState(battery_percentage_);
+    }
+}
+
+void WheelchairStateMachine::handleBatteryFull()
+{
+    RCLCPP_INFO(this->get_logger(), "电池已充满 - 电量: %.1f%%", battery_percentage_);
+
+    // 更新电池状态
+    battery_state_ = BatteryState::FULL;
+
+    // 停止充电,返回就绪状态
+    if (current_state_ == WheelchairState::CHARGING)
+    {
+        transitionToReady();
+    }
+
+    publishStateUpdate("BATTERY_FULL");
+}
+
+// ==================== 其他事件处理函数 ====================
+
+void WheelchairStateMachine::handleBluetoothDisconnected()
+{
+    RCLCPP_INFO(this->get_logger(), "处理蓝牙断开");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleBluetoothConnected()
+{
+    RCLCPP_INFO(this->get_logger(), "处理蓝牙连接成功");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleBaseStationPowerOff()
+{
+    RCLCPP_INFO(this->get_logger(), "处理基站断电");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleLockVehicle()
+{
+    RCLCPP_INFO(this->get_logger(), "处理锁车操作");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleUnlockVehicle()
+{
+    RCLCPP_INFO(this->get_logger(), "处理解锁操作");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleJoystickPullBack()
+{
+    RCLCPP_INFO(this->get_logger(), "处理摇杆后拉操作");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleJoystickStop()
+{
+    RCLCPP_INFO(this->get_logger(), "处理摇杆停止操作");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handlePushStart()
+{
+    RCLCPP_INFO(this->get_logger(), "处理推行启动");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handlePushStop()
+{
+    RCLCPP_INFO(this->get_logger(), "处理推行停止");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleErrorCode()
+{
+    RCLCPP_INFO(this->get_logger(), "处理错误码");
+    // 实现具体逻辑
+}
+
+void WheelchairStateMachine::handleBaseStationLost()
+{
+    RCLCPP_INFO(this->get_logger(), "基站检测丢失");
+
+    // 发布基站丢失通知
+    publishStateUpdate("CHARGING_STATION_LOST");
+
+    // 根据状态处理基站丢失
+    if (current_state_ == WheelchairState::WALKING)
+    {
+        // 行走中检测到基站丢失,进入搜索状态
+        transitionToSearching();
+    }
+    else if (current_state_ == WheelchairState::SEARCHING)
+    {
+        // 搜索中检测到基站丢失,返回就绪状态
+        transitionToReady();
+    }
+}
+
+void WheelchairStateMachine::handleSearchTimeout()
+{
+    RCLCPP_INFO(this->get_logger(), "搜索30秒超时");
+
+    // 发布超时通知
+    publishStateUpdate("SEARCH_CHARGING_STATION_TIMEOUT");
+
+    // 根据状态转移表,搜索超时应返回就绪状态
+    if (current_state_ == WheelchairState::SEARCHING)
+    {
+        transitionToReady();
+    }
+}
+
+// ==================== 回充功能实现 ====================
+
+void WheelchairStateMachine::onCmdEnable(const std_msgs::msg::Bool::SharedPtr msg)
+{
+    bool new_enable = msg->data;
+    
+    // 如果从false变为true,启动回充
+    if (!cmd_enable_ && new_enable) {
+        RCLCPP_INFO(this->get_logger(), "收到回充启动命令");
+        handleEvent("ipad_phone_interface_start");  // 触发回充
+    }
+    // 如果从true变为false,取消回充
+    else if (cmd_enable_ && !new_enable) {
+        RCLCPP_INFO(this->get_logger(), "收到回充取消命令");
+        handleEvent("ipad_phone_interface_cancel");  // 取消回充
+    }
+    
+    cmd_enable_ = new_enable;
+    RCLCPP_INFO(this->get_logger(), "回充命令使能: %s", cmd_enable_ ? "启用" : "禁用");
+}
+
+void WheelchairStateMachine::rechargeTimerCallback()
+{
+    // 如果不是回充模式,直接返回
+    if (last_mode_ != "recharge" || !recharge_active_)
+    {
+        return;
+    }
+
+    auto now = this->now();
+    auto time_diff = (now - ctrl_twist_time_).seconds();
+
+    // 如果最近有控制命令且使能,发布控制命令
+    if (time_diff < 2.0 && cmd_enable_ &&
+        (current_state_ == WheelchairState::SEARCHING || current_state_ == WheelchairState::WALKING))
+    {
+        pub_ctrl_->publish(ctrl_twist_);
+    }
+
+    // 检查充电电压
+    if (getChargingVoltage() && current_state_ == WheelchairState::SEARCHING)
+    {
+        RCLCPP_INFO(this->get_logger(), "检测到充电电压,开始充电");
+        transitionToCharging();
+    }
+}
+
+void WheelchairStateMachine::scanCallback(const sensor_msgs::msg::LaserScan::SharedPtr msg)
+{
+    // 如果回充未激活或不是搜索状态,不处理激光数据
+    if (!recharge_active_ || current_state_ != WheelchairState::SEARCHING)
+    {
+        return;
+    }
+
+    processLaserScan(msg);
+}
+
+void WheelchairStateMachine::processLaserScan(const sensor_msgs::msg::LaserScan::SharedPtr msg)
+{
+    // 应用过滤
+    filterScanModify0(msg);
+    filterScanModify1(msg);
+    filterScanModify2(msg);
+    filterScanModify3(msg);
+
+    auto [begin_index, end_index] = filterScanModify4(msg);
+
+    if (end_index - begin_index < 2)
+    {
+        RCLCPP_DEBUG(this->get_logger(), "没有可连续性的点");
+        return;
+    }
+
+    // 处理分段数据
+    if (processSegmentData(msg, begin_index, end_index))
+    {
+        // 更新检测时间
+        detect_station_time_ = this->now();
+    }
+}
+
+bool WheelchairStateMachine::processSegmentData(const sensor_msgs::msg::LaserScan::SharedPtr msg,
+                                                int begin_index, int end_index)
+{
+    // 转换到XY坐标系
+    auto xy_list = laserScanToXY1(begin_index, end_index, msg);
+    if (xy_list.size() < 2)
+    {
+        RCLCPP_WARN(this->get_logger(), "XY转换结果点数不足: %zu", xy_list.size());
+        return false;
+    }
+
+    // 获取分段
+    auto sections = getSections(begin_index, end_index, msg, xy_list);
+    if (sections.empty())
+    {
+        RCLCPP_WARN(this->get_logger(), "没有获取到分段");
+        return false;
+    }
+
+    // 选择最近的分段
+    int nearest_index = -1;
+    double nearest_x = 100.0;
+
+    for (size_t i = 0; i < sections.size(); i++)
+    {
+        int section_start = sections[i].first;
+        int section_end = sections[i].second;
+
+        if (section_end - section_start < 10)
+            continue;
+
+        double avg_total = 0.0;
+        int avg_count = 0;
+
+        for (int index = section_start; index <= section_end; index++)
+        {
+            if (xy_list.find(index) == xy_list.end())
+                continue;
+
+            double x = msg->ranges[index];
+            avg_count++;
+            avg_total += x;
+        }
+
+        if (avg_count == 0)
+            continue;
+
+        double avg_value = avg_total / avg_count;
+
+        if (nearest_x > avg_value)
+        {
+            nearest_x = avg_value;
+            nearest_index = i;
+        }
+    }
+
+    if (nearest_index < 0)
+    {
+        return false;
+    }
+
+    begin_index = sections[nearest_index].first;
+    end_index = sections[nearest_index].second;
+
+    // 重置激光数据
+    auto filtered_msg = resetLaserData(msg, begin_index, end_index);
+
+    // 发布过滤后的数据
+    publishFilteredScan(filtered_msg, filtered_msg->ranges);
+
+    // 进一步处理
+    auto xy_list_full = laserScanToXY(filtered_msg);
+    if (xy_list_full.size() < 2)
+    {
+        RCLCPP_ERROR(this->get_logger(), "xy坐标系没有转换成功");
+        return false;
+    }
+
+    // 获取分段
+    auto turn_segments = getSegments(xy_list_full);
+    auto filtered_segments = filterSegments(turn_segments);
+
+    if (filtered_segments.empty())
+    {
+        RCLCPP_DEBUG(this->get_logger(), "未检测到有效的充电站分段");
+        return false;
+    }
+
+    // 创建结果对象
+    RechargeResult result;
+    result.frameid = frame_index_++;
+    result.laserScanData = filtered_msg;
+
+    // 处理左右点
+    int segment_index = 0;
+    for (const auto &seg : filtered_segments)
+    {
+        const auto &info = seg.second;
+        int start_idx = std::get<0>(info);
+        int end_idx = std::get<1>(info);
+
+        for (int index = start_idx; index <= end_idx; index++)
+        {
+            if (xy_list_full.find(index) == xy_list_full.end())
+                continue;
+
+            double x = xy_list_full[index].first;
+            double y = xy_list_full[index].second;
+            IndexPoint item(x, y, index);
+
+            if (segment_index == 0)
+            {
+                result.left_points.push_back(item);
+            }
+            else if (segment_index == 1)
+            {
+                result.right_points.push_back(item);
+            }
+        }
+        segment_index++;
+
+        if (segment_index >= 2)
+            break; // 只需要两个分段
+    }
+
+    // 如果成功获取左右点,进行控制
+    if (!result.left_points.empty() && !result.right_points.empty())
+    {
+        procData(result);
+        return true;
+    }
+
+    return false;
+}
+
+void WheelchairStateMachine::procData(RechargeResult &result)
+{
+    if (result.left_points.size() < 2 || result.right_points.size() < 2)
+    {
+        RCLCPP_WARN(this->get_logger(), "点数量不足,跳过拟合");
+        return;
+    }
+
+    // 拟合左边直线
+    auto [k1, b1] = RechargeTool::simpleFitLine(result.left_points);
+    RCLCPP_DEBUG(this->get_logger(), "左直线拟合: y = %.4fx + %.4f", k1, b1);
+
+    // 拟合右边直线
+    auto [k2, b2] = RechargeTool::simpleFitLine(result.right_points);
+    RCLCPP_DEBUG(this->get_logger(), "右直线拟合: y = %.4fx + %.4f", k2, b2);
+
+    // 检查是否平行
+    if (fabs(k1 - k2) < 1e-10)
+    {
+        RCLCPP_WARN(this->get_logger(), "两直线平行,无交点");
+        result.middle_point = IndexPoint(0.0, 0.0, 0);
+        result.middle_line = StraightLine(0.0, 0.0);
+        result.left_line = StraightLine(k1, b1);
+        result.right_line = StraightLine(k2, b2);
+        return;
+    }
+
+    // 计算交点
+    double x = (b2 - b1) / (k1 - k2);
+    double y = k1 * x + b1;
+    RCLCPP_DEBUG(this->get_logger(), "交点: (%.4f, %.4f)", x, y);
+
+    // 更新中间点
+    result.middle_point = IndexPoint(x, y, 0);
+
+    // 计算角平分线
+    auto [k3, b3] = RechargeTool::calculateAngleBisector(k1, b1, k2, b2);
+    RCLCPP_DEBUG(this->get_logger(), "角平分线: y = %.4fx + %.4f", k3, b3);
+
+    // 更新结果
+    result.middle_line = StraightLine(k3, b3);
+    result.left_line = StraightLine(k1, b1);
+    result.right_line = StraightLine(k2, b2);
+
+    // 控制运动
+    controlRechargeMotion(result);
+
+    // 发布可视化
+    publishRechargeVisualization(result);
+}
+
+void WheelchairStateMachine::controlRechargeMotion(const RechargeResult &result)
+{
+    // 如果检测到充电电压,停止移动
+    if (getChargingVoltage())
+    {
+        RCLCPP_INFO(this->get_logger(), "检测到充电电压,停止移动");
+        ctrl_twist_.linear.x = 0.0;
+        ctrl_twist_.angular.z = 0.0;
+        ctrl_twist_time_ = this->now();
+        return;
+    }
+
+    double dx = result.middle_point.x;
+    double dy = result.middle_point.y;
+    double k = result.middle_line.k;
+
+    RCLCPP_DEBUG(this->get_logger(), "控制参数 - dx: %.4f, dy: %.4f, k: %.4f", dx, dy, k);
+
+    // 调整 dy
+    dy = dy - 0.10;
+    RCLCPP_DEBUG(this->get_logger(), "调整后 dy: %.4f", dy);
+
+    // 计算偏航角误差
+    double yaw_error = atan(k);
+    RCLCPP_DEBUG(this->get_logger(), "偏航角误差: %.4f rad (%.2f 度)",
+                 yaw_error, yaw_error * 180.0 / M_PI);
+
+    // 计算控制速度
+    auto [linear_velocity, angular_velocity] = RechargeTool::computeDockingVelocity(dx - 0.3, dy, yaw_error);
+    RCLCPP_DEBUG(this->get_logger(), "基础控制速度 - 线速度: %.4f, 角速度: %.4f",
+                 linear_velocity, angular_velocity);
+
+    // 限制角速度
+    if (angular_velocity > 0.0)
+    {
+        angular_velocity = std::min(fabs(angular_velocity), 0.09);
+    }
+    else
+    {
+        angular_velocity = -std::min(fabs(angular_velocity), 0.09);
+    }
+
+    // 根据距离调整速度系数
+    double radio_x = 1.0;
+    double radio_z = 1.0;
+
+    if (dx > 1.0)
+    {
+        // 保持默认
+    }
+    else if (dx > 0.8)
+    {
+        radio_x = 0.7;
+    }
+    else if (dx > 0.7)
+    {
+        radio_x = 0.5;
+    }
+    else if (dx > 0.68)
+    {
+        radio_x = 0.3;
+        radio_z = 1.0;
+    }
+    else if (dx > 0.65)
+    {
+        radio_x = 0.2;
+        radio_z = 1.0;
+    }
+    else if (dx > 0.62)
+    {
+        radio_x = 0.1;
+        radio_z = 1.0;
+    }
+    else if (dx > 0.2)
+    {
+        radio_x = 0.15;
+        radio_z = 1.0;
+    }
+
+    RCLCPP_DEBUG(this->get_logger(), "距离调整系数 - radio_x: %.2f, radio_z: %.2f", radio_x, radio_z);
+
+    // 设置控制命令
+    if (dx > 0.54)
+    {
+        double final_linear_vel = linear_velocity * radio_x;
+        if (final_linear_vel > -0.04)
+        {
+            final_linear_vel = -0.04;
+        }
+
+        ctrl_twist_.linear.x = final_linear_vel;
+        ctrl_twist_.angular.z = angular_velocity * radio_z;
+        ctrl_twist_time_ = this->now();
+
+        RCLCPP_DEBUG(this->get_logger(), "控制命令 - 线速度: %.4f, 角速度: %.4f",
+                     ctrl_twist_.linear.x, ctrl_twist_.angular.z);
+    }
+    else
+    {
+        // 到达充电位置
+        RCLCPP_INFO(this->get_logger(), "到达充电位置 (dx=%.4f <= 0.54), 停止移动", dx);
+        ctrl_twist_.linear.x = 0.0;
+        ctrl_twist_.angular.z = 0.0;
+        ctrl_twist_time_ = this->now();
+
+        // 模拟检测到充电电压
+        setChargingVoltage(true);
+    }
+}
+
+// ==================== 过滤函数实现 ====================
+
+void WheelchairStateMachine::filterScanModify0(sensor_msgs::msg::LaserScan::SharedPtr msg)
+{
+    int fixed = getFixed();
+    double diff_yaw = msg->angle_increment * fixed;
+    msg->angle_min -= diff_yaw;
+    msg->angle_max -= diff_yaw;
+
+    std::vector<float> ranges = msg->ranges;
+    for (size_t i = 0; i < msg->ranges.size(); i++)
+    {
+        int index = getFixedIndex(msg, i);
+        ranges[index] = msg->ranges[i];
+    }
+    msg->ranges = ranges;
+}
+
+void WheelchairStateMachine::filterScanModify1(sensor_msgs::msg::LaserScan::SharedPtr msg)
+{
+    // 过滤角度,只接受后面90度
+    int min_index = param_.min_index;
+    int max_index = param_.max_index;
+
+    for (size_t i = 0; i < msg->ranges.size(); i++)
+    {
+        if (i < min_index || i > max_index)
+        {
+            msg->ranges[i] = std::numeric_limits<float>::infinity();
+        }
+    }
+}
+
+void WheelchairStateMachine::filterScanModify2(sensor_msgs::msg::LaserScan::SharedPtr msg)
+{
+    // 过滤距离
+    for (size_t i = 0; i < msg->ranges.size(); i++)
+    {
+        if (!isValid(msg->ranges[i]))
+            continue;
+
+        if (msg->ranges[i] < param_.min_distance ||
+            msg->ranges[i] > param_.max_distance)
+        {
+            msg->ranges[i] = std::numeric_limits<float>::infinity();
+        }
+    }
+}
+
+void WheelchairStateMachine::filterScanModify3(sensor_msgs::msg::LaserScan::SharedPtr msg)
+{
+    // 过滤强度
+    for (size_t i = 0; i < msg->ranges.size(); i++)
+    {
+        if (!isValid(msg->ranges[i]))
+            continue;
+
+        float value = msg->intensities[i];
+        if (value < param_.min_intensities || value > param_.max_intensities)
+        {
+            msg->ranges[i] = std::numeric_limits<float>::infinity();
+        }
+
+        // 再次检查距离
+        if (msg->ranges[i] < param_.min_distance ||
+            msg->ranges[i] > param_.max_distance)
+        {
+            msg->ranges[i] = std::numeric_limits<float>::infinity();
+        }
+    }
+}
+
+std::pair<int, int> WheelchairStateMachine::filterScanModify4(sensor_msgs::msg::LaserScan::SharedPtr msg)
+{
+    // 过滤连续性
+    int begin_index = 0;
+    int end_index = 0;
+    std::vector<float> ranges = msg->ranges;
+
+    // 顺序找起点
+    for (int i = param_.min_index + 1; i < param_.max_index; i++)
+    {
+        if (!isValid(ranges[i]))
+            continue;
+        if (!isValid(ranges[i - 1]))
+        {
+            begin_index = i;
+            break;
+        }
+    }
+
+    if (static_cast<int>(msg->ranges.size()) - begin_index < 10)
+    {
+        RCLCPP_ERROR(this->get_logger(), "数据太少无法继续处理");
+        return {0, 0};
+    }
+
+    // 倒序找终点
+    for (int i = param_.max_index - 2; i > 0; i--)
+    {
+        if (!isValid(ranges[i]))
+            continue;
+        if (!isValid(ranges[i + 1]))
+        {
+            end_index = i;
+            break;
+        }
+    }
+
+    if (end_index < 10)
+    {
+        return {0, 0};
+    }
+
+    // 顺序检查连续性
+    for (int i = begin_index + 1; i < end_index - 1; i++)
+    {
+        float prev = ranges[i - 1];
+        float curr = ranges[i];
+        float next = ranges[i + 1];
+
+        if (!isValid(prev) && !isValid(curr) && !isValid(next))
+        {
+            if (i - begin_index < param_.min_count)
+            {
+                begin_index = i + 1;
+            }
+            else
+            {
+                break;
+            }
+        }
+        else
+        {
+            float low = 100.0;
+            float high = 0.0;
+            for (int j = i - 1; j <= i + 1; j++)
+            {
+                if (isValid(ranges[j]))
+                {
+                    if (ranges[j] > 0.03 && ranges[j] < low)
+                    {
+                        low = ranges[j];
+                    }
+                    if (ranges[j] > high)
+                    {
+                        high = ranges[j];
+                    }
+                }
+            }
+            if (high - low > 0.08)
+            {
+                if (i - begin_index < param_.min_count)
+                {
+                    begin_index = i + 1;
+                }
+                else
+                {
+                    break;
+                }
+            }
+        }
+    }
+
+    // 倒序检查连续性
+    for (int i = end_index - 1; i > begin_index + 1; i--)
+    {
+        float prev = ranges[i - 1];
+        float curr = ranges[i];
+        float next = ranges[i + 1];
+
+        if (!isValid(prev) && !isValid(curr) && !isValid(next))
+        {
+            if (end_index - i < param_.min_count)
+            {
+                end_index = i - 1;
+            }
+            else
+            {
+                break;
+            }
+        }
+        else
+        {
+            if (isValid(next) && isValid(prev))
+            {
+                if (next - curr > 0.07)
+                {
+                    if (end_index - i < param_.min_count)
+                    {
+                        end_index = i - 1;
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    // 设置无效区域
+    for (int i = param_.min_index; i < begin_index - 1; i++)
+    {
+        msg->ranges[i] = std::numeric_limits<float>::infinity();
+    }
+    for (int i = end_index + 1; i <= param_.max_index; i++)
+    {
+        msg->ranges[i] = std::numeric_limits<float>::infinity();
+    }
+
+    return {begin_index, end_index};
+}
+
+// ==================== 坐标转换函数 ====================
+
+std::map<int, std::pair<double, double>> WheelchairStateMachine::laserScanToXY(
+    const sensor_msgs::msg::LaserScan::SharedPtr scan_msg)
+{
+    std::map<int, std::pair<double, double>> xy_points_map;
+    double angle = scan_msg->angle_min;
+
+    for (size_t i = 0; i < scan_msg->ranges.size(); i++)
+    {
+        float range_val = scan_msg->ranges[i];
+
+        // 跳过无效测量值
+        if (range_val >= scan_msg->range_min && range_val <= scan_msg->range_max)
+        {
+            // 极坐标转笛卡尔坐标
+            double x1 = range_val * cos(angle);
+            double y1 = range_val * sin(angle);
+            double x = -y1;
+            double y = x1;
+            xy_points_map[i] = {x, y};
+        }
+        angle += scan_msg->angle_increment;
+    }
+    return xy_points_map;
+}
+
+std::map<int, std::pair<double, double>> WheelchairStateMachine::laserScanToXY1(
+    int begin_index, int end_index,
+    const sensor_msgs::msg::LaserScan::SharedPtr scan_msg)
+{
+    std::map<int, std::pair<double, double>> xy_points_map;
+    double angle = scan_msg->angle_min;
+
+    for (int i = begin_index - 1; i <= end_index; i++)
+    {
+        float range_val = scan_msg->ranges[i];
+
+        if (range_val >= scan_msg->range_min && range_val <= scan_msg->range_max)
+        {
+            double x1 = range_val * cos(angle);
+            double y1 = range_val * sin(angle);
+            double x = -y1;
+            double y = x1;
+            xy_points_map[i] = {x, y};
+        }
+        angle += scan_msg->angle_increment;
+    }
+    return xy_points_map;
+}
+
+// ==================== 分段处理函数 ====================
+
+std::vector<std::pair<int, int>> WheelchairStateMachine::getSections(
+    int begin_index, int end_index,
+    const sensor_msgs::msg::LaserScan::SharedPtr msg,
+    const std::map<int, std::pair<double, double>> &xy_list)
+{
+    std::vector<std::pair<int, int>> sections;
+
+    int section_start = begin_index + 1;
+    int section_end = 0;
+
+    int last_index = -1;
+    double last_x = 0.0, last_y = 0.0;
+
+    for (int i = begin_index + 1; i < end_index - 1; i++)
+    {
+        if (xy_list.find(i) == xy_list.end())
+        {
+            continue;
+        }
+
+        auto curr = xy_list.at(i);
+        if (last_index < 0)
+        {
+            last_index = i;
+            last_x = curr.first;
+            last_y = curr.second;
+            continue;
+        }
+
+        double dx = curr.first - last_x;
+        double dy = curr.second - last_y;
+        double dist = sqrt(dx * dx + dy * dy);
+
+        if (dist > 0.05 || (i - last_index) > 2)
+        {
+            section_end = i;
+            if ((section_end - section_start) >= param_.min_count)
+            {
+                sections.emplace_back(section_start, section_end);
+            }
+            section_start = i;
+        }
+
+        last_index = i;
+        last_x = curr.first;
+        last_y = curr.second;
+    }
+
+    if (last_index > section_end + 10)
+    {
+        section_end = last_index;
+        if ((section_end - section_start) > param_.min_count)
+        {
+            sections.emplace_back(section_start, section_end);
+        }
+    }
+
+    return sections;
+}
+
+std::map<int, std::tuple<int, int, double, double, double, double, double, int>>
+WheelchairStateMachine::getSegments(const std::map<int, std::pair<double, double>> &xy_points_map)
+{
+    std::map<int, std::tuple<int, int, double, double, double, double, double, int>> turn_segments;
+
+    if (xy_points_map.empty())
+    {
+        return turn_segments;
+    }
+
+    // 将点转换为路径
+    std::vector<IndexPoint> path;
+    for (const auto &point : xy_points_map)
+    {
+        path.emplace_back(point.second.first, point.second.second, point.first);
+    }
+
+    // 使用相同的参数进行分段
+    auto segments = RechargeTool::splitPointsToSegments(path, 24.0, 4);
+
+    // 处理每个分段
+    for (size_t i = 0; i < segments.size(); i++)
+    {
+        if (segments[i].size() < 2)
+            continue;
+
+        // 获取分段的前两个点
+        const auto &s_prev = segments[i][0];
+        const auto &s_next = segments[i][1];
+
+        // 计算距离和角度
+        double dx = s_prev.x - s_next.x;
+        double dy = s_prev.y - s_next.y;
+        double distance = sqrt(dx * dx + dy * dy);
+        double line_angle = atan2(dy, dx);
+
+        int index_start = s_prev.index;
+        int index_end = s_next.index;
+
+        // 应用过滤条件
+        if (distance > 0.14 && (index_end - index_start) > 3)
+        {
+            turn_segments[index_start] = std::make_tuple(
+                index_start, // 0: 开始索引
+                index_end,   // 1: 结束索引
+                0.0,         // 2: 0.0
+                s_prev.x,    // 3: x坐标
+                s_prev.y,    // 4: y坐标
+                distance,    // 5: 距离
+                line_angle,  // 6: 线角度
+                1            // 7: 1
+            );
+        }
+    }
+
+    return turn_segments;
+}
+
+std::map<int, std::tuple<int, int, double, double, double, double, double, int>>
+WheelchairStateMachine::filterSegments(
+    const std::map<int, std::tuple<int, int, double, double, double, double, double, int>> &turn_segments)
+{
+    std::map<int, std::tuple<int, int, double, double, double, double, double, int>> new_segments;
+
+    if (turn_segments.empty() || turn_segments.size() < 2)
+    {
+        return new_segments;
+    }
+
+    bool last_line_angle_valid = false;
+    double last_line_angle = 0.0;
+    std::tuple<int, int, double, double, double, double, double, int> last_seg;
+    int last_key = -1;
+
+    // 遍历所有分段
+    for (const auto &seg : turn_segments)
+    {
+        const auto &info = seg.second;
+        double line_angle = std::get<6>(info);
+
+        if (last_line_angle_valid)
+        {
+            // 计算角度差
+            double diff_angle = line_angle - last_line_angle;
+
+            // 目标角度计算
+            double target_angle = 0.5 * M_PI * (120.0 / 90.0);
+            target_angle = M_PI - target_angle;
+            double tolerance = 0.2;
+
+            // 角度条件判断
+            if (diff_angle > (target_angle - tolerance) && diff_angle < (target_angle + tolerance))
+            {
+                // 索引检查
+                int right_start_idx = std::get<0>(info);
+                int left_end_idx = std::get<1>(last_seg);
+
+                if (right_start_idx - left_end_idx < 3)
+                {
+                    // 添加到结果
+                    new_segments[last_key] = last_seg;
+                    new_segments[seg.first] = info;
+                    break; // 找到一对就跳出循环
+                }
+            }
+        }
+
+        // 更新上一个分段信息
+        last_line_angle = line_angle;
+        last_line_angle_valid = true;
+        last_seg = info;
+        last_key = seg.first;
+    }
+
+    return new_segments;
+}
+
+// ==================== 辅助函数实现 ====================
+
+std::string WheelchairStateMachine::stateToString(WheelchairState state) const
+{
+    switch (state)
+    {
+    case WheelchairState::READY:
+        return "就绪中";
+    case WheelchairState::WALKING:
+        return "行走中";
+    case WheelchairState::SEARCHING:
+        return "搜索中";
+    case WheelchairState::CHARGING:
+        return "充电中";
+    default:
+        return "未知状态";
+    }
+}
+
+std::string WheelchairStateMachine::chargingStateToString(ChargingState state) const
+{
+    switch (state)
+    {
+    case ChargingState::IDLE:
+        return "空闲";
+    case ChargingState::NAVIGATING:
+        return "导航中";
+    case ChargingState::CHARGING:
+        return "充电中";
+    case ChargingState::UNAVAILABLE:
+        return "不可用";
+    case ChargingState::COMPLETED:
+        return "完成";
+    default:
+        return "未知";
+    }
+}
+
+std::string WheelchairStateMachine::batteryStateToString(BatteryState state) const
+{
+    switch (state)
+    {
+    case BatteryState::NORMAL:
+        return "正常";
+    case BatteryState::LOW:
+        return "低电量";
+    case BatteryState::CRITICAL:
+        return "严重低电量";
+    case BatteryState::CHARGING:
+        return "充电中";
+    case BatteryState::FULL:
+        return "充满";
+    default:
+        return "未知";
+    }
+}
+
+void WheelchairStateMachine::displaySystemStatus()
+{
+    std::string status_report = "系统状态报告:\n";
+
+    // 轮椅状态
+    status_report += "  - 轮椅状态: " + getCurrentState() + "\n";
+
+    // 电池状态
+    status_report += "  - 电池状态: " + batteryStateToString(battery_state_) +
+                     " (" + std::to_string(battery_percentage_) + "%)\n";
+
+    // 充电状态
+    status_report += "  - 充电状态: " + chargingStateToString(charging_state_) + "\n";
+
+    // 回充状态
+    if (current_state_ == WheelchairState::SEARCHING)
+    {
+        status_report += "  - 回充状态: 正在搜索充电站\n";
+    }
+    else if (current_state_ == WheelchairState::CHARGING)
+    {
+        status_report += "  - 回充状态: 正在充电\n";
+    }
+    else
+    {
+        status_report += "  - 回充状态: 待命\n";
+    }
+
+    // 控制模式
+    status_report += " - 控制模式: ";
+    status_report += (interface_active_ ? "iPad远程控制" : "本地控制");
+    status_report += "\n";
+
+    // 充电电压检测
+    status_report += "  - 充电电压: " + std::string(getChargingVoltage() ? "检测到" : "未检测") + "\n";
+
+    // 安全状态
+    status_report += "  - 安全状态: 正常\n";
+
+    RCLCPP_INFO(this->get_logger(), "\n%s", status_report.c_str());
+
+    // 发布到状态话题
+    auto status_msg = std_msgs::msg::String();
+    status_msg.data = status_report;
+    state_publisher_->publish(status_msg);
+}
+
+void WheelchairStateMachine::publishStateUpdate(const std::string &state_str, const std::string &message)
+{
+    auto msg = std_msgs::msg::String();
+    if (message.empty())
+    {
+        msg.data = state_str;
+    }
+    else
+    {
+        msg.data = state_str + ":" + message;
+    }
+    state_publisher_->publish(msg);
+}
+
+void WheelchairStateMachine::publishRechargeVisualization(const RechargeResult &result)
+{
+    auto marker_array = std::make_shared<visualization_msgs::msg::MarkerArray>();
+
+    // 添加中线标记
+    visualization_msgs::msg::Marker line_marker;
+    line_marker.header.frame_id = "laser_frame_back1";
+    line_marker.header.stamp = this->now();
+    line_marker.ns = "rotating_line";
+    line_marker.id = 0;
+    line_marker.type = visualization_msgs::msg::Marker::LINE_STRIP;
+    line_marker.action = visualization_msgs::msg::Marker::ADD;
+    line_marker.pose.orientation.w = 1.0;
+    line_marker.scale.x = 0.02;
+    line_marker.color.r = 1.0;
+    line_marker.color.a = 1.0;
+
+    // 计算线段端点
+    double k = result.middle_line.k;
+    double b = result.middle_line.b;
+    double x1 = result.middle_point.x;
+    double x2 = x1 - 1.0;
+
+    if (x1 > 10.0 || x1 < -10.0)
+    {
+        x1 = 0.5;
+        x2 = -0.5;
+    }
+
+    geometry_msgs::msg::Point p1, p2;
+    p1.x = x1;
+    p1.y = k * x1 + b;
+    p1.z = 0.0;
+    p2.x = x2;
+    p2.y = k * x2 + b;
+    p2.z = 0.0;
+
+    line_marker.points.push_back(p1);
+    line_marker.points.push_back(p2);
+    marker_array->markers.push_back(line_marker);
+
+    // 添加中间点标记
+    visualization_msgs::msg::Marker middle_marker;
+    middle_marker.header.frame_id = "laser_frame_back1";
+    middle_marker.header.stamp = this->now();
+    middle_marker.ns = "middle_point";
+    middle_marker.id = 1;
+    middle_marker.type = visualization_msgs::msg::Marker::SPHERE;
+    middle_marker.action = visualization_msgs::msg::Marker::ADD;
+    middle_marker.pose.orientation.w = 1.0;
+    middle_marker.scale.x = 0.03;
+    middle_marker.scale.y = 0.03;
+    middle_marker.color.b = 1.0;
+    middle_marker.color.a = 1.0;
+
+    middle_marker.pose.position.x = result.middle_point.x;
+    middle_marker.pose.position.y = result.middle_point.y;
+    middle_marker.pose.position.z = 0.0;
+
+    marker_array->markers.push_back(middle_marker);
+
+    // 发布标记数组
+    pub_rviz_->publish(*marker_array);
+}
+
+Eigen::MatrixXd WheelchairStateMachine::getPointList(const std::vector<IndexPoint> &points)
+{
+    Eigen::MatrixXd pts(points.size(), 2);
+
+    for (size_t i = 0; i < points.size(); i++)
+    {
+        pts(i, 0) = points[i].x;
+        pts(i, 1) = points[i].y;
+    }
+
+    return pts;
+}
+
+void WheelchairStateMachine::rvizShowResult(const RechargeResult &result)
+{
+    // 与 publishRechargeVisualization 功能相同
+    publishRechargeVisualization(result);
+}
+
+void WheelchairStateMachine::publishFilteredScan(
+    const sensor_msgs::msg::LaserScan::SharedPtr original_msg,
+    const std::vector<float> &filtered_ranges)
+{
+    auto msg = std::make_shared<sensor_msgs::msg::LaserScan>();
+    msg->header = original_msg->header;
+    msg->angle_min = original_msg->angle_min;
+    msg->angle_max = original_msg->angle_max;
+    msg->angle_increment = original_msg->angle_increment;
+    msg->time_increment = original_msg->time_increment;
+    msg->scan_time = original_msg->scan_time;
+    msg->range_min = original_msg->range_min;
+    msg->range_max = original_msg->range_max;
+    msg->ranges = filtered_ranges;
+    msg->intensities = original_msg->intensities;
+
+    pub_filtered_->publish(*msg);
+    RCLCPP_DEBUG(this->get_logger(), "已发布过滤后的激光数据");
+}
+
+sensor_msgs::msg::LaserScan::SharedPtr WheelchairStateMachine::resetLaserData(
+    sensor_msgs::msg::LaserScan::SharedPtr msg,
+    int b_index, int e_index)
+{
+    std::vector<float> filtered_ranges = msg->ranges;
+    std::vector<float> intensities = msg->intensities;
+
+    for (int i = 0; i < b_index; i++)
+    {
+        filtered_ranges[i] = std::numeric_limits<float>::infinity();
+        intensities[i] = 0.0;
+    }
+
+    for (int i = e_index; i < static_cast<int>(msg->ranges.size()); i++)
+    {
+        filtered_ranges[i] = std::numeric_limits<float>::infinity();
+        intensities[i] = 0.0;
+    }
+
+    msg->ranges = filtered_ranges;
+    msg->intensities = intensities;
+
+    return msg;
+}
+
+bool WheelchairStateMachine::isValid(float value) const
+{
+    if (fabs(value) < 0.001)
+        return false;
+    return !std::isinf(value) && !std::isnan(value);
+}
+
+int WheelchairStateMachine::getFixedIndex(const sensor_msgs::msg::LaserScan::SharedPtr msg, int i) const
+{
+    int fixed = getFixed();
+    if (fixed == 0)
+        return i;
+
+    int length = msg->ranges.size();
+    int index;
+
+    if (i >= length - fixed)
+    {
+        index = i - length + fixed;
+    }
+    else
+    {
+        index = i + fixed;
+    }
+    return index;
+}
+
+// ==================== 充电检测函数 ====================
+
+void WheelchairStateMachine::setChargingVoltage(bool value)
+{
+    detect_charging_voltage_ = value;
+    detect_charging_voltage_time_ = this->now();
+
+    if (value)
+    {
+        RCLCPP_INFO(this->get_logger(), "检测到充电电压");
+    }
+    else
+    {
+        RCLCPP_INFO(this->get_logger(), "充电电压已断开");
+    }
+}
+
+bool WheelchairStateMachine::getChargingVoltage() const
+{
+    auto now = this->now();
+    auto diff = now - detect_charging_voltage_time_;
+
+    // 如果检测到充电电压且在0.1秒内,认为有效
+    if (detect_charging_voltage_ && diff.seconds() < 0.1)
+    {
+        return true;
+    }
+    return false;
+}
+
+void WheelchairStateMachine::setMode(const std::string &mode)
+{
+    last_mode_ = mode;
+    if (last_mode_ != "recharge")
+    {
+        setChargingVoltage(false);
+    }
+}
+
+std::string WheelchairStateMachine::getMode() const
+{
+    return last_mode_;
+}
+
+// ==================== 回充控制接口 ====================
+
+void WheelchairStateMachine::startRecharge()
+{
+    if (current_state_ == WheelchairState::READY)
+    {
+        handleIpadPhoneStart();
+    }
+    else
+    {
+        RCLCPP_WARN(this->get_logger(), "当前状态无法启动回充: %s", getCurrentState().c_str());
+    }
+}
+
+void WheelchairStateMachine::cancelRecharge()
+{
+    handleIpadPhoneCancel();
+}