本地运行
最快的方式是在仓库根目录执行:
conda activate 你的-fastsim-环境
./demo/server/web_client/start_all.sh
然后打开 http://127.0.0.1:8090。这个脚本会同时启动 FastSim Server 和独立的静态
前端;按 Ctrl+C 会停止二者。如果希望分别查看日志或单独重启其中一端,则在两个终端
分别执行 start_server.sh 和 start_web.sh。
连接成功后,在 Server path 启动页填写随 Demo 提供的相机 Run:
- Root:
demos - Configuration path:
server/web_client/run.yaml - Target state:
running
这个 Run 包含一个可控双关节机械臂、一个红色方块,以及 sensors.overview、
sensors.side 两台 1280 x 720 RGB 场景相机。两个不同视角都能看到机械臂和红色
方块。页面会从不可变 Run Scenario 自动发现相机,默认全选,也可以只看其中一路。
默认后端端口为 8010,前端端口为 8090。无需编辑脚本即可覆盖,例如:
FASTSIM_SERVER_PORT=8011 FASTSIM_WEB_PORT=8091 \
./demo/server/web_client/start_all.sh
脚本不会假定任何 Conda 环境名,而是直接使用当前已激活环境中的
fastsim-server。如果确实希望脚本在未提前激活的情况下进入某个 Conda 环境,必须
显式给出自己的环境名:
FASTSIM_CONDA_ENV=你的-fastsim-环境 \
./demo/server/web_client/start_all.sh
需要两个彼此独立的终端。第一个终端持有仿真,第二个终端只托管 HTML、CSS 和 JavaScript。
终端 1:启动 FastSim Server
启动一个空的常驻 Server,并授权一个目录。demos 是对外显示的别名,绝对路径只留在
服务端:
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 自带的静态服务器只是为了方便,不是前端运行依赖:
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 主机:localhost、127.0.0.0/8内的 IPv4 地址(包括 URL 解析器规范化后的简写)和[::1]。所有非 loopback Server 都必须使用 HTTPS。页面会先校验 URL,然后才会读取 Bearer token 或发送请求。为了方便 再次使用,校验后的 Origin 可以保存在浏览器 local storage 中。 - Bearer token:连接受保护 Server 时可填写。它只保存在当前模块内存中;连接后 输入框会清空,断开时内存值会清除,绝不会进入 URL、local storage、日志或部署 配置。
- Live 与 Ready:分别显示
GET /health/live和GET /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。它不会调用 stop
或 close,也不会取消已经接纳的 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.overview和sensors.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 数据面;
只有生命周期进入 prepared、running 或 paused 后才会开始这些读取。
任意活跃的 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:服务端授权路径
- 选择 Server path;
- 从下拉框选择 Server 公布的根目录别名;
- 输入该根目录下的相对 POSIX 路径,例如
server/08_camera_rgb_and_png/run.yaml; - 默认保持 Running,也可以明确选择 Prepared 或 Open;
- 点击 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:上传一个配置
- 选择 Upload one config;
- 选择一个
.yaml、.yml、.json或.toml文件; - 点击 Launch Run。
后缀和 UTF-8 最大字节数以 Server 公布值为准。浏览器会先检查文件大小,再进行严格
UTF-8 解码并拒绝 NUL,最后在启动 JSON 中发送 {filename, content}。此入口不接收
目录、压缩包、可执行文件、project 元数据或 lock 文件。独立上传配置只能使用已安装
catalog 或隔离编译器允许的资源;如果 Run 依赖相邻的本地文件,应改用服务端授权路径。
界面用 Idle、Launching、Active、Closing、Error 显示槽位状态。启动 operation 活跃期间,Run、plugin、scene、control 与 camera 的重型轮询都会停止;健康、槽位状态 和 operation 进度仍会更新。成功后,生命周期与数据面板会自动接入新 Run。
浏览器数据边界
local storage 只保存规范化后的 Server URL。Bearer token、所选服务端路径、上传文件 名、配置正文和启动请求都不会持久化。断开时会清除 token 与配置输入;每次上传提交 结束也会立即清空文件输入。配置正文不会写入事件日志或原始状态诊断框。
Bearer 认证
创建私有 token 文件,并在启动 Server 时保留同一条 browser-origin 规则:
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-Z、a-z、
0-9、-、.、_、~、+、/,可选的 = padding 只能连续出现在末尾。
Unicode、NUL/控制字符、任意空白、内部 = 和其他标点都会被拒绝。输入框为空表示不
启用认证。
远程部署
Server 监听非 loopback 地址时,必须同时启用 TLS 和 Bearer 认证。即使前端自身
通过 HTTP 托管,客户端也会拒绝任何非 loopback 的 http:// 地址:
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 包和仿真器:
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 示例目录。