本地运行

最快的方式是在仓库根目录执行:

bash
conda activate 你的-fastsim-环境
./demo/server/web_client/start_all.sh

然后打开 http://127.0.0.1:8090。这个脚本会同时启动 FastSim Server 和独立的静态 前端;按 Ctrl+C 会停止二者。如果希望分别查看日志或单独重启其中一端,则在两个终端 分别执行 start_server.shstart_web.sh

连接成功后,在 Server path 启动页填写随 Demo 提供的相机 Run:

  • Root: demos
  • Configuration path: server/web_client/run.yaml
  • Target state: running

这个 Run 包含一个可控双关节机械臂、一个红色方块,以及 sensors.overviewsensors.side 两台 1280 x 720 RGB 场景相机。两个不同视角都能看到机械臂和红色 方块。页面会从不可变 Run Scenario 自动发现相机,默认全选,也可以只看其中一路。

默认后端端口为 8010,前端端口为 8090。无需编辑脚本即可覆盖,例如:

bash
FASTSIM_SERVER_PORT=8011 FASTSIM_WEB_PORT=8091 \
  ./demo/server/web_client/start_all.sh

脚本不会假定任何 Conda 环境名,而是直接使用当前已激活环境中的 fastsim-server。如果确实希望脚本在未提前激活的情况下进入某个 Conda 环境,必须 显式给出自己的环境名:

bash
FASTSIM_CONDA_ENV=你的-fastsim-环境 \
  ./demo/server/web_client/start_all.sh

需要两个彼此独立的终端。第一个终端持有仿真,第二个终端只托管 HTML、CSS 和 JavaScript。

终端 1:启动 FastSim Server

启动一个空的常驻 Server,并授权一个目录。demos 是对外显示的别名,绝对路径只留在 服务端:

bash
fastsim-server \
  --launch-root demos=/absolute/path/to/FastSim/demo \
  --output-root /absolute/path/to/FastSim/outputs \
  --planning-reads \
  --host 127.0.0.1 \
  --port 8000 \
  --browser-origin http://127.0.0.1:8080

--browser-origin 声明允许浏览器访问该 Server 的唯一前端 Origin。协议、主机和端口 必须和实际打开的前端地址完全一致。控制 API 不应使用 *

可以重复填写 --launch-root 别名=/绝对目录,有控制地开放多个根目录。原有的位置参数 启动方式仍受支持,但预加载 Run 会占用唯一 Run 槽位,直到执行 terminal close。

--output-root 是可选项。启用后,Server 会为每个 Run 槽位代次创建独立的私有输出 目录,并开放 Committed artifacts 面板。启动前需自行创建该目录。它只是输出目标, 不是配置启动根目录,也不是资产根目录。

--planning-reads 用于发布 FastSim 的只读场景、坐标系和规划几何 HTTP 服务。Web Client 通过其中较轻量的场景目录与场景状态接口填充右侧 Scene 检查器。这个参数不会 启动规划器,不会允许修改场景,也不会改变控制权限。通用 Server 默认不开放它,是因为 几何资源可能很大,而且可能暴露部署方不希望公开的资产结构;当前仅绑定 loopback 的 Web Demo 会明确开启它。不需要读取这些信息的部署可以不传。相机图像属于另一项独立 能力,Run 配置里仍然必须存在相机实体。

终端 2:独立托管静态前端

下面使用 Python 自带的静态服务器只是为了方便,不是前端运行依赖:

bash
python -m http.server 8080 --bind 127.0.0.1 --directory demo/server/web_client/static

换成 Nginx、对象存储或 CDN 也一样。打开 http://127.0.0.1:8080,保留默认 Server 地址 http://127.0.0.1:8000,点击 Connect

浏览器打开期间,这条命令必须保持运行;按 Ctrl+C 会停止静态站点。如果 8080 已被 占用,可以改成 8081,并同时把 Server 参数改成 --browser-origin http://127.0.0.1:8081,浏览器也访问 8081。

两个命令互不依赖。关闭静态文件服务器不会终止 FastSim Server;停止 FastSim Server 也不影响前端文件如何部署。

工作台布局

客户端采用固定的工程工作台布局,而不是需要不断向下滚动的卡片仪表盘。中央区域以真实 所选场景相机画面为主,画面始终按比例完整缩放,不会裁剪。相机下拉菜单提供 All cameras 和逐相机复选框;页面只请求选中的相机,多路画面自动排成网格。左侧集中放置连接、启动、生命周期 和通用目标控制;右侧检查器容纳场景状态、插件、异步操作和已提交输出;可调整高度的底部 Dock 放置事件控制台、最新公开状态、逐关节命令/状态对比曲线和进程性能曲线。只有打开某个 曲线页签时才会订阅它所需的数据;离开页签或折叠 Dock 后会立即释放该需求。

面板布局变化不会改变前后端边界。所有显示数据和控制操作仍只通过公开的 FastSim Server HTTP API 完成。

连接 Server

Connection 面板包含:

  • FastSim Server URL:完整的 http://https:// Origin。明文 HTTP 只允许 精确的 loopback 主机:localhost127.0.0.0/8 内的 IPv4 地址(包括 URL 解析器规范化后的简写)和 [::1]。所有非 loopback Server 都必须使用 HTTPS。页面会先校验 URL,然后才会读取 Bearer token 或发送请求。为了方便 再次使用,校验后的 Origin 可以保存在浏览器 local storage 中。
  • Bearer token:连接受保护 Server 时可填写。它只保存在当前模块内存中;连接后 输入框会清空,断开时内存值会清除,绝不会进入 URL、local storage、日志或部署 配置。
  • LiveReady:分别显示 GET /health/liveGET /health/ready 的真实 结果。如果 readiness 返回 200,但 application_opened: false,表示 Server 可以 接收工作、但当前没有打开 Run;页面会显示 Run cold,连接状态仍为 Connected,不会误宣称 Run 已经 Ready

健康检查后,页面会读取 GET /api/v1/launch,不会自行猜测 Server 允许的根目录、 配置格式或上传大小。

请求显式禁用浏览器凭据,不发送 referrer,也不跟随重定向。连接失败会直接显示, 不会用模拟遥测数据掩盖问题。每个 HTTP 请求还有有限的传输期限:健康和相机请求为 10 秒,普通读取为 40 秒,异步生命周期/控制接纳为 3605 秒,因此浏览器不会缩短 Server 正常的 application-open 或控制预算。返回 202 Accepted 后,operation 属于 Run;随后原始 HTTP 请求或浏览器连接结束,也不会取消该 operation。

Disconnect 不等于停止仿真

点击 Disconnect 只会停止当前标签页的请求并清除内存 token。它不会调用 stopclose,也不会取消已经接纳的 operation;远端仿真保持当前状态继续存在。 Disconnect 还会中止浏览器中所有尚未完成的 HTTP 请求,因此即使 Server 或网络永久 不响应,也不会阻止下次连接启动新的轮询代次。

改变仿真生命周期必须明确点击对应按钮:

  • Stop Run 调用 POST /api/v1/run/stop,终止后续工作并排空活跃控制;
  • Terminal close 调用 POST /api/v1/run/close,完成插件后处理并释放应用资源;
  • 关闭页面或网络中断不会调用这两个接口中的任何一个。

这个边界对于远程操作很重要:客户端网络断开绝不能悄悄销毁一次 Run。

控制与观测能力

连接后,页面直接使用 FastSim Server 路由:

  • 启动源发现,以及从授权路径或受限文本上传异步启动 Run;
  • prepare、start、pause、resume、step、reset、stop、close 生命周期控制;
  • Run 身份、一致状态、仿真 tick、实测 FPS 和实时倍率;
  • 场景实体位姿,以及机器人和普通铰接物体的关节状态;
  • Plugin Host、每个插件实例的状态、近期事件和受限的失败详情;
  • 已提交的 Run 输出,可安全预览或下载 FSR、视频、结构化数据、图片、日志和报告;
  • 异步 operation 历史与取消;
  • 可移植 control target 和通用 ControlTrack 提交;
  • 逐关节的目标值编辑器,每个轴都有独立的名称和数值输入,并实时对比后端实际执行的 控制命令与测得的关节状态;
  • 按需采样 FastSim Server 进程的仿真 FPS、CPU、常驻内存,以及 NVML 可用时归属于当前 进程的 GPU 显存;
  • 自动发现 RGB 相机,并按后端能力读取所选相机的 JPEG(优先)或 PNG;Demo 包含 sensors.overviewsensors.side
  • 底部控制台通过有界 cursor 读取 Server 结构化日志,Run 处于 cold 或 failed 时仍可用。

控制编辑器以 descriptor 为准。target、轴、单位、command space、controller 和当前轴值 来自 /api/v1/control/targets/api/v1/control/state;页面不会假设所有铰接物体 都是机器人。相机帧显示前会检查 1280×720 尺寸、Run、实体、generation、tick 和媒体 类型响应头;新帧完全解码后才替换旧 object URL,延迟或无效响应不会造成黑屏闪烁。

Trajectory time 可以留空。留空表示立即下发并保持目标,真实关节按物理驱动的增益和 约束自行收敛;填写时间则表示从最新观测状态出发,在对应的仿真时间内把期望值平滑变化到 目标值。这个时间规定的是参考命令曲线,不能单独保证真实关节在终点时刻恰好零误差。两种 方式在命令结束后都会继续保持最终目标。

所有依赖当前 Run 的生命周期写入与控制写入都会携带最新 active launch status 中的 X-FastSim-Slot-Generation。如果另一个客户端已经替换了 Run,Server 会返回 409 stale_slot_generation,从而避免本应发给第 N 代 Run 的延迟命令落到第 N+1 代。 Launch 请求、operation 查询与 operation 取消属于 supervisor 层调用,不携带该请求头。

传输能力发现完成后,Run、operation、日志、状态、场景、控制和插件更新共用一条 WebSocket 会话。Server 支持时,所选场景相机通过 WebRTC 传输;兼容快照模式才使用独立的 single-flight 请求。已由实时会话提供的数据不再保留周期 GET 循环。实时传输不可用时,回退 通道仍然有界且严格 single-flight。空槽位或尚未 prepare 的 Run 不会请求 Run 数据面; 只有生命周期进入 preparedrunningpaused 后才会开始这些读取。

任意活跃的 lifecycle.* operation 都会关闭运行时读取门禁。页面会停止并取消尚未 完成的 Run 状态、仿真、插件、控制、场景和相机轮询,只继续轮询健康、Run 槽位和 operation 状态。无论 lifecycle 命令来自当前页面,还是由其他客户端发起后被 operation 列表发现,规则都相同。operation 进入终态后,页面先刷新 /api/v1/launch;只有槽位 仍为 active 时才单次读取 /api/v1/run,随后重新开放门禁并恢复相应轮询。 提交异常通过 finally 恢复门禁;断开和切换 backend 会让旧门禁代次失效,因此旧操作 完成后无法重启新连接的轮询。标签页隐藏时也会暂停轮询,相机轮询可以单独关闭。

插件诊断与 Run 输出

插件面板会读取公开的 Host 快照、每个已公布实例的精确状态,以及按 sequence cursor 分页的事件接口。所有字段都只作为文本渲染,插件 payload 不会被解释成 HTML。点击 Refresh 可以立即读取一次,不会改变仿真生命周期。

Server 使用 --output-root 启动后,输出面板才会启用。面板只列出当前 Run 槽位代次中 非空、在白名单内的普通文件:.fsr.mp4.webm.json.jsonl.csv.png.jpg.jpeg.txt.log.md。隐藏文件、临时文件、未知格式、 符号链接、FIFO 及其他特殊文件不会显示。文本、数据和报告预览最多 1 MiB;图片和视频 取 64 MiB 与 Server 公布上限中的较小值。FSR 不在控制台内解析,应下载后用 fsr_viewer 打开。

浏览器只会在 Run 进入 active、terminal close 后回到 idle,或者运维者点击 Refresh 时刷新列表,不会逐 tick 扫描文件系统。小文件使用有上限的 Blob 下载;超过 64 MiB 的 文件使用 16 MiB HTTP Range 分块和浏览器 File System Access API 直接写盘,不把整份 录制放进内存。不支持该 API 的浏览器仍可通过同一个带认证的 Range HTTP 接口,交给 外部下载工具处理。

启动一次仿真

Launch a simulation 面板提供两种配置来源。

方式 A:服务端授权路径

  1. 选择 Server path
  2. 从下拉框选择 Server 公布的根目录别名;
  3. 输入该根目录下的相对 POSIX 路径,例如 server/08_camera_rgb_and_png/run.yaml
  4. 默认保持 Running,也可以明确选择 PreparedOpen
  5. 点击 Launch Run

浏览器会携带 Idempotency-Key 调用 POST /api/v1/run/launch,再复用 operation 轮询 跟踪返回的 lifecycle.launch。绝对路径、Windows 分隔符、空路径段、... 都会 被拒绝。Server 还会独立检查真实路径始终位于授权根目录内,并拒绝符号链接和逃逸; 浏览器校验只是提前反馈,安全边界始终在 Server。

--launch-root 授权的是客户端可以选择哪一个配置文件,并不是对配置所传递引用的完整 资产图进行沙箱隔离。被选配置会作为受信任 FastSim Project 编译,仍可解析 registry、 已安装 package、声明的 trusted_roots 和带 digest 的 HTTPS 资源。因此,运维者只能 暴露自己控制、且不允许不可信用户写入的 Project 目录树。

方式 B:上传一个配置

  1. 选择 Upload one config
  2. 选择一个 .yaml.yml.json.toml 文件;
  3. 点击 Launch Run

后缀和 UTF-8 最大字节数以 Server 公布值为准。浏览器会先检查文件大小,再进行严格 UTF-8 解码并拒绝 NUL,最后在启动 JSON 中发送 {filename, content}。此入口不接收 目录、压缩包、可执行文件、project 元数据或 lock 文件。独立上传配置只能使用已安装 catalog 或隔离编译器允许的资源;如果 Run 依赖相邻的本地文件,应改用服务端授权路径。

界面用 IdleLaunchingActiveClosingError 显示槽位状态。启动 operation 活跃期间,Run、plugin、scene、control 与 camera 的重型轮询都会停止;健康、槽位状态 和 operation 进度仍会更新。成功后,生命周期与数据面板会自动接入新 Run。

浏览器数据边界

local storage 只保存规范化后的 Server URL。Bearer token、所选服务端路径、上传文件 名、配置正文和启动请求都不会持久化。断开时会清除 token 与配置输入;每次上传提交 结束也会立即清空文件输入。配置正文不会写入事件日志或原始状态诊断框。

Bearer 认证

创建私有 token 文件,并在启动 Server 时保留同一条 browser-origin 规则:

bash
fastsim-server \
  --launch-root demos=/absolute/path/to/FastSim/demo \
  --host 127.0.0.1 \
  --port 8000 \
  --browser-origin http://127.0.0.1:8080 \
  --token-file ./fastsim-server.token

连接前把文件中的 token 粘贴到密码输入框。不要把 token 放进 query string,也不要把 token 文件提交到仓库。Web Client 与 FastSim Server 使用完全相同的 RFC6750 b64token 规则:总长度为 32~4096 个 ASCII 字符;主体只能包含 A-Za-z0-9-._~+/,可选的 = padding 只能连续出现在末尾。 Unicode、NUL/控制字符、任意空白、内部 = 和其他标点都会被拒绝。输入框为空表示不 启用认证。

远程部署

Server 监听非 loopback 地址时,必须同时启用 TLS 和 Bearer 认证。即使前端自身 通过 HTTP 托管,客户端也会拒绝任何非 loopback 的 http:// 地址:

bash
fastsim-server \
  --launch-root runs=/srv/fastsim \
  --host simulation.example.com \
  --port 8443 \
  --browser-origin https://control.example.com \
  --token-file /run/secrets/fastsim-server.token \
  --tls-cert-file /run/secrets/server.crt \
  --tls-key-file /run/secrets/server.key

static/ 中的文件部署到 https://control.example.com,然后在页面里填写公开的 Server Origin https://simulation.example.com:8443。传给 --host 的主机名必须解析到 该机器实际持有的地址,浏览器访问的 Server authority 也必须使用相同主机名和 --port,TLS 证书必须覆盖它。本版不承诺 NAT、反向代理或外部 TLS termination 可以 透明工作。HTTPS 前端会拒绝 HTTP 后端,因为浏览器会拦截这种 mixed active content。

正式部署时,应由静态站点响应头设置 Content Security Policy,并把 connect-src 收窄到唯一预期的 Server Origin。为了允许运行时选择 Server,随示例提供的通用 HTML 使用了范围较宽的 http: https:;生产部署必须收紧。静态主机还必须同时发送 Content-Security-Policy: frame-ancestors 'none'X-Frame-Options: DENY,防止 clickjacking。HTML <meta> 形式的 CSP 无法实现 frame-ancestors。作为纵深防御, 应用检测到 window.top !== window.self 时也会拒绝 bootstrap:嵌入页面不会向 Server 发送任何请求,并明确显示已阻断状态。FastSim Server 需要返回精确的 CORS Origin, 并暴露 FastSim 身份与 Range 响应头,绝不能配置带凭据的通配 Origin。

打包与性能

源码包和 FastSim Wheel 会把该静态应用放到 share/fastsim/demo/server/web_client/static。这些仍然只是普通静态文件:前端服务器 不需要导入 FastSim,也不需要安装任何仿真器。

Web Client 是按需付出性能成本的。没有浏览器连接时,FastSim Core 不承担 Web 轮询 开销;连接页面后,可以降低状态或相机频率、减少所选相机,或者关闭相机,以减少 HTTP 序列化和图像编码工作。只选择一路相机时,每个 single-flight 周期仍只有一个画面请求。

测试前端

测试只需要 Node.js,不需要 Python 包和仿真器:

bash
node --test demo/server/web_client/tests/test_state_parsing.mjs

测试覆盖 URL 校验、直连端点构造、只驻留内存的 Bearer、启动发现与五种 UI 状态、 精确启动 payload、根目录/路径穿越拒绝、严格 UTF-8 上传与 Server 公布的字节限制、 强制 fetch 安全选项、HTTP 错误传播、状态解析、通用 Track 构造、PNG 验证、轮询 single-flight/代次行为、idle/cold Run 重型读取抑制,以及 lifecycle 数据面门禁和在途 取消。测试还会验证产物代次门禁、安全路径与有界 Range 分块、catalog descriptor、 插件诊断,以及输出面板必需的 DOM 结构。

返回 Server 示例目录