From be9ae0f51decaca784a309ca51a5ab5d09b103b5 Mon Sep 17 00:00:00 2001 From: HarveyQue Date: Sun, 13 Sep 2026 01:22:57 +0800 Subject: [PATCH] docs: remove retired product provenance --- CLAUDE.md | 8 ++-- README.md | 11 +++-- crates/osdl-core/src/media/mod.rs | 27 ++++++----- crates/osdl-core/src/media/onvif_camera.rs | 6 +-- crates/osdl-core/tests/integration.rs | 2 +- docs/architecture.md | 15 +++--- docs/cs-architecture.md | 56 +++++++++++----------- docs/getting-started.md | 8 ++-- docs/recipes/bus-manifest.md | 9 ++-- docs/recipes/configs/onvif-camera.yaml | 23 ++++----- docs/recipes/media-gateway.md | 36 +++++++------- 11 files changed, 103 insertions(+), 98 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2e49ca5..85abb69 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -140,16 +140,16 @@ cargo run --bin lab serve # Boot engine + gRPC server (TCP + UDS) cargo test # Run all tests (e2e + integration) ``` -## Integration with Xyzen +## Integration with SciLaxy ``` -Xyzen Cloud ←WebSocket→ Runner (gRPC client) ←gRPC→ `lab serve` (OsdlEngine) → Transport → Device +SciLaxy Cloud ←WebSocket→ Runner (gRPC client) ←gRPC→ `lab serve` (OsdlEngine) → Transport → Device ``` The runner is a **pure gRPC client** of OpenSDL, not an embedded crate: it depends on `osdl-proto` (the generated tonic crate) behind the -`feature = "osdl"`. The engine + gRPC server live in a separate `osdl -serve` process — spawned and supervised by the desktop host (see +`feature = "osdl"`. The engine + gRPC server live in a separate `lab serve` +process — spawned and supervised by the desktop host (see `desktop/electron`'s `lab_server.ts`) or run independently on a lab Pi. The runner connects to whatever endpoint the user configured. diff --git a/README.md b/README.md index 6ad044c..8ee4d58 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,17 @@ ## What is OpenSDL? -OpenSDL connects laboratory hardware to your application through a unified control layer. It reuses existing device driver ecosystems (starting with [Uni-Lab-OS](https://github.com/deepmodeling/Uni-Lab-OS)) without requiring their platform software to run. +OpenSDL connects laboratory hardware to your application through a unified +control layer. Protocol adapters let it consume device descriptions and encode +commands for multiple driver ecosystems without running their platform software. +The included UniLabOS adapter supports the +[Uni-Lab-OS](https://github.com/deepmodeling/Uni-Lab-OS) device-description +ecosystem. ``` - Your Application (Xyzen, LIMS, custom) + Your Application (SciLaxy, LIMS, custom) │ - │ Rust crate / CLI + │ gRPC / CLI │ ┌───────────────────────▼────────────────────────────────────┐ │ Mother Node │ diff --git a/crates/osdl-core/src/media/mod.rs b/crates/osdl-core/src/media/mod.rs index 0fc1d5b..bf737cf 100644 --- a/crates/osdl-core/src/media/mod.rs +++ b/crates/osdl-core/src/media/mod.rs @@ -103,16 +103,14 @@ impl MediaSourceConfig { } /// Split an optional `scheme://` prefix off a host string. Lets the YAML -/// author opt into HTTPS by writing `https://srs.xyzen.cc` for `http_host` -/// / `webrtc_host`; bare `host[:port]` keeps the historical `http://` -/// default so existing dev recipes (`localhost:18085`) still work. +/// author opt into HTTPS by writing `https://playback.media.example` for +/// `http_host` / `webrtc_host`; bare `host[:port]` uses the `http://` +/// default used by local recipes (`localhost:18085`). /// -/// Why this matters: Xyzen's web frontend is served over HTTPS, and +/// Why this matters: the SciLaxy web frontend is served over HTTPS, and /// browsers refuse to `fetch()` `http://...` from an HTTPS page (mixed -/// content). Production SRS lives behind ingress-nginx with a real cert, -/// so the playback URLs must be `https://`; without this knob the runner -/// emitted `http://srs.xyzen.cc/...` and every WHEP POST was silently -/// blocked before leaving the renderer. +/// content). Deployments behind TLS termination must therefore include the +/// `https://` prefix so playback requests can leave the renderer. fn split_scheme(host: &str) -> (&'static str, &str) { if let Some(rest) = host.strip_prefix("https://") { ("https", rest) @@ -225,10 +223,15 @@ mod scheme_tests { #[test] fn https_prefix_is_honored() { - let out = endpoints(Some("https://srs.xyzen.cc"), Some("https://srs.xyzen.cc")); - assert!(find(&out, Protocol::Flv).starts_with("https://srs.xyzen.cc/")); - assert!(find(&out, Protocol::Hls).starts_with("https://srs.xyzen.cc/")); - assert!(find(&out, Protocol::Webrtc).starts_with("https://srs.xyzen.cc/rtc/v1/whep/")); + let out = endpoints( + Some("https://playback.media.example"), + Some("https://playback.media.example"), + ); + assert!(find(&out, Protocol::Flv).starts_with("https://playback.media.example/")); + assert!(find(&out, Protocol::Hls).starts_with("https://playback.media.example/")); + assert!( + find(&out, Protocol::Webrtc).starts_with("https://playback.media.example/rtc/v1/whep/") + ); } #[test] diff --git a/crates/osdl-core/src/media/onvif_camera.rs b/crates/osdl-core/src/media/onvif_camera.rs index 2149544..281ad06 100644 --- a/crates/osdl-core/src/media/onvif_camera.rs +++ b/crates/osdl-core/src/media/onvif_camera.rs @@ -106,7 +106,7 @@ pub enum H264TranscodeSource { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RemoteRtmpConfig { /// Base URL up to and including the app, no trailing slash. Example: - /// `rtmp://srs.sciol.ac.cn:1935/openSDL`. + /// `rtmp://ingest.media.example:1935/openSDL`. pub base_url: String, /// Stream name. Defaults to the camera id. @@ -114,13 +114,13 @@ pub struct RemoteRtmpConfig { pub stream: Option, /// Public host:port for HTTP-FLV / HLS playback URLs published by the - /// SRS server, e.g. `srs.sciol.ac.cn:8080`. Used only to assemble + /// SRS server, e.g. `https://playback.media.example`. Used only to assemble /// `MediaEndpoint`s for callers. #[serde(default)] pub http_host: Option, /// Public host[:port] for WebRTC playback. Example: - /// `srs.sciol.ac.cn:1985`. Used only to assemble `MediaEndpoint`s. + /// `https://playback.media.example`. Used only to assemble `MediaEndpoint`s. #[serde(default)] pub webrtc_host: Option, } diff --git a/crates/osdl-core/tests/integration.rs b/crates/osdl-core/tests/integration.rs index 1276339..a03d69a 100644 --- a/crates/osdl-core/tests/integration.rs +++ b/crates/osdl-core/tests/integration.rs @@ -26,7 +26,7 @@ fn test_load_registry() { fn test_bus_config_round_trips_and_device_types_resolve() { use osdl_core::config::{BusConfig, BusDeviceConfig}; - // Simulate what a user would put in ~/.xyzen/config.yaml — one bus + // Simulate a user configuration containing one bus // fronted by a Runze pump hardware_id, five real devices behind it. let yaml = r#" match_hardware_id: syringe_pump_with_valve.runze.SY03B-T06 diff --git a/docs/architecture.md b/docs/architecture.md index e998c83..3f23b54 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -187,18 +187,19 @@ osdl/devices/{device_id}/status # mother publishes parsed device stat osdl/devices/{device_id}/online # retained + LWT ``` -## Integration with Xyzen +## Integration with SciLaxy -When embedded in Xyzen Desktop (Tauri): +SciLaxy Desktop supervises a standalone `lab serve` process: ``` -Xyzen Cloud → WebSocket → Runner → OsdlEngine → Transport → Device +SciLaxy Cloud → WebSocket → Runner (gRPC client) → `lab serve` → Transport → Device ``` -- `osdl-core` as optional crate dependency in `xyzen-runner` (`feature = "osdl"`) -- New Runner message types: `osdl_list_devices`, `osdl_send_command`, etc. -- OsdlEvent forwarded to cloud via existing WebSocket (same pattern as PTY events) -- Desktop Tauri app also gets direct access for local device UI +- The Electron main process starts, monitors, and stops `lab serve`. +- `scilaxy-runner` depends only on `osdl-proto` behind `feature = "osdl"`. +- The Runner connects to the configured gRPC endpoint and forwards commands and + `OsdlEvent` data over its existing cloud WebSocket. +- `osdl-core` and `osdl-server` remain in the standalone OpenSDL process. ## Wireless Communication Options diff --git a/docs/cs-architecture.md b/docs/cs-architecture.md index 9e2cede..d29fc5d 100644 --- a/docs/cs-architecture.md +++ b/docs/cs-architecture.md @@ -1,8 +1,8 @@ # OpenSDL Client/Server Architecture -This document describes the post-refactor C/S architecture: how the -engine, the gRPC server, and the CLI compose, what ships in each crate, -and how the same code runs in three different deployment modes. +This document describes the C/S architecture: how the engine, the gRPC +server, and the CLI compose, what ships in each crate, and how the same +code runs in three different deployment modes. If you want a higher-level orientation, start with [`architecture.md`](architecture.md) (the engine's transport layer + @@ -10,17 +10,16 @@ hardware path) and then come back here. ## Why C/S -The engine has always been a long-running, async, stateful thing — -broker, mDNS, mediamtx, ESP-NOW dongles, SQLite event store, dozens of -in-flight devices. We needed three deployment shapes: +The engine is a long-running, async, stateful service that owns the +broker, mDNS, mediamtx, ESP-NOW dongles, SQLite event store, and +in-flight devices. It supports three deployment shapes: 1. **Local box** — engine + client on the same lab machine. 2. **Remote** — client on a workstation, engine on the lab Pi. -3. **Bundled** — desktop app embeds the engine in-process for the agent. +3. **Desktop-supervised** — Electron supervises a standalone local server. -The dominant constraint was that all three modes have to run the same -code paths. So we split the codebase into four crates with the engine -sitting under a thin gRPC adapter: +All three modes use the same engine and gRPC code paths. The workspace +separates those responsibilities into four crates: ``` crates/ @@ -30,16 +29,14 @@ crates/ └── lab-cli/ clap CLI: `serve` boots the engine, others are gRPC clients ``` -`osdl-server` is a *library*. The CLI's `lab serve` calls it; the -desktop bundle in mode 3 also calls it (in-process, against an -EngineHandle the agent already holds). Two consumers, one -implementation. +`osdl-server` is a library used by the CLI's `lab serve` command. Desktop +mode launches that command as a child process and reaches it only through +the generated gRPC interface. ## Engine: handle + loop -`OsdlEngine` was previously a single `&mut self` thing where `run()` -consumed unique receivers. That doesn't compose with multiple gRPC -subscribers. We split it into: +The engine exposes a cloneable handle so multiple gRPC subscribers can +share state while one `OsdlEngine::run()` loop owns the receivers: - **`OsdlEngine`** — owns the loop. Holds the per-loop `mpsc` receivers (transport RX, command injection, ESP-NOW REG events). One @@ -233,21 +230,24 @@ lab --endpoint http://lab.local:50051 … ───→ lab serve --listen 0.0. EngineHandle → OsdlEngine ``` -### Mode 3 — bundled (desktop) +### Mode 3 — desktop-supervised ``` -[ Tauri app ] - ├── agent (LLM) - ├── EngineHandle (direct) ←── no IPC for the agent - └── osdl_server::serve() on UDS ←── for the in-app CLI / external tools - │ - ▼ - OsdlEngine.run() +[ Electron main process ] + └── supervises `lab serve` + │ + ├── UDS (Linux/macOS) or loopback TCP (Windows) + │ +[ scilaxy-runner: osdl-proto gRPC client ] + │ + ▼ + SciLaxy Cloud WebSocket ``` -The agent gets the same `EngineHandle` API the gRPC service uses, so -agent code looks identical to library code. The UDS surface is there -for everything else (debugging, scripting, packaged tools). +The Runner does not link `osdl-core` or hold an `EngineHandle`. Electron +selects the endpoint, starts the server, waits for readiness, and keeps +the Runner configuration aligned with that endpoint. A lab Pi may instead +run `lab serve` independently and expose a configured TCP endpoint. ## Observability and error handling diff --git a/docs/getting-started.md b/docs/getting-started.md index 3bbe319..c61f0b7 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -440,13 +440,15 @@ mosquitto_pub -h localhost -t "osdl/serial/pump-01/tx" -m "/1ZR 3. 在 `adapter/unilabos.rs` 中注册路由 4. 写测试 → 跑通 → 提交 -### 接入 Xyzen Desktop +### 接入 SciLaxy Desktop ``` -Xyzen Desktop (Tauri) → Runner → OsdlEngine → ESP32 → 设备 +SciLaxy Desktop (Electron) → Runner (gRPC 客户端) → `lab serve` → ESP32 → 设备 ``` -OpenSDL 引擎会嵌入到 Xyzen Desktop 应用中,通过 Runner 暴露给前端 UI 和云端 Agent,实现 AI 直接控制实验室硬件。 +Electron 主进程负责启动和监管独立的 `lab serve` 进程。Runner 仅依赖 +`osdl-proto`,连接用户配置的 gRPC 端点,并通过现有 WebSocket 在云端与 +OpenSDL 之间转发命令和事件;OpenSDL 引擎不会嵌入 Runner。 --- diff --git a/docs/recipes/bus-manifest.md b/docs/recipes/bus-manifest.md index 00e094d..72bdb09 100644 --- a/docs/recipes/bus-manifest.md +++ b/docs/recipes/bus-manifest.md @@ -1,10 +1,9 @@ # Recipe — Verify a bus manifest -Replaces `bus_manifest_live.rs`. Boots a server with the ChinWe bus -manifest, waits for the node to register, and prints the resulting -device set so you can confirm the engine built 5 independently- -addressable records — same end state Xyzen Runner reaches when the user -drops the same manifest into `~/.xyzen/config.yaml`. +Boots a server with the ChinWe bus manifest, waits for the node to +register, and prints the resulting device set so you can confirm the +engine built 5 independently-addressable records from the configured +manifest. No commands are sent; the recipe is purely about REG-time validation. diff --git a/docs/recipes/configs/onvif-camera.yaml b/docs/recipes/configs/onvif-camera.yaml index d782d37..aaa2246 100644 --- a/docs/recipes/configs/onvif-camera.yaml +++ b/docs/recipes/configs/onvif-camera.yaml @@ -43,25 +43,20 @@ media_sources: # http_host: localhost:18085 # HLS / FLV egress (http_server port) # webrtc_host: localhost:1985 # WHEP signalling (http_api port) # - # --- Production Xyzen Shanghai SRS --- - # Why two hosts: - # * base_url uses srs-rtc.xyzen.cc — that name resolves to the - # dedicated NLB which exposes RTMP 1935/TCP and WebRTC media - # 8000/UDP. - # * http_host / webrtc_host use srs.xyzen.cc — that name resolves - # to the cluster's ingress-nginx LB and serves HLS playlists + - # WHEP signalling over HTTPS. The WebRTC media itself still goes - # to srs-rtc:8000/UDP via the SDP `a=candidate` line that SRS - # fills in from its CANDIDATE env var. + # --- Configurable remote SRS example --- + # `base_url` is the RTMP ingest endpoint. `http_host` and `webrtc_host` + # are browser-reachable playback and signalling endpoints; they may be + # the same host when one TLS proxy serves both routes. Replace the + # reserved example domains with endpoints from your deployment. # # Note the `https://` prefix on http_host / webrtc_host below. # When the host is bare (e.g. `localhost:18085`) the engine emits # `http://...` URLs; prefix with `https://` (or `http://` to be # explicit) when SRS is fronted by HTTPS ingress. Browser frontends # served over HTTPS will refuse to fetch `http://` endpoints, so - # production deployments must use the prefixed form. + # TLS deployments must use the prefixed form. # remote_rtmp: - # base_url: rtmp://srs-rtc.xyzen.cc:1935/live + # base_url: rtmp://ingest.media.example:1935/live # stream: lab-1 - # http_host: https://srs.xyzen.cc - # webrtc_host: https://srs.xyzen.cc + # http_host: https://playback.media.example + # webrtc_host: https://playback.media.example diff --git a/docs/recipes/media-gateway.md b/docs/recipes/media-gateway.md index f049be0..202663c 100644 --- a/docs/recipes/media-gateway.md +++ b/docs/recipes/media-gateway.md @@ -176,31 +176,29 @@ and uses the H.264 SRS output before trying any local HEVC HLS path. The same endpoint list is sent to owner and teammate — only the browser's reach differs. -### Production SRS (Xyzen Shanghai) +### Remote SRS deployment -Production SRS lives in the Shanghai cluster -(`kubernetes/shk/xyzen-srs/`). Two public hostnames, because the media -plane and the HTTP plane have different transport requirements: +Configure endpoints from the SRS deployment that will receive the stream. +Larger deployments commonly use separate names because the media plane and +the HTTP plane have different transport requirements: -- `srs-rtc.xyzen.cc` — dedicated Aliyun NLB. Exposes **RTMP 1935/TCP** - (where mediamtx pushes) and **WebRTC media 8000/UDP** (what SRS bakes - into the SDP `a=candidate` line, so browsers connect to it directly). -- `srs.xyzen.cc` — ingress-nginx (HTTPS). Serves **HLS playlists** and - **WHEP signalling** (the `/rtc/v1/whep/` POST). Ingress-nginx is HTTP - only — **don't** push RTMP here. +- The ingest endpoint accepts **RTMP over TCP** from mediamtx and may also + advertise a browser-reachable **WebRTC media** address in its SDP. +- The playback endpoint serves **HLS playlists**, HTTP-FLV, and **WHEP + signalling**. Do not send RTMP to an HTTP-only ingress. -Camera YAML for the production SRS: +The reserved domains below are placeholders, not deployed SciLaxy services: ```yaml remote_rtmp: - base_url: rtmp://srs-rtc.xyzen.cc:1935/live + base_url: rtmp://ingest.media.example:1935/live stream: lab-1 - http_host: https://srs.xyzen.cc - webrtc_host: https://srs.xyzen.cc + http_host: https://playback.media.example + webrtc_host: https://playback.media.example ``` -The `https://` prefix on `http_host` / `webrtc_host` is required for -production: the engine emits `http://...` URLs by default (matching the +The `https://` prefix on `http_host` / `webrtc_host` is required for TLS +deployments: the engine emits `http://...` URLs by default (matching the bare `localhost:18085` form used in dev), and browsers loaded over HTTPS refuse to `fetch()` `http://` resources (mixed content). Prefixing the host with `https://` switches the emitted endpoint URLs @@ -211,7 +209,9 @@ Smoke-testing without a camera: ```sh ffmpeg -re -f lavfi -i testsrc -c:v libx264 -f flv \ - rtmp://srs-rtc.xyzen.cc:1935/live/test + rtmp://ingest.media.example:1935/live/test # then check the stream is listed: -curl -s https://srs.xyzen.cc/api/v1/streams/ | jq +curl -s https://playback.media.example/api/v1/streams/ | jq ``` + +Replace both example domains before running the smoke test.