news 2026/9/11 18:31:01

Isaac Sim机械臂仿真实战:从URDF导入到MoveIt2规划与排错指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Isaac Sim机械臂仿真实战:从URDF导入到MoveIt2规划与排错指南

做机械臂仿真这几年,我先后在 Gazebo、CoppeliaSim 和 MuJoCo 之间来回搬家,直到被接触动力学和视觉逼真度逼到 Isaac Sim,才真正把“仿真能辅助真实部署”这件事跑通。这篇指南不是官方文档的中文复述,而是我从 Ubuntu 22.04 + ROS2 Humble + Isaac Sim 2023.1 这套组合在机械臂抓取项目里的完整实操记录——从环境搭建、URDF 转 USD、ROS2 桥接,到用 MoveIt2 做运动规划,再到我踩过的几个坑。如果你正要入手机械臂仿真,或者从 Gazebo 迁移到 Isaac Sim,这篇文章能帮你少走不少弯路。

1. 为什么机械臂仿真非要换 Isaac Sim:物理引擎与视觉保真的本质差异

1.1 从 Gazebo 迁移过来的人,到底被什么逼疯过

先说个扎心的事实:Gazebo 做机械臂运动学仿真完全够用,但一旦涉及抓取、力控、视觉感知,它的短板就很明显了。我之前在 Gazebo 里跑 Franka Panda 的夹爪抓取,物体接触瞬间的抖动、穿透、甚至弹飞都是常态。调了一天摩擦系数、接触刚度,可能第二天换了个模型尺寸又全崩了。这不是配置问题,而是底层物理引擎在接触动力学上的近似处理太粗暴。

Isaac Sim 不一样。它基于 NVIDIA PhysX 5.x,接触力计算原生支持,能拿到精确的接触力、关节力矩、摩擦锥解算结果。简单说,你在仿真里能明确知道“机械臂末端在某个姿态下对桌面施加了多大力”,而这个力数据在 Gazebo 里往往是拍脑袋估的。做力控算法、柔顺控制、夹具夹持力标定,Isaac Sim 的体验接近真实硬件调试。

视觉方面的差距更明显。Gazebo 的默认渲染器和真实相机的成像特征差距很大,做视觉抓取时,从仿真里训练的模型迁移到真机上往往要重新调参。Isaac Sim 的 RTX 渲染器支持路径追踪、物理正确的光照、反射和阴影,还能生成带有深度、分割、边界框标注的合成数据。对做机械臂视觉抓取、目标检测、域随机化的团队来说,这省掉的不是几天,而是几周的数据采集时间。

1.2 PhysX 5.x 在接触动力学上的表现:力控制不是拍脑袋

Isaac Sim 物理引擎的另一个特点是“可调但默认合理”。在 Gazebo 里,Gripper 咬合一个软物体需要自己写 contact model;在 Isaac Sim 里,默认的 PhysX contact solver 就能收敛,关节驱动接口也支持位置、速度、力矩三种模式,而且可以精确设置关节阻尼、摩擦系数、最大力。

在实际跑 Panda 夹爪时,我能直接给关节设置目标位置和最大力矩,超过力矩阈值自动停住。这在 Gazebo 里需要额外写插件,甚至要 Hack 底层代码。物理引擎的表现越接近真实,机械臂控制算法从仿真迁移到实机的成本就越低——这才是换引擎最大的理由。

1.3 视觉渲染管线:合成数据采集对抓取研究的意义

还有一个容易忽略的点:Isaac Sim 里相机传感器输出的不只是 RGB 图,还可以输出深度图、语义分割图、法线图、边界框,而且所有数据都自带精确的相机内参和位姿。因为渲染是用 GPU 做的,同时开 8 个相机也不会把 CPU 打爆。做机械臂视觉抓取的可以直接把传感器输出接到 PyTorch 训练管线里,跑域随机化时场景、光照、纹理都能动态改。

2. 环境搭建:Ubuntu 22.04 + Humble + Isaac Sim 2023.1 的版本匹配

2.1 显卡与驱动前置检查

Isaac Sim 对显卡要求很高。官方推荐 RTX 系列,我的实际操作体验是:RTX 2060 能跑但偏卡,RTX 3060 可以完成中小场景实验,RTX 4070 以上体验舒服很多。显存建议 8GB 起步,12GB 更好——渲染、物理、CUDA 图几项同时跑,显存比内存更早成为瓶颈。

驱动方面,别用 Ubuntu 自带的 nouveau,必须装 NVIDIA 官方驱动。我的检查命令是:

nvidia-smi

输出里需要看到 CUDA Version 显示 12.x 或更高。没有的话先装驱动:

sudo ubuntu-drivers autoinstall sudo reboot

确认驱动正常后再装 CUDA toolkit,但如果你只跑 Isaac Sim,不强制全量安装 CUDA,Isaac Sim 自带依赖库。

2.2 Ubuntu 22.04 安装 ROS2 Humble 踩坑:unable to locate package 的真相

很多人卡在E: unable to locate package ros-humble-desktop这句报错上,我也一样。这个报错的本质不是你网络有问题,而是 apt 的包索引里根本没有 ros-humble 的源。绝大多数人少做了一步:导入 ROS2 的 GPG key 和软件源。

完整命令如下:

sudo apt update && sudo apt install locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 export LANG=en_US.UTF-8 sudo apt install software-properties-common curl sudo add-apt-repository universe sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null sudo apt update sudo apt install ros-humble-desktop python3-argcomplete ros-dev-tools

注意:Humble 对应 22.04,Foxy 对应 20.04,Jazzy 对应 24.04。如果你在 22.04 上强行装 Jazzy,也会出现“unable to locate package”。这俩版本对应关系一定要查清。

安装完记得执行:

source /opt/ros/humble/setup.bash echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc

2.3 Isaac Sim 安装与 Python 虚拟环境隔离

Isaac Sim 的安装方式有两种:Omniverse Launcher 图形化安装,或者 pip 包安装。我强烈推荐 pip 包安装,原因是命令行可控、版本固定、CI 友好。

以 2023.1.1 版本为例:

python3 -m venv ~/isaacsim_env source ~/isaacsim_env/bin/activate pip install isaacsim==2023.1.1 --extra-index-url https://pypi.nvidia.com

安装后可以启动测试:

isaacsim

如果启动后能看到默认的小场景和工具栏,环境就通了。注意:每一版 Isaac Sim 对 Python 版本有要求,2023.1.x 要求 Python 3.10。如果你的系统 Python 是 3.11 或 3.12,建议用 conda 或 pyenv 切到 3.10 后建虚拟环境再装。

2.4 Omniverse Launcher 还是 pip 包?我选后者

在 Launcher 里点“Install”感觉挺省事,但实际用下来有几个问题:Launcher 版本更新频繁,不同项目的依赖可能因为版本漂移而跑不起来;Launcher 安装路径很深,命令行里找可执行文件和 Python 环境比较麻烦。而 pip 包版本锁死,每个项目都可以建独立虚拟环境,出问题随时重装。我现在的固定做法是:每个真实项目单独建 venv,按isaacsim==2023.1.1这样的精确版本锁定依赖。

3. 模型导入:从 URDF/xacro 到 USD 的转换链路

3.1 为什么 Isaac Sim 不直接用 URDF

机械臂常用的 URDF 是 ROS 生态的机器人描述格式,它描述的是运动学树、惯性、碰撞体,但缺少物理材质、视觉材质、传感器配置等高级属性。Isaac Sim 基于 Omniverse Kit,原生格式是 USD(Universal Scene Description),支持嵌套层级、实例化、材质绑定和自定义属性,比 URDF 更像一个完整的“场景格式”,而不只是“机器人格式”。

所以导入机械臂模型的完整链路是:先从 ROS 包拿到 urdf/xacro,然后再转成 USD。Isaac Sim 提供了一个 URDF Importer 扩展,界面在Isaac Utils -> URDF Import,也可以直接用 Python API 导入。

3.2 URDF Importer 导入熊猫臂的实操

以 Franka Panda 为例,先安装描述包:

sudo apt install ros-humble-franka-description

或者直接从 GitHub 拉:

git clone https://github.com/frankaemika/franka_ros2.git

导入前建议把 xacro 先转成纯 URDF。因为 xacro 里可能有宏变量,Importer 对 xacro 的解析不如对纯 URDF 稳定:

source /opt/ros/humble/setup.bash ros2 run xacro xacro franka_ros2/franka_description/robots/panda_arm.urdf.xacro > panda_arm.urdf

然后在 Isaac Sim 里用 Python 脚本导入:

from isaacsim import SimulationApp simulation_app = SimulationApp({"headless": False}) from omni.isaac.urdf import _urdf from omni.isaac.core.utils.stage import open_stage urdf_path = "/path/to/panda_arm.urdf" usd_path = "/path/to/panda_arm.usd" importer = _urdf.UrdfImporter() importer.import_urdf(urdf_path, usd_path)

完成后会生成panda_arm.usd文件。导入参数里有两个关键开关:

  • fix_base_link:勾选后底座固定在地图上,不勾选机械臂会整体掉落。
  • make_default_prim:指定导入后的根节点为当前场景默认物体,后续脚本容易引用。

3.3 导入后的资产清单:物理属性、关节驱动、惯量修正

URDF 转成 USD 后,第一件事不是立刻跑,而是检查关节属性。我最常遇到的三个问题:

第一,关节驱动模式默认可能是 Kinematic,要改成 Dynamic。Kinematic 模式下,关节位置是硬设的,不参与物理计算,抓取接触就失效了。在 Isaac Sim 里检查关节的driveType属性,应该为angularlinear对应的动态驱动模式。

第二,惯量参数。很多 URDF 里的惯量数值是经验值,导入后机械臂可能抖动甚至飞出去。解决办法:在 Isaac Sim 的 PhysX 设置里打开“Auto-convert inertial frames”,或者手动修正massinertia张量,让质心尽量靠近真实位置。

第三,碰撞体遗漏。URDF 里如果只写了视觉网格、没写碰撞网格,导入的 USD 中不会有碰撞体,机械臂会直接穿透桌面。这时候需要手动在 USD 里给对应 link 加 Collider 组件,或者回到 URDF 里补<collision>节点后再导入。

4. ROS2 桥接:isaac_ros_bridge 的通信机制与手写发布订阅

4.1 桥接节点到底在桥上传递了什么

Isaac Sim 与 ROS2 通信的核心是 ROS2 Bridge 扩展。它干的事情可以理解为:把 Isaac Sim 内部的 USD 场景状态翻译成 ROS2 话题、服务、Action,同时把 ROS2 的控制指令翻译回 Isaac Sim 里的关节驱动指令。

官方提供的扩展是omni.isaac.ros2_bridge。启动 Isaac Sim 时如果加上--/isaac/ros2_bridge:=true,桥接器会自动加载。我通常写一个 Python 启动脚本:

from isaacsim import SimulationApp simulation_app = SimulationApp({ "headless": True, "/isaac/ros2_bridge": True, })

桥接器启动后,机械臂的关节状态、TF、相机图像、IMU 数据会自动发布到 ROS2 话题上。你可以用ros2 topic list直接看到:

ros2 topic list

典型输出里有/isaac_arm/joint_states/isaac_arm/joint_group_positions/tf等话题。

4.2 FastDDS 与共享内存配置:让话题延迟降下来

ROS2 Humble 默认 DDS 是 FastDDS。Isaac Sim 和 ROS2 之间的通信如果走网络回环,数据量一大就会有明显延迟。我在实际用的时候发现,图像话题的延迟可能到 50ms 以上,做视觉闭环控制根本不行。

解决办法是启用共享内存传输。FastDDS 支持 SHM 传输,只要把双方的环境变量设置一致:

export FASTRTPS_DEFAULT_PROFILES_FILE=/path/to/shm_profile.xml export RMW_IMPLEMENTATION=rmw_fastrtps_cpp

或者更省事的方式:用 ROS2 的--ros-args -p设置 QoS 为BEST_EFFORT并减小队列深度。图像话题默认是RELIABLE时,遇到网络抖动会不断重传,延迟飙升;机械臂控制场景里,图像和关节状态用BEST_EFFORT更合适。

4.3 手写一个 Publisher 控制机械臂关节的命令行实验

先不用 MoveIt2,我用命令行直接给关节发话题,验证桥接链路通不通。Isaac Sim 的 ROS2 Bridge 支持接收/isaac_arm/joint_group_positions话题,消息类型是std_msgs/msg/Float64MultiArray

ros2 topic pub /isaac_arm/joint_group_positions std_msgs/msg/Float64MultiArray "{data: [0.0, -0.785, 0.0, -2.356, 0.0, 1.571, 0.785]}" --once

如果机械臂的关节动了一下,说明桥接通信正常。这里的数组顺序必须和导入 USD 时各关节的顺序一致。顺序查法:

ros2 topic echo /isaac_arm/joint_states --once | grep name

顺序不一致时,表现为“发了指令但关节乱动”。这个问题我踩过不止一次,脚本化控制时务必先打印关节名列表核对顺序。

5. 机械臂控制实战:关节驱动与 MoveIt2 集成

5.1 关节驱动 API:ArticulationView 的 set_joint_position_targets

如果你不想走 ROS2 话题,直接写 Python 脚本控制机械臂也是很顺畅的路径。核心 API 是 ArticulationView。

from isaacsim.core.prims import ArticulationView arm = ArticulationView("/World/panda_arm") arm.initialize() arm.set_joint_position_targets([0.0, -0.785, 0.0, -2.356, 0.0, 1.571, 0.785])

这里有个关键点:set_joint_position_targets 设置的“目标”不是最终效果,物理引擎会通过内置的 PID 控制器去追这个目标。PID 的 P、I、D 值可以调:

arm.set_joint_pids([1000.0, 0.0, 50.0] * 7)

实际调试中,P 值过大会抖动,D 值过小会震荡。我从 Gazebo 转过来时总习惯写自己的 PID 控制器,后来发现 Isaac Sim 自带的关节 PID 在多数场景下已经比我自己调的要稳,直接用就行。

5.2 用 ROS2 Topic 驱动 Isaac Sim 中的关节

把桥接和脚本合在一起,就是最常见的“外部控制”结构:外部 ROS2 节点发话题,Isaac Sim 桥接器收到后写入关节目标。

import rclpy from rclpy.node import Node from std_msgs.msg import Float64MultiArray class ArmCommandNode(Node): def __init__(self): super().__init__(node_name="arm_command_node") self.publisher = self.create_publisher( Float64MultiArray, "/isaac_arm/joint_group_positions", 10 ) def send_position(self, positions): msg = Float64MultiArray() msg.data = positions self.publisher.publish(msg) rclpy.init() node = ArmCommandNode() rate = node.create_rate(30) while rclpy.ok(): node.send_position([0.0, -0.785, 0.0, -2.356, 0.0, 1.571, 0.785]) rclpy.spin_once(node) rate.sleep()

这个循环频率建议 30~50Hz,不需要太快,因为关节位置目标本身是平滑轨迹,更高的发布频率反而会把噪声传进控制器。

5.3 MoveIt2 做运动规划:从 RViz2 里拖动目标点

MoveIt2 的优势在于路点规划、避障、逆解。Isaac Sim 本身没有完整运动规划器,但在机械臂抓取场景里绝对不能缺 MoveIt2。

集成时最关键的问题是:MoveIt2 需要知道自己控制的机器人关节和 TF,但它不知道 Isaac Sim 里的物理仿真。所以常规做法是,MoveIt2 负责规划路径,然后把规划好的关节轨迹通过话题发布出来,桥接器把轨迹点里的关节位置按时间逐点写入 Isaac Sim。

启动 MoveIt2 的流程:

ros2 launch moveit2_tutorials demo.launch.py

或者用 Panda 的配置:

ros2 launch panda_moveit_config demo.launch.py

MoveIt2 规划出来的关节轨迹默认发布在/joint_trajectory话题。为了让 Isaac Sim 接收,我一般写一个中继节点,从这个话题读取FollowJointTrajectory型的目标,转成 Float64MultiArray 后重新发布给/isaac_arm/joint_group_positions

from trajectory_msgs.msg import JointTrajectory from std_msgs.msg import Float64MultiArray def trajectory_callback(msg: JointTrajectory): points = msg.points for point in points: positions = list(point.positions) arm_pub.publish(Float64MultiArray(data=positions)) rate.sleep()

注意时间戳:MoveIt2 轨迹点自带time_from_start,中继节点要以这个字段为基准做 sleep,否则机械臂动作会快好几倍。我一开始忽略了,机械臂“唰”一下就到目标姿态,看着惊心动魄,后来加了时间戳同步才恢复正常。

5.4 仿真时间同步:别让控制频率和渲染帧率打架

Isaac Sim 里有一个容易混淆的概念:渲染帧率和物理步长。渲染帧率可以不等于物理步长。默认物理步长是 1/60 秒,也就是 60Hz 的物理仿真。你的控制指令频率如果远低于 60Hz,机械臂动作会一顿一顿;远高于 60Hz,指令会在物理步进之间被丢弃或累积。

调物理步长的方法:

from isaacsim.core.utils.semantics import add_update_semantics from isaacsim.core.api.world import World world = World(stage_units_in_meters=1.0, physics_dt=1.0 / 120.0, rendering_dt=1.0 / 60.0)

physics_dt是物理步长,rendering_dt是渲染帧率。我一般把物理步长设为 120Hz,控制指令频率设为 60Hz,这样两者恰好差 2 倍,控制指令总是能落在物理步进上,不容易出现丢指令或相位抖动。

6. 排错篇:我花过最长时间解决的 6 个问题

6.1 E: unable to locate package ros-humble-desktop

这个错误我在第 2 节已经给了标准解法,但还有一个隐藏原因:系统是 Docker 或精简版 Ubuntu,默认没有software-properties-common。先装它:

sudo apt install software-properties-common

然后再add-apt-repository universe。顺序反了的话,ROS2 仓库加不进去,apt 依然找不到包。

另一个冷门情况是:Ubuntu 22.04 的etc/os-release里输出UBUNTU_CODENAMEjammy,但某些国内镜像源的路径不同,导致apt update时报 404 或无法解析。解决方案是把软件源地址从packages.ros.org换成可访问的镜像源,再重新 update。

6.2 桥接节点收不到/发不出话题

桥接器启动后,ros2 topic list能看见话题,但频率为 0 或收不到数据,最常见原因是 DDS 发现协议问题。Isaac Sim 的桥接进程和外部 ROS2 节点不在同一台机器时,需要在两边都设置ROS_DOMAIN_ID且一致:

export ROS_DOMAIN_ID=1

如果在一台机器上还发现不了,检查两边的FASTRTPS_DEFAULT_PROFILES_FILE是否一致。另一个容易忽略的是:Isaac Sim 的 headless 模式下,某些图像传感器默认不渲染,导致相机话题没有数据。解决方法是打开render at 60Hz或改用非 headless 模式。

6.3 关节抖动与物理参数发散

关节抖动通常不是控制器 PID 问题,而是物理步长和 PID 更新频率不匹配。有一个典型的错误配置:物理步长设为 60Hz,但关节 PID 更新在渲染线程里执行,渲染卡顿时 PID 更新频率不稳定,关节就抖。

解决办法是把物理步长调小(120Hz 或 240Hz),或者在 USD 关节属性里把max_velocity限制在机械臂实际速度上限内。我遇到一次 Panda 关节抖动,最后发现是导入时关节的max_velocity没有限制,PhysX 会尝试以极高速度追目标,产生振荡。

6.4 相机点云在 RViz2 里错位

Isaac Sim 发布的深度图转到 RViz2 后,点云经常和实际机械臂位置错位。这不是坐标系标定问题,而是深度图的后处理参数不对。在 Isaac Sim 的相机传感器设置里,要确认裁剪平面(near/far)和传感器的尺寸、焦距与真实相机内参一致。RViz2 里固定坐标系选/world,而不是/map,并且确认 TF 树里有没有循环。

如果点云整体漂移,还有一个隐藏坑:深度图话题的 QoS 默认是RELIABLE,图像数据量大时会产生滞后,点云对应的深度和 RGB 不是同一时刻。把图像话题的 QoS 改成BEST_EFFORT后,位置误差立刻小了很多。

6.5 GPU 显存占用暴增

Isaac Sim 的显存占用很容易涨到几个 GB,开 RTX 渲染后更高。我的项目里常见爆显存场景:同时开了 4 个以上相机,每个相机都开 RGB、深度、分割三路输出。合成数据采集需要的传感器特别多,显存不够时程序直接崩溃。

解法是分层加载:场景模型延迟加载、相机按需开关。比如先用Usd.Prim.IsActive停用不需要的传感器,等需要时再激活。还有一个经验是:先把深度图像数据从 GPU 拷贝到 CPU 并释放 GPU 缓存,避免历史帧堆积在显存里。

6.6 仿真时间与真实时间漂移

Isaac Sim 默认的物理仿真时间不等于真实时间,尤其当渲染负载较高时,仿真时间会比真实时间慢。这对算法验证影响很大:你打算让机械臂 5 秒运动到目标,实际可能用了 10 秒。

检查时间是否漂移的方法:

ros2 topic echo /clock --once

如果发现/clock和真实时间差距大,需要开启实时同步。在 SimulationApp 初始化时加上:

simulation_app = SimulationApp({ "headless": False, "physics_dt": 1.0 / 120.0, "rendering_dt": 1.0 / 60.0, "real_time": True, })

real_time为 True 时,物理引擎会尽量让仿真时间贴合真实时间。但要注意,如果渲染帧率太低,real_time也不一定救得回来。内部机制是物理步进等待渲染释放资源,负载高时依然会慢。最终解法还是降低渲染负载——我一般用 headless 模式跑长时间数据采集,只在可视化调试时开 GUI。

排错到最后,我自己形成了一个固定工作流:先写一个最小的 Python 脚本,只加载模型、打开桥接器、用命令行ros2 topic pub测关节通信,链路通后再加 MoveIt2 和视觉传感器。每次搭新场景都从这条路走一遍,能省出整块的查错时间。机械臂仿真这个领域,真正难的不是某一个算法,而是把底层物理、中间通信、上层规划整个链路跑顺。希望这份指南也能帮你把链路一次跑通。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 18:28:21

VS Code 新UI被曝重大bug:菜单栏消失了

我敢打赌&#xff0c;你每天打开 VS Code&#xff0c;做的第一个动作&#xff0c;大概率就是瞥一眼左上角那个三条横线的 菜单按钮。你可能从没点过它&#xff0c;但它就在那儿&#xff0c;像个沉默的保安。 直到有一天&#xff0c;你心血来潮点了一下——什么都没发生。 你瞪大…

作者头像 李华
网站建设 2026/9/11 18:27:37

C# WinForms视觉框架:从界面布局到运动控制与日志全流程

简介&#xff1a;面向C# WinForms桌面应用开发的通用视觉框架资源包&#xff0c;适合需要快速搭建带左侧工具栏、右侧图像区、右下日志、顶部导航、底部变量区等典型工业软件界面的开发者&#xff0c;也可用于数据可视化和视觉检测项目的前期框架选型。资源共97个文件&#xff…

作者头像 李华
网站建设 2026/9/11 18:26:12

monit-日志监控工具

前段时间&#xff0c;CTO下达了一个brief&#xff0c;需要搭建monit日志监控应用&#xff0c;匹配日志中的异常信息&#xff0c;自动发送邮件/微信告警。具体的要求如下&#xff1a; 1.监控***项目的各个应用&#xff0c;nginx的日志&#xff0c;匹配到错误时发送告警 2.监控…

作者头像 李华
网站建设 2026/9/11 18:22:49

Spring Boot + MyBatis-Plus 酒店管理系统实战:状态机、缓存与安全部署

简介&#xff1a;这是一套基于 Spring Boot 与 SSM 体系构建的酒店管理系统完整项目源码&#xff0c;主要面向 JavaWeb 初学者、毕业设计及课程实践者。系统包含管理员与普通用户两侧&#xff1a;普通用户可注册登录、在线预订房间&#xff0c;根据入住时间自动计算费用&#x…

作者头像 李华