Bilingual documentation: English default + Chinese .zh-cn.md (0.5.1)
- Every doc (README, AGENT, docs/*) now ships as English under the default filename and Chinese as *.zh-cn.md; both versions cross-link in their first line, in-document links stay within the same language - AGENT.md §7 records the keep-both-languages-in-sync rule (same commit, no translating later); CHANGELOG kept in both languages - Docs-only change; version 0.5.1 synced in the usual three places
This commit is contained in:
1 parent
8b1855b229
commit
9284a9c480
20 files changed
+1545
-527
No files matched your search
@@ -1,105 +1,150 @@
|
||||
# AGENT.md — 后续 Agent 必读(坑与约定)
|
||||
**English** | [简体中文](AGENT.zh-cn.md)
|
||||
|
||||
本文件沉淀开发至今的全部环境坑、架构约定与验证方法。**动手前先读完本文。**
|
||||
# AGENT.md — required reading for follow-up agents (gotchas & conventions)
|
||||
|
||||
## 0. 快速上手
|
||||
This file captures every environment pitfall, architecture rule and
|
||||
verification method learned so far. **Read it fully before touching anything.**
|
||||
(Chinese version: AGENT.zh-cn.md — keep both in sync, see §7.)
|
||||
|
||||
## 0. Quick start
|
||||
|
||||
```bash
|
||||
npm install # typescript 走 npmmirror(见 §1)
|
||||
npm run build # tsc:src/ → build/(改 src 后必须;build/ 不入 git)
|
||||
npm test # 单元测试 + 集成冒烟
|
||||
npm install # typescript via npmmirror (see §1)
|
||||
npm run build # tsc: src/ → build/ (mandatory after src changes; build/ not in git)
|
||||
npm test # unit tests + integration smoke
|
||||
npm run serve # http://127.0.0.1:8080/esp-emu-peris.html
|
||||
npm run sync # 发布进 _tools/esp-emu-browser/www(先 build;--clean 清残留)
|
||||
npm run sync # publish into _tools/esp-emu-browser/www (build first; --clean removes stale output)
|
||||
```
|
||||
|
||||
架构与数据流见 `docs/ARCHITECTURE.md`;外设开发见 `docs/DEVICE-API.md`;
|
||||
esp-emulator wasm 能力边界见 `docs/EMU-API.md` 与 `docs/FEASIBILITY.md`(M0 实测)。
|
||||
Architecture & data flow: `docs/ARCHITECTURE.md`; peripheral development:
|
||||
`docs/DEVICE-API.md`; esp-emulator wasm capability limits: `docs/EMU-API.md`
|
||||
and `docs/FEASIBILITY.md` (M0 measurements).
|
||||
|
||||
## 1. 环境坑(本机)
|
||||
## 1. Environment pitfalls (this machine)
|
||||
|
||||
- **CDN 不可达**:jsdelivr 直连被重置。npm 用镜像:
|
||||
`npm --registry=https://registry.npmmirror.com install ...`;静态资源从
|
||||
`https://registry.npmmirror.com/<pkg>/-/<name>-<ver>.tgz` 下 tarball 解包
|
||||
(xterm 就是这样 vendor 进 `peris/vendor/` 的;包内是**非压缩**版,无 .min)。
|
||||
- **Playwright 浏览器二进制**未装(网络);要装:
|
||||
`PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright`。
|
||||
- **ESP-IDF**:master 检出在 `C:\esp\master\esp-idf`,环境启用用
|
||||
`C:\Users\A\Desktop\quick\idfm.bat`(PowerShell profile)。脚本化调用**必须清掉
|
||||
`MSYSTEM` 环境变量**(Git Bash 继承它,idf.py 会拒绝),参考 `tests/firmware/build.ps1`。
|
||||
- 固件加载进浏览器**必须 `idf.py merge-bin`** 的 merged image;裸 app bin 无声不启动
|
||||
(症状:UART 永远无输出)。产物:`tests/firmware/peris_probe/build/merged-binary.bin`。
|
||||
- `node --test <目录>` 在 Windows/Git Bash 下解析异常,**显式传 .test.mjs 文件列表**。
|
||||
- **CDN unreachable**: direct jsdelivr connections are reset. Use a mirror for
|
||||
npm: `npm --registry=https://registry.npmmirror.com install ...`; fetch
|
||||
static assets as tarballs from
|
||||
`https://registry.npmmirror.com/<pkg>/-/<name>-<ver>.tgz` and unpack
|
||||
(that is how xterm was vendored into `peris/vendor/`; the package ships the
|
||||
**unminified** build, no .min).
|
||||
- **Playwright browser binaries** not installed (network); to install:
|
||||
`PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright`.
|
||||
- **ESP-IDF**: master checkout at `C:\esp\master\esp-idf`; enable the
|
||||
environment with `C:\Users\A\Desktop\quick\idfm.bat` (PowerShell profile).
|
||||
Scripted invocation **must clear the `MSYSTEM` env var** (Git Bash inherits
|
||||
it and idf.py refuses); see `tests/firmware/build.ps1`.
|
||||
- Firmware must be loaded into the browser as the **`idf.py merge-bin` merged
|
||||
image**; a bare app bin silently never starts (symptom: UART stays quiet
|
||||
forever). Artifact: `tests/firmware/peris_probe/build/merged-binary.bin`.
|
||||
- `node --test <directory>` resolves incorrectly on Windows/Git Bash —
|
||||
**pass the .test.mjs file list explicitly** (package.json does).
|
||||
|
||||
## 2. esp-emulator wasm 行为(实测,勿凭直觉)
|
||||
## 2. esp-emulator wasm behaviour (measured — do not guess)
|
||||
|
||||
- **没有回调**,一切靠 `run_batch()` 批间轮询;UART 输出是 `run_batch` 的返回字符串。
|
||||
- **LEDC/RMT 波形不出现在 `gpio_state()`**(它们走 GPIO matrix);只有 CPU 直写 GPIO 可见。
|
||||
PWM→读 LEDC `DUTY_R`(值=duty×16),WS2812→读 RMT 通道 RAM(发送后留存)。
|
||||
地址表:`src/core/mmio/`,来源与验证:FEASIBILITY.md。
|
||||
- **`vTaskDelay` 期间时间快进**:睡眠窗口内引脚级波形不可捕获,与采样率无关。
|
||||
CPU 活跃的位绑定波形可采样,但**同一批次间隙内连续翻转输入会丢边沿**
|
||||
(press→release 必须隔批次,见 m1_smoke S3)。
|
||||
- `gpio_set_input` 默认电平高(上拉);能触发固件 GPIO 中断(可靠)。
|
||||
- 新 `WasmEmulator` 实例后输入电平复位 → 设备必须在 `onEmuReady()` 重驱动。
|
||||
- **No callbacks**: everything is `run_batch()` polling between batches; UART
|
||||
output is the return string of `run_batch`.
|
||||
- **LEDC/RMT waveforms never appear in `gpio_state()`** (they go through the
|
||||
GPIO matrix); only CPU-direct GPIO writes are visible.
|
||||
PWM → read the LEDC `DUTY_R` (value = duty×16), WS2812 → read the RMT
|
||||
channel RAM (left over after transmission).
|
||||
Address tables: `src/core/mmio/`; sources & verification: FEASIBILITY.md.
|
||||
- **Time fast-forwards during `vTaskDelay`**: pin-level waveforms inside a
|
||||
sleep window cannot be captured, regardless of sample rate.
|
||||
Waveforms of CPU-active bit-banged signals can be sampled, but **consecutive
|
||||
input flips within one batch gap lose edges** (press→release must straddle
|
||||
batches; see m1_smoke S3).
|
||||
- `gpio_set_input` defaults to a high level (pull-up); it reliably raises the
|
||||
firmware's GPIO interrupts.
|
||||
- After a fresh `WasmEmulator` instance the input levels reset → devices must
|
||||
re-drive in `onEmuReady()`.
|
||||
|
||||
## 3. 架构铁律(docs/ARCHITECTURE.md 有完整图)
|
||||
## 3. Architecture iron rules (full picture in docs/ARCHITECTURE.md)
|
||||
|
||||
1. **模拟器只在 worker**:页面永不 import wasm;一切经 `src/ui/worker-client.ts`。
|
||||
2. **worker 里唯一允许复刻上游逻辑的文件是 `src/core/worker.ts`**,改动必须带
|
||||
`peris:` 标记,便于上游升级 diff(docs/UPGRADE.md 流程)。
|
||||
3. **设备插件是原生 JS**(丢文件即用,不参与构建);框架是 TS。别给 devices/ 加
|
||||
import 依赖到 src/ 运行时模块(可用 `build/core/*.d.ts` 类型做 JSDoc 提示)。
|
||||
4. 插件元数据必须含 `static pins(cfg)`(冲突检测)、`typeId`、`title`、
|
||||
`defaultConfig`,可选 `version`;registry 加载时校验,**缺了当场报错**。
|
||||
5. 配置以页面 `PerisPanel.config` 为唯一事实源,变更全量下发+localStorage;
|
||||
不要在 worker 侧缓存/修改配置。
|
||||
6. 状态推送有 33ms 节流 + **尾随定时器**(保证最后一次 dirty 必达)——别删掉尾随逻辑,
|
||||
那是修过的真实缺陷(状态静默丢失)。
|
||||
7. sigbus 按批做 gpio_state 差分,**联合掩码**(out|oe)每引脚每批至多通知一次。
|
||||
8. **同语义代码必须复用,禁止复制一份再改**:同一逻辑只允许一份实现,所有
|
||||
入口(行内 chip / 详情编辑器 / 行图标 / 新插件)调用同一函数——
|
||||
GPIO 可选项= `config-editor.ts fillPinOptions()`;V·G 徽章推导=`rowChips()`;
|
||||
插件多视觉元素(如 LED 灯泡+行图标)共用同一 setter/render,不许给图标
|
||||
单写一份样式逻辑;面板对 View 固定 **先 `rowIcon` 后 `setConfig`**,
|
||||
保证配置式样能覆盖到图标。改行为前先找有没有既有函数,有就复用;
|
||||
review 发现两处雷同代码时,先抽公共函数再改。
|
||||
1. **The emulator lives only in the worker**: the page never imports the wasm;
|
||||
everything goes through `src/ui/worker-client.ts`.
|
||||
2. **The only file allowed to replicate upstream logic is `src/core/worker.ts`**
|
||||
— every change there must carry a `peris:` marker for upstream-upgrade
|
||||
diffs (procedure in docs/UPGRADE.md).
|
||||
3. **Device plugins are plain JS** (drop a file and it works, no build); the
|
||||
framework is TypeScript. Do not add imports from devices/ into src/ runtime
|
||||
modules (compiled `build/core/*.d.ts` types may be used for JSDoc hints).
|
||||
4. Plugin metadata must include `static pins(cfg)` (conflict detection),
|
||||
`typeId`, `title`, `defaultConfig`, optional `version`; the registry
|
||||
validates at load time and **errors out immediately when missing**.
|
||||
5. The page's `PerisPanel.config` is the single source of truth for
|
||||
configuration: every change is sent in full + localStorage; never cache or
|
||||
mutate config on the worker side.
|
||||
6. State pushes have a 33ms throttle + a **trailing timer** (guarantees the
|
||||
last dirty flag is always delivered) — do not remove the trailing flush;
|
||||
it fixed a real defect (silently dropped state).
|
||||
7. sigbus diffs `gpio_state` per batch with the **union mask** (out|oe),
|
||||
notifying each pin at most once per batch.
|
||||
8. **Same-semantic code must be reused — never copy and modify**: one logic,
|
||||
one implementation, and every entry point (row chip / details editor / row
|
||||
icon / new plugin) calls the same function — GPIO options =
|
||||
`fillPinOptions()` in `config-editor.ts`; V·G badge derivation =
|
||||
`rowChips()`; a plugin's multiple visuals (LED bulb + row icon) share one
|
||||
setter/render — never write a second styling path for the icon; the panel
|
||||
calls View hooks in a fixed order, **`rowIcon` before `setConfig`**, so
|
||||
config-driven styling covers the icon too. Before changing behaviour, look
|
||||
for an existing function and reuse it; when review finds duplicated code,
|
||||
extract the shared function first, then change it.
|
||||
|
||||
## 4. 曾踩过的 bug(防止回潮)
|
||||
## 4. Past bugs (prevent regressions)
|
||||
|
||||
| bug | 教训 |
|
||||
| bug | lesson |
|
||||
|---|---|
|
||||
| registry 把 manifest 模块路径解析到 `peris/` 根 | manifest 路径相对 devices/ 目录,编译位置变化要同步改 `DEVICES_DIR` |
|
||||
| 设备构造函数里 `ctx.subscribe()` 时实例未注册 | sigbus 存**取值函数**惰性解析,不要存实例 |
|
||||
| 错误条 `style.display=''` 回退 CSS `none` 永不可见 | 显式 `display='block'`,且无错时清空 textContent(避免读到陈旧报错) |
|
||||
| `peris_config` 早于 registry init 到达 → 全部 unknown type | `applyConfig` 先 await init |
|
||||
| 设备漏写 `static pins()` → 冲突检测静默失效 | registry 强制校验 + 单测覆盖 |
|
||||
| 按键 activeLow 电平写反 | idle=高、pressed=低(active-low 上拉接法);单测固定语义 |
|
||||
| Worker URL 相对**页面**解析,不是相对 import 的模块 | worker-client 里写页面相对路径 `build/core/worker.js` |
|
||||
| 节流窗口丢最后一次状态 | 尾随 flush(见 §3.7) |
|
||||
| LED 行图标颜色与详情灯泡不一致(0.3.0) | 面板曾先 setConfig 后 rowIcon,且插件两处各自设色;统一 setColour + 固定调用顺序(§3.8) |
|
||||
| 换芯片加载固件后,详情编辑器 GPIO 选项仍是旧芯片(0.3.0) | 编辑器把 validPins 快照在构建时;芯片变化必须重建编辑器;选项构建统一走 fillPinOptions(§3.8) |
|
||||
| registry resolved manifest module paths to the `peris/` root | manifest paths are relative to the devices/ dir; update `DEVICES_DIR` when compile locations move |
|
||||
| `ctx.subscribe()` in a device constructor ran before the instance was registered | sigbus stores a **getter function** for lazy resolution — never the instance |
|
||||
| error bar `style.display=''` fell back to CSS `none`, never visible | set `display='block'` explicitly, and clear textContent when there is no error (stale messages) |
|
||||
| `peris_config` arrived before registry init → everything "unknown type" | `applyConfig` awaits init first |
|
||||
| a device forgot `static pins()` → conflict detection silently off | registry enforces + unit-tested |
|
||||
| button activeLow levels inverted | idle = high, pressed = low (active-low pull-up wiring); semantics pinned by unit tests |
|
||||
| worker URL resolved relative to the **page**, not the importing module | worker-client hard-codes the page-relative path `build/core/worker.js` |
|
||||
| throttle window dropped the last state | trailing flush (see §3.6) |
|
||||
| LED row-icon colour disagreed with the details bulb (0.3.0) | the panel called setConfig before rowIcon and the plugin styled the two in separate places; unified `setColour` + fixed call order (§3.8) |
|
||||
| after loading firmware for another chip, the details editor kept the old chip's GPIO options (0.3.0) | the editor snapshot `validPins` at build time; chip changes must rebuild editors, and option building goes through `fillPinOptions` (§3.8) |
|
||||
|
||||
## 5. 测试与验证约定
|
||||
## 5. Testing & verification conventions
|
||||
|
||||
- **单元测试**(node:test,零依赖):纯逻辑——sigbus 位运算、电平语义、配置校验、
|
||||
MMIO 地址表。改 core/devices 必跑;新增设备请仿照 `tests/unit/devices.test.mjs`
|
||||
补电平/状态语义用例。
|
||||
- **集成冒烟** `tests/m1_smoke.mjs`:真实 wasm+固件+runtime+设备。**时序经验**:
|
||||
- boot 约占 500+ 批(50k 指令/批),别盲选批次号,**按 UART 标记驱动**
|
||||
(固件用 `###P1 start/end` 等标记,见 `tests/firmware/peris_probe/main/main.c`);
|
||||
- 固件在每轮 P1 start 把按键计数**清零**,计数可能只出现在 `###P1 end btn=N` 行,
|
||||
断言正则别只匹配 `i=` 行;
|
||||
- flushStates 有节流,读状态断言前 `await` ~60ms。
|
||||
- **M0 探针** `tests/m0_probe.mjs`:上游 wasm 升级后**第一个跑**的东西,六项
|
||||
结论(Q1a..Q3b)若有变化先改 FEASIBILITY.md/EMU-API.md 再动其它代码。
|
||||
- **GUI 冒测**(手动,清单在 ARCHITECTURE.md §5):本会话所用 IAB 自动化易劣化
|
||||
(点击 actionability 超时、截图 surface 失败、xterm DOM 只渲染视口行且 rAF
|
||||
停滞时整块冻结)——**DOM 只读断言最可靠**;固件注入可用页面自身 drop 处理器
|
||||
(同源 fetch → File → DataTransfer → drop 事件)作为环境准备。
|
||||
- **Unit tests** (node:test, zero deps): pure logic — sigbus bit math, level
|
||||
semantics, config validation, MMIO address tables. Mandatory after changing
|
||||
core/devices; for a new device, follow `tests/unit/devices.test.mjs` and add
|
||||
level/state semantics cases.
|
||||
- **Integration smoke** `tests/m1_smoke.mjs`: real wasm + firmware + runtime +
|
||||
devices. **Timing lessons**:
|
||||
- boot takes ~500+ batches (50k instr/batch) — do not hard-code batch
|
||||
numbers, **drive by UART markers** (the firmware prints `###P1 start/end`
|
||||
etc., see `tests/firmware/peris_probe/main/main.c`);
|
||||
- the firmware resets the button counter at each P1 start, so the count may
|
||||
only appear on the `###P1 end btn=N` line — do not match only `i=` lines;
|
||||
- flushStates is throttled; `await` ~60ms before asserting on state.
|
||||
- **M0 probe** `tests/m0_probe.mjs`: after an upstream wasm upgrade this is
|
||||
**the first thing to run**; if any of the six conclusions (Q1a..Q3b)
|
||||
changed, update FEASIBILITY.md/EMU-API.md before touching other code.
|
||||
- **GUI smoke** (manual; checklist in ARCHITECTURE.md §5): the IAB automation
|
||||
used in past sessions degrades easily (click actionability timeouts,
|
||||
screenshot surface failures, xterm DOM rendering only viewport rows and
|
||||
freezing whole blocks when rAF stalls) — **read-only DOM assertions are the
|
||||
most reliable**; firmware injection can use the page's own drop handler
|
||||
(same-origin fetch → File → DataTransfer → drop event) as environment setup.
|
||||
|
||||
## 6. 版本与发布
|
||||
## 6. Versioning & releases
|
||||
|
||||
- 项目版本:`src/core/version.ts` + `package.json` + `docs/CHANGELOG.md` 三处同步。
|
||||
- 设备版本:各插件 `static version`,卡片头展示;CHANGELOG 有设备版本表。
|
||||
- 发进 www 用 `npm run sync`(--clean 清旧产物);www 原版文件(index.html/app.js/
|
||||
worker.js/pkg/serve.py)**永远不动**。
|
||||
- Project version: synced in three places — `src/core/version.ts` +
|
||||
`package.json` + `docs/CHANGELOG.md`.
|
||||
- Device versions: each plugin's `static version`, shown in the row header;
|
||||
CHANGELOG keeps a device version table.
|
||||
- Publish into www with `npm run sync` (--clean removes stale output); the
|
||||
stock www files (index.html/app.js/worker.js/pkg/serve.py) are **never
|
||||
touched**.
|
||||
|
||||
## 7. Documentation conventions (bilingual)
|
||||
|
||||
- Every document ships in two languages: **English under the default
|
||||
filename**, **Chinese as `*.zh-cn.md`** (README/AGENT/docs/*); the two
|
||||
versions cross-link in their first line, and in-document links stay within
|
||||
the same language.
|
||||
- Any documentation change must be applied to **both languages** in the same
|
||||
commit — no translating later.
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
[English](AGENT.md) | **简体中文**
|
||||
|
||||
# AGENT.md — 后续 Agent 必读(坑与约定)
|
||||
|
||||
本文件沉淀开发至今的全部环境坑、架构约定与验证方法。**动手前先读完本文。**
|
||||
(英文版:AGENT.md——两种语言需同步修改,见 §7。)
|
||||
|
||||
## 0. 快速上手
|
||||
|
||||
```bash
|
||||
npm install # typescript 走 npmmirror(见 §1)
|
||||
npm run build # tsc:src/ → build/(改 src 后必须;build/ 不入 git)
|
||||
npm test # 单元测试 + 集成冒烟
|
||||
npm run serve # http://127.0.0.1:8080/esp-emu-peris.html
|
||||
npm run sync # 发布进 _tools/esp-emu-browser/www(先 build;--clean 清残留)
|
||||
```
|
||||
|
||||
架构与数据流见 `docs/ARCHITECTURE.zh-cn.md`;外设开发见 `docs/DEVICE-API.zh-cn.md`;
|
||||
esp-emulator wasm 能力边界见 `docs/EMU-API.zh-cn.md` 与 `docs/FEASIBILITY.zh-cn.md`(M0 实测)。
|
||||
|
||||
## 1. 环境坑(本机)
|
||||
|
||||
- **CDN 不可达**:jsdelivr 直连被重置。npm 用镜像:
|
||||
`npm --registry=https://registry.npmmirror.com install ...`;静态资源从
|
||||
`https://registry.npmmirror.com/<pkg>/-/<name>-<ver>.tgz` 下 tarball 解包
|
||||
(xterm 就是这样 vendor 进 `peris/vendor/` 的;包内是**非压缩**版,无 .min)。
|
||||
- **Playwright 浏览器二进制**未装(网络);要装:
|
||||
`PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright`。
|
||||
- **ESP-IDF**:master 检出在 `C:\esp\master\esp-idf`,环境启用用
|
||||
`C:\Users\A\Desktop\quick\idfm.bat`(PowerShell profile)。脚本化调用**必须清掉
|
||||
`MSYSTEM` 环境变量**(Git Bash 继承它,idf.py 会拒绝),参考 `tests/firmware/build.ps1`。
|
||||
- 固件加载进浏览器**必须 `idf.py merge-bin`** 的 merged image;裸 app bin 无声不启动
|
||||
(症状:UART 永远无输出)。产物:`tests/firmware/peris_probe/build/merged-binary.bin`。
|
||||
- `node --test <目录>` 在 Windows/Git Bash 下解析异常,**显式传 .test.mjs 文件列表**
|
||||
(package.json 已如此)。
|
||||
|
||||
## 2. esp-emulator wasm 行为(实测,勿凭直觉)
|
||||
|
||||
- **没有回调**,一切靠 `run_batch()` 批间轮询;UART 输出是 `run_batch` 的返回字符串。
|
||||
- **LEDC/RMT 波形不出现在 `gpio_state()`**(它们走 GPIO matrix);只有 CPU 直写 GPIO 可见。
|
||||
PWM→读 LEDC `DUTY_R`(值=duty×16),WS2812→读 RMT 通道 RAM(发送后留存)。
|
||||
地址表:`src/core/mmio/`,来源与验证:FEASIBILITY.zh-cn.md。
|
||||
- **`vTaskDelay` 期间时间快进**:睡眠窗口内引脚级波形不可捕获,与采样率无关。
|
||||
CPU 活跃的位绑定波形可采样,但**同一批次间隙内连续翻转输入会丢边沿**
|
||||
(press→release 必须隔批次,见 m1_smoke S3)。
|
||||
- `gpio_set_input` 默认电平高(上拉);能触发固件 GPIO 中断(可靠)。
|
||||
- 新 `WasmEmulator` 实例后输入电平复位 → 设备必须在 `onEmuReady()` 重驱动。
|
||||
|
||||
## 3. 架构铁律(docs/ARCHITECTURE.zh-cn.md 有完整图)
|
||||
|
||||
1. **模拟器只在 worker**:页面永不 import wasm;一切经 `src/ui/worker-client.ts`。
|
||||
2. **worker 里唯一允许复刻上游逻辑的文件是 `src/core/worker.ts`**,改动必须带
|
||||
`peris:` 标记,便于上游升级 diff(docs/UPGRADE.zh-cn.md 流程)。
|
||||
3. **设备插件是原生 JS**(丢文件即用,不参与构建);框架是 TS。别给 devices/ 加
|
||||
import 依赖到 src/ 运行时模块(可用 `build/core/*.d.ts` 类型做 JSDoc 提示)。
|
||||
4. 插件元数据必须含 `static pins(cfg)`(冲突检测)、`typeId`、`title`、
|
||||
`defaultConfig`,可选 `version`;registry 加载时校验,**缺了当场报错**。
|
||||
5. 配置以页面 `PerisPanel.config` 为唯一事实源,变更全量下发+localStorage;
|
||||
不要在 worker 侧缓存/修改配置。
|
||||
6. 状态推送有 33ms 节流 + **尾随定时器**(保证最后一次 dirty 必达)——别删掉尾随逻辑,
|
||||
那是修过的真实缺陷(状态静默丢失)。
|
||||
7. sigbus 按批做 gpio_state 差分,**联合掩码**(out|oe)每引脚每批至多通知一次。
|
||||
8. **同语义代码必须复用,禁止复制一份再改**:同一逻辑只允许一份实现,所有
|
||||
入口(行内 chip / 详情编辑器 / 行图标 / 新插件)调用同一函数——
|
||||
GPIO 可选项= `config-editor.ts fillPinOptions()`;V·G 徽章推导=`rowChips()`;
|
||||
插件多视觉元素(如 LED 灯泡+行图标)共用同一 setter/render,不许给图标
|
||||
单写一份样式逻辑;面板对 View 固定 **先 `rowIcon` 后 `setConfig`**,
|
||||
保证配置式样能覆盖到图标。改行为前先找有没有既有函数,有就复用;
|
||||
review 发现两处雷同代码时,先抽公共函数再改。
|
||||
|
||||
## 4. 曾踩过的 bug(防止回潮)
|
||||
|
||||
| bug | 教训 |
|
||||
|---|---|
|
||||
| registry 把 manifest 模块路径解析到 `peris/` 根 | manifest 路径相对 devices/ 目录,编译位置变化要同步改 `DEVICES_DIR` |
|
||||
| 设备构造函数里 `ctx.subscribe()` 时实例未注册 | sigbus 存**取值函数**惰性解析,不要存实例 |
|
||||
| 错误条 `style.display=''` 回退 CSS `none` 永不可见 | 显式 `display='block'`,且无错时清空 textContent(避免读到陈旧报错) |
|
||||
| `peris_config` 早于 registry init 到达 → 全部 unknown type | `applyConfig` 先 await init |
|
||||
| 设备漏写 `static pins()` → 冲突检测静默失效 | registry 强制校验 + 单测覆盖 |
|
||||
| 按键 activeLow 电平写反 | idle=高、pressed=低(active-low 上拉接法);单测固定语义 |
|
||||
| Worker URL 相对**页面**解析,不是相对 import 的模块 | worker-client 里写页面相对路径 `build/core/worker.js` |
|
||||
| 节流窗口丢最后一次状态 | 尾随 flush(见 §3.6) |
|
||||
| LED 行图标颜色与详情灯泡不一致(0.3.0) | 面板曾先 setConfig 后 rowIcon,且插件两处各自设色;统一 setColour + 固定调用顺序(§3.8) |
|
||||
| 换芯片加载固件后,详情编辑器 GPIO 选项仍是旧芯片(0.3.0) | 编辑器把 validPins 快照在构建时;芯片变化必须重建编辑器;选项构建统一走 fillPinOptions(§3.8) |
|
||||
|
||||
## 5. 测试与验证约定
|
||||
|
||||
- **单元测试**(node:test,零依赖):纯逻辑——sigbus 位运算、电平语义、配置校验、
|
||||
MMIO 地址表。改 core/devices 必跑;新增设备请仿照 `tests/unit/devices.test.mjs`
|
||||
补电平/状态语义用例。
|
||||
- **集成冒烟** `tests/m1_smoke.mjs`:真实 wasm+固件+runtime+设备。**时序经验**:
|
||||
- boot 约占 500+ 批(50k 指令/批),别盲选批次号,**按 UART 标记驱动**
|
||||
(固件用 `###P1 start/end` 等标记,见 `tests/firmware/peris_probe/main/main.c`);
|
||||
- 固件在每轮 P1 start 把按键计数**清零**,计数可能只出现在 `###P1 end btn=N` 行,
|
||||
断言正则别只匹配 `i=` 行;
|
||||
- flushStates 有节流,读状态断言前 `await` ~60ms。
|
||||
- **M0 探针** `tests/m0_probe.mjs`:上游 wasm 升级后**第一个跑**的东西,六项
|
||||
结论(Q1a..Q3b)若有变化先改 FEASIBILITY/EMU-API 文档再动其它代码。
|
||||
- **GUI 冒测**(手动,清单在 ARCHITECTURE §5):本会话所用 IAB 自动化易劣化
|
||||
(点击 actionability 超时、截图 surface 失败、xterm DOM 只渲染视口行且 rAF
|
||||
停滞时整块冻结)——**DOM 只读断言最可靠**;固件注入可用页面自身 drop 处理器
|
||||
(同源 fetch → File → DataTransfer → drop 事件)作为环境准备。
|
||||
|
||||
## 6. 版本与发布
|
||||
|
||||
- 项目版本:`src/core/version.ts` + `package.json` + `docs/CHANGELOG.zh-cn.md`(英文版
|
||||
CHANGELOG.md 同步)三处同步。
|
||||
- 设备版本:各插件 `static version`,行头展示;CHANGELOG 有设备版本表。
|
||||
- 发进 www 用 `npm run sync`(--clean 清旧产物);www 原版文件(index.html/app.js/
|
||||
worker.js/pkg/serve.py)**永远不动**。
|
||||
|
||||
## 7. 文档约定(双语)
|
||||
|
||||
- 所有文档两种语言:**英文版用默认文件名**,**中文版 `*.zh-cn.md`**
|
||||
(README/AGENT/docs/*);两版第一行互链,文内链接保持同语言跳转。
|
||||
- 任何文档改动必须在**同一次提交**里落到两种语言——不许"以后再翻译"。
|
||||
@@ -1,74 +1,86 @@
|
||||
**English** | [简体中文](README.zh-cn.md)
|
||||
|
||||
# esp-emu-peris
|
||||
|
||||
为乐鑫 [esp-emulator](https://github.com/espressif/esp-emulator) 浏览器形态(wasm)做的**模拟外设面板**:LED、按键、触摸按键、拨动开关……以插件方式不断添加,不需要改动官方 `www/` 里的任何基础文件。
|
||||
**Simulated peripherals** for the browser (wasm) build of Espressif's
|
||||
[esp-emulator](https://github.com/espressif/esp-emulator): LEDs, push buttons,
|
||||
touch buttons, toggle switches… — added as plugins, without touching any stock
|
||||
file in the official `www/`.
|
||||
|
||||
```
|
||||
esp-emu-peris.html 入口页面(UART0 控制台 | 外设行式面板 | Debug,三栏同屏)
|
||||
src/ TypeScript 源码(npm run build → build/)
|
||||
core/ worker 侧框架:version/types/protocol/chips/mmio/sigbus/registry/runtime/worker
|
||||
ui/ 页面侧模块:app/worker-client/terminal/chart/debug-panels/peris-panel/config-editor
|
||||
build/ 编译产物(tsc,未入 git;core/ + ui/)
|
||||
esp-emu-peris.html entry page (UART0 console | peripherals rows | Debug — three panes at once)
|
||||
src/ TypeScript sources (npm run build → build/)
|
||||
core/ worker-side framework: version/types/protocol/chips/mmio/sigbus/registry/runtime/worker
|
||||
ui/ page-side modules: app/worker-client/terminal/chart/debug-panels/peris-panel/config-editor
|
||||
build/ compiler output (tsc, not in git; core/ + ui/)
|
||||
peris/
|
||||
devices/ 外设插件(原生 JS,丢文件即用,含各自版本号):led/button/touch-button/toggle-switch
|
||||
vendor/ peris.css xterm 本地化 + 面板样式
|
||||
examples/ 接线配置示例(JSON,可直接 Import)
|
||||
tests/unit/ 单元测试(node:test)
|
||||
tests/m1_smoke.mjs 集成冒烟(真实 wasm+固件全链路)
|
||||
tests/m0_probe.mjs 模拟器能力探针(上游升级时先跑)
|
||||
tests/firmware/ ESP-IDF 测试固件
|
||||
devices/ peripheral plugins (plain JS, drop-in, each with its own version): led/button/touch-button/toggle-switch
|
||||
vendor/ peris.css vendored xterm + panel styles
|
||||
examples/ wiring-config examples (JSON, directly importable)
|
||||
tests/unit/ unit tests (node:test)
|
||||
tests/m1_smoke.mjs integration smoke test (real wasm + firmware, full chain)
|
||||
tests/m0_probe.mjs emulator capability probe (run first on upstream upgrades)
|
||||
tests/firmware/ ESP-IDF test firmware
|
||||
docs/ ARCHITECTURE / EMU-API / FEASIBILITY / DEVICE-API / CHANGELOG / UPGRADE / ROADMAP
|
||||
AGENT.md 环境/架构/坑(贡献前必读)
|
||||
AGENT.md environment/architecture/gotchas (read before contributing)
|
||||
```
|
||||
|
||||
架构、数据流、状态归属与扩展点见 **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**;
|
||||
版本规则见 **[docs/CHANGELOG.md](docs/CHANGELOG.md)**(项目版本三处同步,设备有独立版本)。
|
||||
Architecture, data flow, state ownership and extension points:
|
||||
**[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)**; versioning rules:
|
||||
**[docs/CHANGELOG.md](docs/CHANGELOG.md)** (project version synced in three
|
||||
places; devices carry their own versions).
|
||||
|
||||
## 快速开始
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
# 一次性:npm 依赖(自动用 npmmirror 源装 typescript)
|
||||
# one-time: npm deps (typescript installs from the npmmirror registry)
|
||||
npm install
|
||||
|
||||
# 开发循环:改 src/ → 构建 → 测试 → 起 merged 开发服务器
|
||||
# dev loop: edit src/ → build → test → merged dev server
|
||||
npm run build
|
||||
npm test
|
||||
npm run serve # http://127.0.0.1:8080/esp-emu-peris.html
|
||||
|
||||
# 浏览器打开上面的地址,选择芯片、拖入 merged flash image(.bin)
|
||||
# 注意:必须是 idf.py merge-bin 生成的完整镜像,裸 app bin 无法启动
|
||||
# open the URL above in a browser, pick a chip, drop a merged flash image (.bin)
|
||||
# NOTE: it must be a full image produced by idf.py merge-bin — a bare app bin
|
||||
# will not boot
|
||||
|
||||
# 外设栏(控制台右侧):Add 添加 LED/按键等;每外设一行——行内图标可交互
|
||||
# (LED 点击换色/按键按压),名称、GPIO 方框、V/G 电平徽章点击即改,
|
||||
# ▸ 展开详情与高级配置,⠿ 拖动排序;面板头部:项目名输入框 + 保存/打开图标
|
||||
# (导出/导入接线配置 JSON,自动存 localStorage 并记忆芯片,下次打开自动
|
||||
# 选中)
|
||||
# peripherals pane (right of the console): the + icon adds LEDs/buttons/etc.
|
||||
# Each device is one row — the row icon is interactive (LED click changes
|
||||
# colour / button press), name, GPIO box and V/G pull badge edit on click,
|
||||
# ▸ expands details & advanced config, ⠿ drags to reorder. Panel head:
|
||||
# project-name input + save/open icons (export/import the wiring config JSON,
|
||||
# autosaved to localStorage and remembering the chip, auto-selected next time)
|
||||
```
|
||||
|
||||
把外设面板**发布**进官方 www 目录(只增不改;先 build):
|
||||
**Publish** the peripherals panel into the official www directory (add-only,
|
||||
never modifies; build first):
|
||||
|
||||
```bash
|
||||
npm run sync # 复制入口 html + peris/(含编译产物)+ examples/
|
||||
npm run sync # copies the entry html + peris/ (incl. build output) + examples/
|
||||
```
|
||||
|
||||
## 能力与限制(实测结论,详见 docs/FEASIBILITY.md)
|
||||
## Capabilities & limits (measured; details in docs/FEASIBILITY.md)
|
||||
|
||||
| 外设 | 状态 | 途径 |
|
||||
| Peripheral | Status | Approach |
|
||||
|---|---|---|
|
||||
| 各色 LED | ✅ M1 | 轮询 GPIO 输出寄存器 |
|
||||
| 物理按键 / 触摸按键(GPIO 语义)/ 拨动开关 | ✅ M1 | 驱动引脚输入,固件可读到并触发中断 |
|
||||
| PWM 调光 LED / RGB | 🔜 M2 | 读 LEDC duty 寄存器(引脚级 PWM 不可见) |
|
||||
| WS2812 幻彩灯 | 🔜 M3 | 读 RMT 通道 RAM 解码(引脚级波形不可见) |
|
||||
| 位绑定 I2C/SPI 设备、IR | 🔜 M4 | 高速引脚采样(需固件位绑定) |
|
||||
| 硬件 I2C/SPI 设备、显示屏、ADC、真触摸、I2S、SD | ⏸ M5 | 等 esp-emulator 上游暴露 API |
|
||||
| LEDs, any colour | ✅ M1 | polls the GPIO output registers |
|
||||
| Push button / touch button (GPIO semantics) / toggle switch | ✅ M1 | drives pin input; firmware reads it and gets interrupts |
|
||||
| PWM dimmed LED / RGB | 🔜 M2 | reads LEDC duty registers (pin-level PWM is invisible) |
|
||||
| WS2812 addressable LEDs | 🔜 M3 | decodes from RMT channel RAM (pin-level waveform invisible) |
|
||||
| Bit-banged I2C/SPI devices, IR | 🔜 M4 | high-rate pin sampling (requires bit-banged firmware) |
|
||||
| Hardware I2C/SPI devices, displays, ADC, real touch, I2S, SD | ⏸ M5 | waiting for esp-emulator upstream APIs |
|
||||
|
||||
注:触摸**外设**未被模拟——触摸按键以 GPIO 输入呈现,固件按 GPIO 读取即可
|
||||
(`tests/firmware/peris_probe` 有示例:GPIO3 输入 + 下降沿 ISR)。
|
||||
Note: the touch **peripheral** itself is not emulated — the touch button
|
||||
appears as a GPIO input that firmware reads as plain GPIO
|
||||
(`tests/firmware/peris_probe` has an example: GPIO3 input + falling-edge ISR).
|
||||
|
||||
## 添加一个新外设
|
||||
## Adding a new peripheral
|
||||
|
||||
1. 在 `peris/devices/` 放一个模块,导出 `Device` 与 `View`(接口见
|
||||
`peris/device-api.js` 顶部注释或 docs/DEVICE-API.md);
|
||||
2. 在 `peris/devices/manifest.json` 加一行。完成——页面和 worker 会自动加载。
|
||||
1. Drop a module into `peris/devices/` exporting `Device` and `View`
|
||||
(interface: docs/DEVICE-API.md);
|
||||
2. Add one line to `peris/devices/manifest.json`. Done — page and worker pick
|
||||
it up automatically.
|
||||
|
||||
```js
|
||||
// peris/devices/my-dev.js
|
||||
@@ -88,22 +100,23 @@ export class View {
|
||||
}
|
||||
```
|
||||
|
||||
## 测试固件与测试
|
||||
## Test firmware & tests
|
||||
|
||||
```powershell
|
||||
# 构建固件(用 C:\Users\A\Desktop\quick\idfm.bat 的 IDF 环境)
|
||||
# build the firmware (IDF env from C:\Users\A\Desktop\quick\idfm.bat)
|
||||
powershell -ExecutionPolicy Bypass -NoProfile -File tests\firmware\build.ps1
|
||||
# 生成 merged image(加载用):
|
||||
# merged image for loading:
|
||||
# tests/firmware/peris_probe/build/merged-binary.bin
|
||||
```
|
||||
|
||||
```bash
|
||||
npm test # 单元测试 + 集成冒烟(见 docs/ARCHITECTURE.md 测试分层)
|
||||
npm run test:m0 # 模拟器能力探针(上游 wasm 升级时先跑)
|
||||
npm test # unit + integration smoke (test layering in docs/ARCHITECTURE.md)
|
||||
npm run test:m0 # emulator capability probe (run first on upstream upgrades)
|
||||
```
|
||||
|
||||
固件三阶段循环:GPIO8 闪烁 / GPIO9 LEDC 呼吸 / GPIO4 RMT-WS2812 发帧,
|
||||
并配合 GPIO3 输入中断计数,用于端到端验证。
|
||||
The firmware runs a three-phase loop: GPIO8 blink / GPIO9 LEDC breathing /
|
||||
GPIO4 RMT-WS2812 frames, plus GPIO3 input-interrupt counting, for end-to-end
|
||||
verification.
|
||||
|
||||
注:页面自身不依赖任何 CDN——xterm/xterm-addon-fit 已本地化到
|
||||
`peris/vendor/`(取自 npm 包的非压缩构建),离线可用。
|
||||
Note: the page itself has no CDN dependency — xterm/xterm-addon-fit are
|
||||
vendored into `peris/vendor/` (unminified npm builds), so it works offline.
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
[English](README.md) | **简体中文**
|
||||
|
||||
# esp-emu-peris
|
||||
|
||||
为乐鑫 [esp-emulator](https://github.com/espressif/esp-emulator) 浏览器形态(wasm)做的**模拟外设面板**:LED、按键、触摸按键、拨动开关……以插件方式不断添加,不需要改动官方 `www/` 里的任何基础文件。
|
||||
|
||||
```
|
||||
esp-emu-peris.html 入口页面(UART0 控制台 | 外设行式面板 | Debug,三栏同屏)
|
||||
src/ TypeScript 源码(npm run build → build/)
|
||||
core/ worker 侧框架:version/types/protocol/chips/mmio/sigbus/registry/runtime/worker
|
||||
ui/ 页面侧模块:app/worker-client/terminal/chart/debug-panels/peris-panel/config-editor
|
||||
build/ 编译产物(tsc,未入 git;core/ + ui/)
|
||||
peris/
|
||||
devices/ 外设插件(原生 JS,丢文件即用,含各自版本号):led/button/touch-button/toggle-switch
|
||||
vendor/ peris.css xterm 本地化 + 面板样式
|
||||
examples/ 接线配置示例(JSON,可直接 Import)
|
||||
tests/unit/ 单元测试(node:test)
|
||||
tests/m1_smoke.mjs 集成冒烟(真实 wasm+固件全链路)
|
||||
tests/m0_probe.mjs 模拟器能力探针(上游升级时先跑)
|
||||
tests/firmware/ ESP-IDF 测试固件
|
||||
docs/ ARCHITECTURE / EMU-API / FEASIBILITY / DEVICE-API / CHANGELOG / UPGRADE / ROADMAP
|
||||
AGENT.md 环境/架构/坑(贡献前必读)
|
||||
```
|
||||
|
||||
架构、数据流、状态归属与扩展点见 **[docs/ARCHITECTURE.zh-cn.md](docs/ARCHITECTURE.zh-cn.md)**;
|
||||
版本规则见 **[docs/CHANGELOG.zh-cn.md](docs/CHANGELOG.zh-cn.md)**(项目版本三处同步,设备有独立版本)。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 一次性:npm 依赖(自动用 npmmirror 源装 typescript)
|
||||
npm install
|
||||
|
||||
# 开发循环:改 src/ → 构建 → 测试 → 起 merged 开发服务器
|
||||
npm run build
|
||||
npm test
|
||||
npm run serve # http://127.0.0.1:8080/esp-emu-peris.html
|
||||
|
||||
# 浏览器打开上面的地址,选择芯片、拖入 merged flash image(.bin)
|
||||
# 注意:必须是 idf.py merge-bin 生成的完整镜像,裸 app bin 无法启动
|
||||
|
||||
# 外设栏(控制台右侧):+ 图标添加 LED/按键等;每外设一行——行内图标可交互
|
||||
# (LED 点击换色/按键按压),名称、GPIO 方框、V/G 电平徽章点击即改,
|
||||
# ▸ 展开详情与高级配置,⠿ 拖动排序;面板头部:项目名输入框 + 保存/打开图标
|
||||
# (导出/导入接线配置 JSON,自动存 localStorage 并记忆芯片,下次打开自动
|
||||
# 选中)
|
||||
```
|
||||
|
||||
把外设面板**发布**进官方 www 目录(只增不改;先 build):
|
||||
|
||||
```bash
|
||||
npm run sync # 复制入口 html + peris/(含编译产物)+ examples/
|
||||
```
|
||||
|
||||
## 能力与限制(实测结论,详见 docs/FEASIBILITY.zh-cn.md)
|
||||
|
||||
| 外设 | 状态 | 途径 |
|
||||
|---|---|---|
|
||||
| 各色 LED | ✅ M1 | 轮询 GPIO 输出寄存器 |
|
||||
| 物理按键 / 触摸按键(GPIO 语义)/ 拨动开关 | ✅ M1 | 驱动引脚输入,固件可读到并触发中断 |
|
||||
| PWM 调光 LED / RGB | 🔜 M2 | 读 LEDC duty 寄存器(引脚级 PWM 不可见) |
|
||||
| WS2812 幻彩灯 | 🔜 M3 | 读 RMT 通道 RAM 解码(引脚级波形不可见) |
|
||||
| 位绑定 I2C/SPI 设备、IR | 🔜 M4 | 高速引脚采样(需固件位绑定) |
|
||||
| 硬件 I2C/SPI 设备、显示屏、ADC、真触摸、I2S、SD | ⏸ M5 | 等 esp-emulator 上游暴露 API |
|
||||
|
||||
注:触摸**外设**未被模拟——触摸按键以 GPIO 输入呈现,固件按 GPIO 读取即可
|
||||
(`tests/firmware/peris_probe` 有示例:GPIO3 输入 + 下降沿 ISR)。
|
||||
|
||||
## 添加一个新外设
|
||||
|
||||
1. 在 `peris/devices/` 放一个模块,导出 `Device` 与 `View`(接口见
|
||||
docs/DEVICE-API.zh-cn.md);
|
||||
2. 在 `peris/devices/manifest.json` 加一行。完成——页面和 worker 会自动加载。
|
||||
|
||||
```js
|
||||
// peris/devices/my-dev.js
|
||||
export class Device {
|
||||
static typeId = 'my-dev'; static title = 'My Device'; static category = 'misc';
|
||||
static defaultConfig = { name: 'My', pin: 6 };
|
||||
static configSchema = [{ key: 'pin', label: 'GPIO pin', type: 'pin' }];
|
||||
static pins(cfg) { return [cfg.pin]; }
|
||||
constructor(ctx, cfg) { this.ctx = ctx; ctx.subscribe([cfg.pin]); }
|
||||
onPinState(pin, s) { this.on = s.oe && s.out; this.ctx.markDirty(); }
|
||||
getState() { return { on: this.on }; }
|
||||
destroy() { this.ctx.unsubscribe(); }
|
||||
}
|
||||
export class View {
|
||||
constructor(el, sendCmd) { el.textContent = 'hello'; }
|
||||
render(state) { }
|
||||
}
|
||||
```
|
||||
|
||||
## 测试固件与测试
|
||||
|
||||
```powershell
|
||||
# 构建固件(用 C:\Users\A\Desktop\quick\idfm.bat 的 IDF 环境)
|
||||
powershell -ExecutionPolicy Bypass -NoProfile -File tests\firmware\build.ps1
|
||||
# 生成 merged image(加载用):
|
||||
# tests/firmware/peris_probe/build/merged-binary.bin
|
||||
```
|
||||
|
||||
```bash
|
||||
npm test # 单元测试 + 集成冒烟(见 docs/ARCHITECTURE.zh-cn.md 测试分层)
|
||||
npm run test:m0 # 模拟器能力探针(上游升级时先跑)
|
||||
```
|
||||
|
||||
固件三阶段循环:GPIO8 闪烁 / GPIO9 LEDC 呼吸 / GPIO4 RMT-WS2812 发帧,
|
||||
并配合 GPIO3 输入中断计数,用于端到端验证。
|
||||
|
||||
注:页面自身不依赖任何 CDN——xterm/xterm-addon-fit 已本地化到
|
||||
`peris/vendor/`(取自 npm 包的非压缩构建),离线可用。
|
||||
+107
-77
@@ -1,104 +1,134 @@
|
||||
# esp-emu-peris 系统架构
|
||||
**English** | [简体中文](ARCHITECTURE.zh-cn.md)
|
||||
|
||||
## 1. 分层总览
|
||||
# esp-emu-peris system architecture
|
||||
|
||||
## 1. Layered overview
|
||||
|
||||
```
|
||||
┌───────────────────────────────── 页面(page)─────────────────────────────────┐
|
||||
│ esp-emu-peris.html 上游样式原块(便于升级 diff)+ <link peris/peris.css> │
|
||||
│ 三栏同屏:UART0 控制台 │ 外设面板 │ Debug(0.3.0 起不再标签切换) │
|
||||
│ build/ui/* (TypeScript 编译产物) │
|
||||
│ app.ts 编排器:机器状态、Run/Pause/固件/网络控件、消息路由 │
|
||||
│ worker-client Worker 唯一入口(其它模块一律经由它收发消息) │
|
||||
│ terminal / chart / debug-panels UART 控制台 / 性能图 / Debug 只读面板 │
|
||||
│ peris-panel + config-editor 行式外设列表(图标/名称/GPIO/V·G 徽章 │
|
||||
│ + 展开/删除/拖动)与详情配置编辑器 │
|
||||
┌───────────────────────────────── page ──────────────────────────────────────┐
|
||||
│ esp-emu-peris.html stock style block kept verbatim (easy upgrade diff) │
|
||||
│ + <link peris/peris.css> │
|
||||
│ three panes at once: UART0 console | peripherals | debug (no tabs since 0.3)│
|
||||
│ build/ui/* (TypeScript compiler output) │
|
||||
│ app.ts orchestrator: machine state, Run/Pause/firmware/network │
|
||||
│ controls, message routing │
|
||||
│ worker-client the only Worker entry (all other modules go through it) │
|
||||
│ terminal / chart / debug-panels UART console / perf chart / read-only │
|
||||
│ debug panels │
|
||||
│ peris-panel + config-editor row-based peripherals list │
|
||||
│ (icon/name/GPIO/V·G badge │
|
||||
│ + expand/remove/drag-sort) and the │
|
||||
│ details config editor │
|
||||
└───────────────▲───────────────────────────────────────────────▲────────────┘
|
||||
postMessage(协议见 core/protocol.ts) │ DOM
|
||||
postMessage (protocol in core/protocol.ts) │ DOM
|
||||
┌───────────────┴───────────────────────────────────────────────▼────────────┐
|
||||
│ 页面侧设备 View(peris/devices/*.js 的 View 类,由 peris-panel 实例化) │
|
||||
│ page-side device Views (View classes of peris/devices/*.js, instantiated │
|
||||
│ by peris-panel) │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ← peris_state / peris_command → │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ build/core/worker.ts 上游 worker 复刻 + 6 处 peris: 钩子(唯一可碰上游逻辑) │
|
||||
│ build/core/runtime.ts 设备宿主:配置校验/实例化/命令/状态节流推送 │
|
||||
│ build/core/sigbus.ts 引脚信号总线:每批 gpio_state() 差分分发 │
|
||||
│ build/core/registry.ts 插件注册表(manifest 驱动 + 元数据校验) │
|
||||
│ build/core/{version,types,protocol,chips,mmio}.ts 版本/契约/消息/芯片表/寄存器表 │
|
||||
│ worker 侧设备 Device(peris/devices/*.js 的 Device 类) │
|
||||
└───────────────────────────┬─────────────────────────────────────────────────┘
|
||||
│ WasmEmulator(闭源 wasm,只此一处触碰)
|
||||
esp_emu_bg.wasm v0.51.0
|
||||
│ build/core/worker.ts upstream worker replica + 6 peris: hooks (the only │
|
||||
│ file allowed to touch upstream logic) │
|
||||
│ build/core/runtime.ts device host: config validation/instantiation/commands │
|
||||
│ /throttled state pushes │
|
||||
│ build/core/sigbus.ts pin signal bus: per-batch gpio_state() diff dispatch │
|
||||
│ build/core/registry.ts plugin registry (manifest-driven + meta validation) │
|
||||
│ build/core/{version,types,protocol,chips,mmio}.ts version/contracts/ │
|
||||
│ messages/chip tables/register tables │
|
||||
│ worker-side devices (Device classes of peris/devices/*.js) │
|
||||
└───────────────────────────────────────────────────┬─────────────────────────┘
|
||||
│ WasmEmulator (closed-source
|
||||
│ wasm, touched only here)
|
||||
esp_emu_bg.wasm v0.51.0
|
||||
```
|
||||
|
||||
## 2. 关键决策与理由
|
||||
## 2. Key decisions and why
|
||||
|
||||
| 决策 | 理由 |
|
||||
| decision | rationale |
|
||||
|---|---|
|
||||
| 模拟器只存在于 worker | 页面永不触碰 wasm;所有交互走 postMessage,线程模型不可能被破坏 |
|
||||
| 框架层 TypeScript、设备插件原生 JS | 类型检查覆盖 bug 高发区(本次重构即修掉状态丢弃缺陷);插件保持"丢文件即用",不引入构建依赖 |
|
||||
| tsc 逐文件编译、不打包 | 产物仍是标准 ESM 目录,部署模型不变(www 只增不改);上游同构,升级 diff 容易 |
|
||||
| 样式拆 peris.css,上游样式原块留 HTML | 升级时直接 diff HTML 里的上游段 |
|
||||
| 数据在 TS 常量(chips/mmio)而非 JSON | tsc 原生携带、类型检查、无运行时拷贝问题 |
|
||||
| registry 运行时校验插件元数据 | 插件作者没有编译期检查,缺 `pins()`/`typeId` 在加载时即报错(真实 bug 类) |
|
||||
| emulator lives only in the worker | the page never touches the wasm; all interaction goes through postMessage, so the threading model cannot be broken |
|
||||
| framework in TypeScript, device plugins in plain JS | type-checking covers the bug-prone areas (the big refactor fixed a dropped-state defect this way); plugins stay drop-in with no build dependency |
|
||||
| tsc per-file compilation, no bundling | output stays a standard ESM directory, deployment model unchanged (www add-only); same shape as upstream for easy upgrade diffs |
|
||||
| peris styles split into peris.css, stock styles stay inline in the HTML | upstream sections of the HTML can be diffed directly on upgrade |
|
||||
| data lives in TS constants (chips/mmio), not JSON | carried natively by tsc, type-checked, no runtime copy issues |
|
||||
| registry validates plugin metadata at load time | plugin authors get no compile-time checks; a missing `pins()`/`typeId` fails immediately at load (a real bug class) |
|
||||
|
||||
## 3. 数据流与状态归属
|
||||
## 3. Data flow and state ownership
|
||||
|
||||
- **固件 → 设备**:worker 每批 `run_batch()` 后 `runtime.onBatchEnd()` → `sigbus.sample()`
|
||||
读 `gpio_state()`(4 个 32 位字),按**联合变化掩码**(out|oe)对每个变化引脚分发一次
|
||||
`onPinState`。无变化时成本 = 1 次 wasm 调用 + 4 次 XOR。
|
||||
- **设备 → 页面**:设备 `markDirty()` → `flushStates` 节流 ~30fps;**节流窗口内挂尾随
|
||||
定时器,保证最后一次 dirty 必达**(否则静止在窗口内的最终状态会被丢弃)。
|
||||
- **页面 → 设备**:View `sendCmd()` → `peris_command` → `runtime.command()` → `onCommand`。
|
||||
- **输入引脚电平**:唯一写者是 `ctx.setPinInput`(设备)与 Debug 网格的 `gpio_input`。
|
||||
页面侧 `gpioInputLevels[]` 只是 Debug 网格自己的镜像,不是权威状态;权威状态在设备里
|
||||
(`onEmuReady` 时重驱动)。
|
||||
- **配置**:页面 `PerisPanel.config` 是唯一事实源;每次变更全量 `peris_config` 下发
|
||||
(worker 销毁重建实例)并写 localStorage。引脚冲突由 worker 校验后回报
|
||||
`peris_ready.errors`,页面黄条展示。
|
||||
- **Firmware → device**: after each `run_batch()` the worker calls
|
||||
`runtime.onBatchEnd()` → `sigbus.sample()` reads `gpio_state()` (four 32-bit
|
||||
words) and, using the **union change mask** (out|oe), dispatches one
|
||||
`onPinState` per changed pin. With no changes the cost is 1 wasm call + 4
|
||||
XORs.
|
||||
- **Device → page**: `markDirty()` → `flushStates` throttled to ~30 fps; **a
|
||||
trailing timer is attached inside the throttle window so the last dirty flag
|
||||
is always delivered** (otherwise a final state resting inside the window
|
||||
would be dropped).
|
||||
- **Page → device**: View `sendCmd()` → `peris_command` → `runtime.command()` →
|
||||
`onCommand`.
|
||||
- **Input pin levels**: the only writers are `ctx.setPinInput` (devices) and
|
||||
the Debug grid's `gpio_input`. The page-side `gpioInputLevels[]` is only the
|
||||
Debug grid's own mirror, not authoritative state; the authority lives in the
|
||||
devices (re-driven on `onEmuReady`).
|
||||
- **Config**: the page's `PerisPanel.config` is the single source of truth;
|
||||
every change is sent in full via `peris_config` (the worker destroys and
|
||||
rebuilds instances) and written to localStorage. Pin conflicts are validated
|
||||
by the worker and reported back through `peris_ready.errors`, shown as a
|
||||
yellow bar on the page.
|
||||
|
||||
## 4. 目录职责
|
||||
## 4. Directory responsibilities
|
||||
|
||||
```
|
||||
src/core/ worker 侧框架(TS 源) → 编译到 build/core/
|
||||
src/ui/ 页面侧模块(TS 源) → 编译到 build/ui/
|
||||
build/ tsc 产物(未入 git;HTML/worker/测试都引用这里)
|
||||
peris/devices/ 设备插件(JS 源,不编译;manifest.json 注册;各自版本号)
|
||||
peris/vendor/ xterm 本地化
|
||||
peris/peris.css peris 专属样式
|
||||
tests/unit/ 单元测试(node:test,零依赖)
|
||||
tests/m1_smoke.mjs 集成冒烟(真实 wasm+固件+runtime+设备)
|
||||
tests/m0_probe.mjs 模拟器能力探针(升级上游时先跑它)
|
||||
tests/firmware/ IDF 测试固件
|
||||
src/core/ worker-side framework (TS sources) → compiled to build/core/
|
||||
src/ui/ page-side modules (TS sources) → compiled to build/ui/
|
||||
build/ tsc output (not in git; HTML/worker/tests reference it)
|
||||
peris/devices/ device plugins (JS sources, not compiled; manifest.json
|
||||
registers them; each carries its own version)
|
||||
peris/vendor/ vendored xterm
|
||||
peris/peris.css peris-specific styles
|
||||
tests/unit/ unit tests (node:test, zero dependencies)
|
||||
tests/m1_smoke.mjs integration smoke (real wasm + firmware + runtime + devices)
|
||||
tests/m0_probe.mjs emulator capability probe (run first on upstream upgrades)
|
||||
tests/firmware/ IDF test firmware
|
||||
```
|
||||
|
||||
## 5. 构建与测试
|
||||
## 5. Build & tests
|
||||
|
||||
```bash
|
||||
npm install # 一次性(typescript 走 npmmirror 源)
|
||||
npm run build # tsc → peris/core + peris/ui(改 src 后必须)
|
||||
npm run check # 仅类型检查
|
||||
npm test # 单测 + 集成冒烟
|
||||
npm run serve # python serve.py(合并服务仓库与 www/pkg)
|
||||
npm run sync # 发布进官方 www(先 build)
|
||||
npm install # one-time (typescript from the npmmirror registry)
|
||||
npm run build # tsc → peris/core + peris/ui (mandatory after src changes)
|
||||
npm run check # type-check only
|
||||
npm test # unit + integration smoke
|
||||
npm run serve # python serve.py (merges the service repo and www/pkg)
|
||||
npm run sync # publish into the official www (build first)
|
||||
```
|
||||
|
||||
测试分层:**单元**(纯逻辑:位运算/电平语义/校验/行式徽章推导——快、定位准)→
|
||||
**集成**(真实 wasm+固件全链路)→ **GUI**(手动清单:页面加载、三栏同屏可见、加设备
|
||||
后行式渲染、行内改名/改 GPIO/翻 V·G、行内图标交互(点灯换色/按压/开关)、展开详情
|
||||
与配置编辑、拖动排序、载固件、Run 后 LED 翻转、配置导入导出往返;浏览器自动化待
|
||||
网络环境允许时引入 Playwright,
|
||||
`PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright`)。
|
||||
Test layering: **unit** (pure logic: bit math / level semantics / validation /
|
||||
row-badge derivation — fast, pinpoints failures) → **integration** (real wasm
|
||||
+ firmware, full chain) → **GUI** (manual checklist: page load, three panes
|
||||
visible at once, row rendering after adding a device, inline rename / GPIO
|
||||
change / V·G flip, row-icon interactions (LED colour / press / switch), expand
|
||||
details & config editing, drag sorting, firmware load, LED toggling while
|
||||
running, config import/export round-trip; browser automation to be introduced
|
||||
with Playwright once the network allows —
|
||||
`PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright`).
|
||||
|
||||
## 6. 升级流程(摘要,详见 UPGRADE.md)
|
||||
## 6. Upgrade procedure (summary; details in UPGRADE.md)
|
||||
|
||||
1. 新 wasm 包就位后先跑 `npm run test:m0`,对比 FEASIBILITY.md 结论;
|
||||
2. diff 上游 worker.js ↔ `src/core/worker.ts`(改动均标 `peris:`);
|
||||
3. diff 上游 app.js/index.html ↔ `src/ui/*.ts` 与 HTML 的上游样式段;
|
||||
4. `npm run build && npm test`,GUI 冒测。
|
||||
1. After a new wasm package lands, run `npm run test:m0` first and compare
|
||||
against FEASIBILITY.md;
|
||||
2. diff the upstream worker.js ↔ `src/core/worker.ts` (all changes carry
|
||||
`peris:` markers);
|
||||
3. diff upstream app.js/index.html ↔ `src/ui/*.ts` and the stock style section
|
||||
of the HTML;
|
||||
4. `npm run build && npm test`, manual GUI pass.
|
||||
|
||||
## 7. 扩展点
|
||||
## 7. Extension points
|
||||
|
||||
- **新外设**:`peris/devices/` 放模块 + manifest 一行(见 docs/DEVICE-API.md);Worker/页面
|
||||
自动加载,元数据即时校验。
|
||||
- **新芯片**:`src/core/mmio/<chip>.ts` 加地址表 + 注册到 `mmio.ts`;`chips.ts` 补引脚数。
|
||||
- **新页面面板**:ui/ 加模块,在 app.ts 的消息路由注册——worker 协议不动。
|
||||
- **New peripheral**: drop a module into `peris/devices/` + one manifest line
|
||||
(see docs/DEVICE-API.md); worker and page load it automatically with
|
||||
immediate metadata validation.
|
||||
- **New chip**: add the address table in `src/core/mmio/<chip>.ts` and register
|
||||
it in `mmio.ts`; add the pin count in `chips.ts`.
|
||||
- **New page panel**: add a module in ui/ and register it in app.ts's message
|
||||
routing — the worker protocol stays untouched.
|
||||
@@ -0,0 +1,107 @@
|
||||
[English](ARCHITECTURE.md) | **简体中文**
|
||||
|
||||
# esp-emu-peris 系统架构
|
||||
|
||||
## 1. 分层总览
|
||||
|
||||
```
|
||||
┌───────────────────────────────── 页面(page)─────────────────────────────────┐
|
||||
│ esp-emu-peris.html 上游样式原块(便于升级 diff)+ <link peris/peris.css> │
|
||||
│ 三栏同屏:UART0 控制台 │ 外设面板 │ Debug(0.3.0 起不再标签切换) │
|
||||
│ build/ui/* (TypeScript 编译产物) │
|
||||
│ app.ts 编排器:机器状态、Run/Pause/固件/网络控件、消息路由 │
|
||||
│ worker-client Worker 唯一入口(其它模块一律经由它收发消息) │
|
||||
│ terminal / chart / debug-panels UART 控制台 / 性能图 / Debug 只读面板 │
|
||||
│ peris-panel + config-editor 行式外设列表(图标/名称/GPIO/V·G 徽章 │
|
||||
│ + 展开/删除/拖动)与详情配置编辑器 │
|
||||
└───────────────▲───────────────────────────────────────────────▲────────────┘
|
||||
postMessage(协议见 core/protocol.ts) │ DOM
|
||||
┌───────────────┴───────────────────────────────────────────────▼────────────┐
|
||||
│ 页面侧设备 View(peris/devices/*.js 的 View 类,由 peris-panel 实例化) │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ← peris_state / peris_command → │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ build/core/worker.ts 上游 worker 复刻 + 6 处 peris: 钩子(唯一可碰上游逻辑) │
|
||||
│ build/core/runtime.ts 设备宿主:配置校验/实例化/命令/状态节流推送 │
|
||||
│ build/core/sigbus.ts 引脚信号总线:每批 gpio_state() 差分分发 │
|
||||
│ build/core/registry.ts 插件注册表(manifest 驱动 + 元数据校验) │
|
||||
│ build/core/{version,types,protocol,chips,mmio}.ts 版本/契约/消息/芯片表/寄存器表 │
|
||||
│ worker 侧设备 Device(peris/devices/*.js 的 Device 类) │
|
||||
└───────────────────────────────────────────────────┬─────────────────────────┘
|
||||
│ WasmEmulator(闭源 wasm,
|
||||
│ 只此一处触碰)
|
||||
esp_emu_bg.wasm v0.51.0
|
||||
```
|
||||
|
||||
## 2. 关键决策与理由
|
||||
|
||||
| 决策 | 理由 |
|
||||
|---|---|
|
||||
| 模拟器只存在于 worker | 页面永不触碰 wasm;所有交互走 postMessage,线程模型不可能被破坏 |
|
||||
| 框架层 TypeScript、设备插件原生 JS | 类型检查覆盖 bug 高发区(本次重构即修掉状态丢弃缺陷);插件保持"丢文件即用",不引入构建依赖 |
|
||||
| tsc 逐文件编译、不打包 | 产物仍是标准 ESM 目录,部署模型不变(www 只增不改);上游同构,升级 diff 容易 |
|
||||
| 样式拆 peris.css,上游样式原块留 HTML | 升级时直接 diff HTML 里的上游段 |
|
||||
| 数据在 TS 常量(chips/mmio)而非 JSON | tsc 原生携带、类型检查、无运行时拷贝问题 |
|
||||
| registry 运行时校验插件元数据 | 插件作者没有编译期检查,缺 `pins()`/`typeId` 在加载时即报错(真实 bug 类) |
|
||||
|
||||
## 3. 数据流与状态归属
|
||||
|
||||
- **固件 → 设备**:worker 每批 `run_batch()` 后 `runtime.onBatchEnd()` → `sigbus.sample()`
|
||||
读 `gpio_state()`(4 个 32 位字),按**联合变化掩码**(out|oe)对每个变化引脚分发一次
|
||||
`onPinState`。无变化时成本 = 1 次 wasm 调用 + 4 次 XOR。
|
||||
- **设备 → 页面**:设备 `markDirty()` → `flushStates` 节流 ~30fps;**节流窗口内挂尾随
|
||||
定时器,保证最后一次 dirty 必达**(否则静止在窗口内的最终状态会被丢弃)。
|
||||
- **页面 → 设备**:View `sendCmd()` → `peris_command` → `runtime.command()` → `onCommand`。
|
||||
- **输入引脚电平**:唯一写者是 `ctx.setPinInput`(设备)与 Debug 网格的 `gpio_input`。
|
||||
页面侧 `gpioInputLevels[]` 只是 Debug 网格自己的镜像,不是权威状态;权威状态在设备里
|
||||
(`onEmuReady` 时重驱动)。
|
||||
- **配置**:页面 `PerisPanel.config` 是唯一事实源;每次变更全量 `peris_config` 下发
|
||||
(worker 销毁重建实例)并写 localStorage。引脚冲突由 worker 校验后回报
|
||||
`peris_ready.errors`,页面黄条展示。
|
||||
|
||||
## 4. 目录职责
|
||||
|
||||
```
|
||||
src/core/ worker 侧框架(TS 源) → 编译到 build/core/
|
||||
src/ui/ 页面侧模块(TS 源) → 编译到 build/ui/
|
||||
build/ tsc 产物(未入 git;HTML/worker/测试都引用这里)
|
||||
peris/devices/ 设备插件(JS 源,不编译;manifest.json 注册;各自版本号)
|
||||
peris/vendor/ xterm 本地化
|
||||
peris/peris.css peris 专属样式
|
||||
tests/unit/ 单元测试(node:test,零依赖)
|
||||
tests/m1_smoke.mjs 集成冒烟(真实 wasm+固件+runtime+设备)
|
||||
tests/m0_probe.mjs 模拟器能力探针(升级上游时先跑它)
|
||||
tests/firmware/ IDF 测试固件
|
||||
```
|
||||
|
||||
## 5. 构建与测试
|
||||
|
||||
```bash
|
||||
npm install # 一次性(typescript 走 npmmirror 源)
|
||||
npm run build # tsc → peris/core + peris/ui(改 src 后必须)
|
||||
npm run check # 仅类型检查
|
||||
npm test # 单测 + 集成冒烟
|
||||
npm run serve # python serve.py(合并服务仓库与 www/pkg)
|
||||
npm run sync # 发布进官方 www(先 build)
|
||||
```
|
||||
|
||||
测试分层:**单元**(纯逻辑:位运算/电平语义/校验/行式徽章推导——快、定位准)→ **集成**(真实
|
||||
wasm+固件全链路)→ **GUI**(手动清单:页面加载、三栏同屏可见、加设备
|
||||
后行式渲染、行内改名/改 GPIO/翻 V·G、行内图标交互(点灯换色/按压/开关)、展开详情
|
||||
与配置编辑、拖动排序、载固件、Run 后 LED 翻转、配置导入导出往返;浏览器自动化待
|
||||
网络环境允许时引入 Playwright,
|
||||
`PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright`)。
|
||||
|
||||
## 6. 升级流程(摘要,详见 UPGRADE.zh-cn.md)
|
||||
|
||||
1. 新 wasm 包就位后先跑 `npm run test:m0`,对比 FEASIBILITY.zh-cn.md 结论;
|
||||
2. diff 上游 worker.js ↔ `src/core/worker.ts`(改动均标 `peris:`);
|
||||
3. diff 上游 app.js/index.html ↔ `src/ui/*.ts` 与 HTML 的上游样式段;
|
||||
4. `npm run build && npm test`,GUI 冒测。
|
||||
|
||||
## 7. 扩展点
|
||||
|
||||
- **新外设**:`peris/devices/` 放模块 + manifest 一行(见 docs/DEVICE-API.zh-cn.md);Worker/页面
|
||||
自动加载,元数据即时校验。
|
||||
- **新芯片**:`src/core/mmio/<chip>.ts` 加地址表 + 注册到 `mmio.ts`;`chips.ts` 补引脚数。
|
||||
- **新页面面板**:ui/ 加模块,在 app.ts 的消息路由注册——worker 协议不动。
|
||||
+114
-71
@@ -1,94 +1,137 @@
|
||||
# 变更日志
|
||||
**English** | [简体中文](CHANGELOG.zh-cn.md)
|
||||
|
||||
版本三处同步:`src/core/version.ts`、`package.json`、本文件。
|
||||
规则:主版本=协议/架构破坏性变更;次版本=新能力;修订=修复与小改。
|
||||
# Changelog
|
||||
|
||||
The project version is synced in three places: `src/core/version.ts`,
|
||||
`package.json`, and this file (kept in both languages — CHANGELOG.md /
|
||||
CHANGELOG.zh-cn.md).
|
||||
Rules: major = breaking protocol/architecture change; minor = new capability;
|
||||
patch = fixes and small changes.
|
||||
|
||||
## peris 0.5.1
|
||||
|
||||
- **Bilingual documentation**: every document now has an English version under
|
||||
the default filename and a Chinese version as `*.zh-cn.md` (README, AGENT,
|
||||
docs/*); the two cross-link in their first line for one-click switching, and
|
||||
in-document links stay within the same language. AGENT.md §7 records the
|
||||
keep-both-languages-in-sync rule.
|
||||
|
||||
## peris 0.5.0
|
||||
|
||||
- **恢复缓存时自动选中芯片**:localStorage 保存的配置附带 `chip`,页面加载时
|
||||
自动同步 Target 芯片下拉与机器信息(GPIO 网格、寄存器表、引脚可选项随之
|
||||
正确),不用再手选一次。未知芯片名安全忽略。
|
||||
- **面板头部合并**:项目名(导出文件名)输入框移到 `PERIPHERALS` 标题后,
|
||||
保存/打开改为图标按钮紧跟其后(节约一行版面);工具栏只留 添加下拉+图标
|
||||
Add 按钮。
|
||||
- 实现要点:面板构造提前到 GPIO 默认初始化之前,恢复的芯片才能喂给初始网格;
|
||||
`PerisPanelDeps` 新增 `selectChip(chip)`。
|
||||
- **Chip auto-selected when the cached wiring loads**: the localStorage config
|
||||
now carries `chip`; on page load the Target chip select and machine info are
|
||||
synced automatically (GPIO grid, register table and pin options follow), so
|
||||
the user does not have to pick the chip again. Unknown chip names are
|
||||
safely ignored.
|
||||
- **Panel head merged**: the project-name (export file name) input moved next
|
||||
to the `PERIPHERALS` title, followed by save/open icon buttons (saves one
|
||||
toolbar row); the toolbar now holds only the add select + an icon-only Add
|
||||
button.
|
||||
- Implementation notes: the PerisPanel is constructed before the GPIO
|
||||
defaults are initialised so the restored chip feeds the initial grid;
|
||||
`PerisPanelDeps` gained `selectChip(chip)`.
|
||||
|
||||
## peris 0.4.0
|
||||
|
||||
- **导出文件名可设置**:外设工具栏新增文件名输入框,导出按它命名(自动去非法
|
||||
字符、补单个 .json 后缀);留空回退自动名 `peris-{chip}-{date}.json`。输入
|
||||
记忆于 localStorage(`peris.exportName.v1`)。命名逻辑为纯函数
|
||||
`exportFileName()`(单测覆盖)。
|
||||
- **导出附带固件信息**:导出的 JSON 新增 `firmware {name, size, path?}` 备忘
|
||||
字段(芯片 `chip` 原已有)。受浏览器安全模型限制,磁盘全路径一般拿不到:
|
||||
`path` 仅在宿主环境暴露非标准 `File.path`/`filePath` 属性时填充,常规浏览器
|
||||
只有文件名与大小。导入时忽略该字段,不自动加载固件。
|
||||
- 工具栏改为两行(添加行 + 导入导出行)。
|
||||
- **Configurable export file name**: a file-name input in the peripherals
|
||||
toolbar; exports use it (illegal characters stripped, a single .json suffix
|
||||
appended); empty falls back to the auto name `peris-{chip}-{date}.json`.
|
||||
The input is remembered in localStorage (`peris.exportName.v1`). Naming
|
||||
lives in the pure function `exportFileName()` (unit-tested).
|
||||
- **Firmware provenance in exports**: exported JSON gains an informational
|
||||
`firmware {name, size, path?}` field (the `chip` field already existed).
|
||||
Limited by the browser security model, the full disk path is usually
|
||||
unavailable: `path` is filled only when the host exposes the non-standard
|
||||
`File.path`/`filePath` property; regular browsers get name and size only.
|
||||
Import ignores the field and never auto-loads firmware.
|
||||
- Toolbar split into two rows (add row + import/export row).
|
||||
|
||||
## peris 0.3.1
|
||||
|
||||
- **修复**:LED 行内图标颜色不随配置(与详情灯泡不一致)。根因有二:面板先
|
||||
`setConfig` 后 `rowIcon`(图标元素尚不存在,颜色没应用到它),插件里两处各自
|
||||
设色。现 led 插件统一 `setColour()` 同时应用到灯泡与行图标,面板固定
|
||||
**先 rowIcon 后 setConfig**。
|
||||
- **修复**:加载固件换芯片后,展开详情的 GPIO 选项仍停留在旧芯片(行内 chip
|
||||
一直正确)。引脚选项构建抽取为 `fillPinOptions()` 单一实现(详情编辑器与
|
||||
行内 chip 共用);`PerisPanel.refreshForChip()` 在固件加载/复位重载后检测
|
||||
芯片变化并重建详情编辑器。
|
||||
- **AGENT.md 新增铁律 §3.8**:"同语义代码必须复用"——以上两个 bug 皆为复制
|
||||
代码只改一处所致;同时登记两条 bug 记录(§4)。
|
||||
- `npm test`/`npm run test:unit` 改为显式传 .test.mjs 文件列表(AGENT.md §1
|
||||
记录的 Windows/Git Bash 目录解析坑,目录形式在本机直接 MODULE_NOT_FOUND)。
|
||||
- **Fix**: the LED row-icon colour did not follow the config (disagreed with
|
||||
the details bulb). Two root causes: the panel called `setConfig` before
|
||||
`rowIcon` (the icon element did not exist yet when the colour was applied),
|
||||
and the plugin styled the two visuals separately. The led plugin now has a
|
||||
single `setColour()` applied to both bulb and row icon, and the panel
|
||||
fixed the order: **`rowIcon` first, then `setConfig`**.
|
||||
- **Fix**: after loading firmware for another chip, the details editor's GPIO
|
||||
options stayed on the old chip (the row chip was always correct). Pin-option
|
||||
building was extracted into `fillPinOptions()` as the single implementation
|
||||
(shared by the details editor and the row chip);
|
||||
`PerisPanel.refreshForChip()` detects chip changes after firmware
|
||||
load/reset-reload and rebuilds the details editors.
|
||||
- **AGENT.md gained iron rule §3.8**: "same-semantic code must be reused" —
|
||||
both bugs above came from copying code and changing one copy; two bug
|
||||
records added (§4).
|
||||
- `npm test`/`npm run test:unit` now pass the explicit .test.mjs file list
|
||||
(the Windows/Git Bash directory-resolution pitfall recorded in AGENT.md §1;
|
||||
the directory form fails outright with MODULE_NOT_FOUND on this machine).
|
||||
|
||||
## peris 0.3.0
|
||||
|
||||
- **三栏同屏布局**:外设面板独立成栏,置于 UART 控制台与 Debug 之间,与 Debug
|
||||
同宽(360px,窄屏自适应收窄/堆叠);移除 Peripherals/Debug 标签切换,三栏同时可见。
|
||||
外设面板改为内部滚动(工具栏常驻),Debug 面板加标题条。
|
||||
- **行式外设列表**(类 Photoshop 图层):每外设一行——左侧依次为交互图标、名称
|
||||
(点击原地改名)、GPIO 号方框(点击弹出选单)、V/G 闲置电平徽章(点击翻转,
|
||||
V=上拉到 VCC、G=下拉到 GND);右侧靠齐展开按钮、删除按钮、拖动排序柄。
|
||||
展开后的详情区(类 Blender 属性分组)显示设备控件主体 + 全量配置编辑器
|
||||
(一条属性一行);行内放不下的高级功能都收进详情。
|
||||
- **View API 扩展**(可选项,向后兼容):`View.rowIcon(el)` 定制行内交互图标;
|
||||
View 构造函数新增第三参 `host.updateConfig(patch)`,供 View 请求改配置
|
||||
(如 LED 行内图标点击换色);configSchema 新增 `chip:'pull'` /
|
||||
`pullWhenChecked` 字段声明 V/G 徽章。类型契约见 `build/core/types.d.ts`。
|
||||
- **拖动排序**:拖柄按下才允许拖拽(避免误触);松手后按 DOM 序回写配置并持久化
|
||||
(设备集合不变,不重建 worker 实例)。文件拖放导入与行排序互不干扰(按
|
||||
dataTransfer 类型区分)。
|
||||
- 新增单测:内置插件 rowIcon 存在性、`rowChips()` 引脚/V·G 徽章推导
|
||||
(含 GPIO0、`pullWhenChecked:'low'` 反例)。
|
||||
- **Three-pane layout**: the peripherals pane became its own column between
|
||||
the UART console and Debug, the same width as Debug (360px, narrowing/
|
||||
stacking on small screens); the Peripherals/Debug tab switch was removed —
|
||||
all three panes are visible at once. The peripherals pane scrolls
|
||||
internally (toolbar stays visible); the Debug pane gained a title bar.
|
||||
- **Row-based peripherals list** (photoshop-layers style): each device is one
|
||||
row — on the left the interactive icon, the name (click to rename in
|
||||
place), the GPIO-number box (click for a picker) and the V/G idle-pull
|
||||
badge (click to flip; V = pull-up to VCC, G = pull-down to GND); on the
|
||||
right the expand button, remove button and drag-sort grip. The expanded
|
||||
details (blender-style property groups) show the device widget body + the
|
||||
full config editor (one property per line); advanced features that do not
|
||||
fit the row live there.
|
||||
- **View API extension** (optional, backwards compatible): `View.rowIcon(el)`
|
||||
customises the row icon; the View constructor gained a third argument
|
||||
`host.updateConfig(patch)` so Views can request config changes (e.g. the
|
||||
LED row icon's colour picker); configSchema gained `chip:'pull'` /
|
||||
`pullWhenChecked` to declare the V/G badge. Type contracts in
|
||||
`build/core/types.d.ts`.
|
||||
- **Drag sorting**: dragging is armed only while the grip is held (no
|
||||
accidental drags); on release the DOM order is written back to the config
|
||||
and persisted (device set unchanged → no worker rebuild). File-drop import
|
||||
and row sorting do not interfere (distinguished by dataTransfer type).
|
||||
- New unit tests: rowIcon presence on built-in plugins, `rowChips()` pin/V·G
|
||||
badge derivation (including GPIO0 and a `pullWhenChecked:'low'` case).
|
||||
|
||||
## peris 0.2.0
|
||||
|
||||
- **架构重构**:框架层 TypeScript 化(`src/core` + `src/ui`,tsc 逐文件编译到
|
||||
`build/`,不打包);千行 app.js 拆为 7 个 UI 模块;peris 样式独立 `peris/peris.css`;
|
||||
chips/MMIO/协议常量单一来源;构建产物集中 `build/`(gitignore)。
|
||||
- **测试金字塔**:单元测试(node:test,14 用例)+ 集成冒烟 + M0 探针接入 npm scripts。
|
||||
- **修复**:状态推送节流窗口静默丢弃最后一次变化的缺陷(尾随 flush)。
|
||||
- **版本管理**:项目版本(`version.ts`/`package.json`/本文件)与设备版本
|
||||
(`Device.version`,卡片头展示,registry 校验)。
|
||||
- **文档**:ARCHITECTURE.md(分层/数据流/状态归属)、EMU-API.md(闭源 wasm 能力
|
||||
摘要)、AGENT.md(环境坑与约定)。
|
||||
- xterm 本地化(无 CDN 依赖)。
|
||||
- **Architecture refactor**: framework layer moved to TypeScript
|
||||
(`src/core` + `src/ui`, tsc per-file compilation to `build/`, no bundling);
|
||||
the 1000-line app.js split into 7 UI modules; peris styles extracted into
|
||||
`peris/peris.css`; chips/MMIO/protocol constants single-sourced; build
|
||||
output centralised in `build/` (gitignored).
|
||||
- **Test pyramid**: unit tests (node:test, 14 cases) + integration smoke +
|
||||
M0 probe wired into npm scripts.
|
||||
- **Fix**: silently dropped last state inside the throttle window (trailing
|
||||
flush).
|
||||
- **Versioning**: project version (`version.ts`/`package.json`/CHANGELOG)
|
||||
and device versions (`Device.version`, shown in the row header, validated
|
||||
by the registry).
|
||||
- **Docs**: ARCHITECTURE.md (layering/data flow/state ownership), EMU-API.md
|
||||
(closed-source wasm capability summary), AGENT.md (environment pitfalls and
|
||||
conventions).
|
||||
- xterm vendored locally (no CDN dependency).
|
||||
|
||||
## peris 0.1.0
|
||||
|
||||
- M0 可行性验证(GPIO 轮询/输入中断 ✅;引脚级 PWM/RMT 不可见;LEDC 寄存器与
|
||||
RMT RAM 可读)→ docs/FEASIBILITY.md。
|
||||
- M1 外设框架:入口页(Peripherals/Debug 双标签)、自有 worker(上游复刻+
|
||||
采样钩子)、设备插件 API v1(manifest 注册)、LED/按键/触摸按键/拨动开关、
|
||||
配置导入导出 + localStorage、serve.py/sync_www.py、IDF 测试固件与探针。
|
||||
- M0 feasibility verification (GPIO polling/input interrupts ✅; pin-level
|
||||
PWM/RMT invisible; LEDC registers and RMT RAM readable) → docs/FEASIBILITY.
|
||||
- M1 peripherals framework: entry page (Peripherals/Debug tabs at the time),
|
||||
own worker (upstream replica + sampling hooks), device plugin API v1
|
||||
(manifest registration), LED/button/touch button/toggle switch, config
|
||||
import/export + localStorage, serve.py/sync_www.py, IDF test firmware and
|
||||
probes.
|
||||
|
||||
## 设备插件版本
|
||||
## Device plugin versions
|
||||
|
||||
| typeId | 版本 | 变更 |
|
||||
| typeId | version | change |
|
||||
|---|---|---|
|
||||
| led | 1.1.1 | 修复:行图标颜色与配置不一致——统一 setColour 应用到灯泡+行图标 |
|
||||
| button | 1.1.0 | 行内按压图标(press/release);activeLow 声明为 V/G 徽章 |
|
||||
| touch-button | 1.1.0 | 行内触摸图标(继承 button 的徽章声明) |
|
||||
| toggle-switch | 1.1.0 | 行内迷你拨动开关(点击 toggle);activeLow 声明为 V/G 徽章 |
|
||||
| led | 1.1.1 | fix: row-icon colour disagreed with config — one `setColour` for bulb + row icon |
|
||||
| button | 1.1.0 | row press icon (press/release); activeLow declared as the V/G badge |
|
||||
| touch-button | 1.1.0 | row touch icon (inherits button's badge declaration) |
|
||||
| toggle-switch | 1.1.0 | mini row switch (click to toggle); activeLow declared as the V/G badge |
|
||||
|
||||
历史:led/button/touch-button/toggle-switch 1.0.0 初版(见 peris 0.1.0/0.2.0 条目)。
|
||||
History: led/button/touch-button/toggle-switch 1.0.0 initial versions (see the
|
||||
peris 0.1.0/0.2.0 entries).
|
||||
@@ -0,0 +1,103 @@
|
||||
[English](CHANGELOG.md) | **简体中文**
|
||||
|
||||
# 变更日志
|
||||
|
||||
版本三处同步:`src/core/version.ts`、`package.json`、本文件(双语维护——CHANGELOG.zh-cn.md
|
||||
与英文版 CHANGELOG.md)。
|
||||
规则:主版本=协议/架构破坏性变更;次版本=新能力;修订=修复与小改。
|
||||
|
||||
## peris 0.5.1
|
||||
|
||||
- **文档双语化**:所有文档提供英文版(默认文件名)与中文版(`*.zh-cn.md`)
|
||||
(README、AGENT、docs/*);两版顶部互链一键切换,文内链接保持同语言跳转。
|
||||
AGENT.zh-cn.md §7 记录"两种语言需同步修改"的约定。
|
||||
|
||||
## peris 0.5.0
|
||||
|
||||
- **恢复缓存时自动选中芯片**:localStorage 保存的配置附带 `chip`,页面加载时
|
||||
自动同步 Target 芯片下拉与机器信息(GPIO 网格、寄存器表、引脚可选项随之
|
||||
正确),不用再手选一次。未知芯片名安全忽略。
|
||||
- **面板头部合并**:项目名(导出文件名)输入框移到 `PERIPHERALS` 标题后,
|
||||
保存/打开改为图标按钮紧跟其后(节约一行版面);工具栏只留 添加下拉+图标
|
||||
Add 按钮。
|
||||
- 实现要点:面板构造提前到 GPIO 默认初始化之前,恢复的芯片才能喂给初始网格;
|
||||
`PerisPanelDeps` 新增 `selectChip(chip)`。
|
||||
|
||||
## peris 0.4.0
|
||||
|
||||
- **导出文件名可设置**:外设工具栏新增文件名输入框,导出按它命名(自动去非法
|
||||
字符、补单个 .json 后缀);留空回退自动名 `peris-{chip}-{date}.json`。输入
|
||||
记忆于 localStorage(`peris.exportName.v1`)。命名逻辑为纯函数
|
||||
`exportFileName()`(单测覆盖)。
|
||||
- **导出附带固件信息**:导出的 JSON 新增 `firmware {name, size, path?}` 备忘
|
||||
字段(芯片 `chip` 原已有)。受浏览器安全模型限制,磁盘全路径一般拿不到:
|
||||
`path` 仅在宿主环境暴露非标准 `File.path`/`filePath` 属性时填充,常规浏览器
|
||||
只有文件名与大小。导入时忽略该字段,不自动加载固件。
|
||||
- 工具栏改为两行(添加行 + 导入导出行)。
|
||||
|
||||
## peris 0.3.1
|
||||
|
||||
- **修复**:LED 行内图标颜色不随配置(与详情灯泡不一致)。根因有二:面板先
|
||||
`setConfig` 后 `rowIcon`(图标元素尚不存在,颜色没应用到它),插件里两处各自
|
||||
设色。现 led 插件统一 `setColour()` 同时应用到灯泡与行图标,面板固定
|
||||
**先 rowIcon 后 setConfig**。
|
||||
- **修复**:加载固件换芯片后,展开详情的 GPIO 选项仍停留在旧芯片(行内 chip
|
||||
一直正确)。引脚选项构建抽取为 `fillPinOptions()` 单一实现(详情编辑器与
|
||||
行内 chip 共用);`PerisPanel.refreshForChip()` 在固件加载/复位重载后检测
|
||||
芯片变化并重建详情编辑器。
|
||||
- **AGENT.md 新增铁律 §3.8**:"同语义代码必须复用"——以上两个 bug 皆为复制
|
||||
代码只改一处所致;同时登记两条 bug 记录(§4)。
|
||||
- `npm test`/`npm run test:unit` 改为显式传 .test.mjs 文件列表(AGENT.md §1
|
||||
记录的 Windows/Git Bash 目录解析坑,目录形式在本机直接 MODULE_NOT_FOUND)。
|
||||
|
||||
## peris 0.3.0
|
||||
|
||||
- **三栏同屏布局**:外设面板独立成栏,置于 UART 控制台与 Debug 之间,与 Debug
|
||||
同宽(360px,窄屏自适应收窄/堆叠);移除 Peripherals/Debug 标签切换,三栏同时可见。
|
||||
外设面板改为内部滚动(工具栏常驻),Debug 面板加标题条。
|
||||
- **行式外设列表**(类 Photoshop 图层):每外设一行——左侧依次为交互图标、名称
|
||||
(点击原地改名)、GPIO 号方框(点击弹出选单)、V/G 闲置电平徽章(点击翻转,
|
||||
V=上拉到 VCC、G=下拉到 GND);右侧靠齐展开按钮、删除按钮、拖动排序柄。
|
||||
展开后的详情区(类 Blender 属性分组)显示设备控件主体 + 全量配置编辑器
|
||||
(一条属性一行);行内放不下的高级功能都收进详情。
|
||||
- **View API 扩展**(可选项,向后兼容):`View.rowIcon(el)` 定制行内交互图标;
|
||||
View 构造函数新增第三参 `host.updateConfig(patch)`,供 View 请求改配置
|
||||
(如 LED 行内图标点击换色);configSchema 新增 `chip:'pull'` /
|
||||
`pullWhenChecked` 字段声明 V/G 徽章。类型契约见 `build/core/types.d.ts`。
|
||||
- **拖动排序**:拖柄按下才允许拖拽(避免误触);松手后按 DOM 序回写配置并持久化
|
||||
(设备集合不变,不重建 worker 实例)。文件拖放导入与行排序互不干扰(按
|
||||
dataTransfer 类型区分)。
|
||||
- 新增单测:内置插件 rowIcon 存在性、`rowChips()` 引脚/V·G 徽章推导
|
||||
(含 GPIO0、`pullWhenChecked:'low'` 反例)。
|
||||
|
||||
## peris 0.2.0
|
||||
|
||||
- **架构重构**:框架层 TypeScript 化(`src/core` + `src/ui`,tsc 逐文件编译到
|
||||
`build/`,不打包);千行 app.js 拆为 7 个 UI 模块;peris 样式独立 `peris/peris.css`;
|
||||
chips/MMIO/协议常量单一来源;构建产物集中 `build/`(gitignore)。
|
||||
- **测试金字塔**:单元测试(node:test,14 用例)+ 集成冒烟 + M0 探针接入 npm scripts。
|
||||
- **修复**:状态推送节流窗口静默丢弃最后一次变化的缺陷(尾随 flush)。
|
||||
- **版本管理**:项目版本(`version.ts`/`package.json`/本文件)与设备版本
|
||||
(`Device.version`,卡片头展示,registry 校验)。
|
||||
- **文档**:ARCHITECTURE(分层/数据流/状态归属)、EMU-API(闭源 wasm 能力
|
||||
摘要)、AGENT(环境坑与约定)。
|
||||
- xterm 本地化(无 CDN 依赖)。
|
||||
|
||||
## peris 0.1.0
|
||||
|
||||
- M0 可行性验证(GPIO 轮询/输入中断 ✅;引脚级 PWM/RMT 不可见;LEDC 寄存器与
|
||||
RMT RAM 可读)→ docs/FEASIBILITY。
|
||||
- M1 外设框架:入口页(当时为 Peripherals/Debug 双标签)、自有 worker(上游复刻+
|
||||
采样钩子)、设备插件 API v1(manifest 注册)、LED/按键/触摸按键/拨动开关、
|
||||
配置导入导出 + localStorage、serve.py/sync_www.py、IDF 测试固件与探针。
|
||||
|
||||
## 设备插件版本
|
||||
|
||||
| typeId | 版本 | 变更 |
|
||||
|---|---|---|
|
||||
| led | 1.1.1 | 修复:行图标颜色与配置不一致——统一 setColour 应用到灯泡+行图标 |
|
||||
| button | 1.1.0 | 行内按压图标(press/release);activeLow 声明为 V/G 徽章 |
|
||||
| touch-button | 1.1.0 | 行内触摸图标(继承 button 的徽章声明) |
|
||||
| toggle-switch | 1.1.0 | 行内迷你拨动开关(点击 toggle);activeLow 声明为 V/G 徽章 |
|
||||
|
||||
历史:led/button/touch-button/toggle-switch 1.0.0 初版(见 peris 0.1.0/0.2.0 条目)。
|
||||
+120
-86
@@ -1,93 +1,111 @@
|
||||
# esp-emu-peris 外设接口规范(v1)
|
||||
**English** | [简体中文](DEVICE-API.zh-cn.md)
|
||||
|
||||
一个外设 = `peris/devices/` 下的一个 ES 模块 + `manifest.json` 里的一行。除此之外不需要改动项目任何其它文件。
|
||||
# esp-emu-peris peripheral interface spec (v1)
|
||||
|
||||
> **语言**:插件用**原生 JavaScript**(丢文件即用,无需构建)。框架是 TypeScript 编译
|
||||
> 产物,完整契约类型在 `peris/core/types.d.ts`(`DeviceMeta`/`DeviceLike`/`DeviceCtx`/
|
||||
> `PinState` 等),插件里可通过 JSDoc 引用获得编辑器提示。注册表加载时会**运行时校验**
|
||||
> 元数据(缺 `typeId`/`title`/`pins()`/`defaultConfig` 立即报错),单元测试
|
||||
> `tests/unit/devices.test.mjs` 是插件电气语义的范例。
|
||||
A peripheral = one ES module under `peris/devices/` + one line in
|
||||
`manifest.json`. Nothing else in the project needs to change.
|
||||
|
||||
## 模块导出
|
||||
> **Language**: plugins are **plain JavaScript** (drop-in, no build). The
|
||||
> framework is compiled TypeScript; the full contract types live in
|
||||
> `peris/core/types.d.ts` (`DeviceMeta`/`DeviceLike`/`DeviceCtx`/`PinState`
|
||||
> etc.) and can be referenced from JSDoc for editor hints. The registry
|
||||
> **validates metadata at load time** (a missing `typeId`/`title`/`pins()`/
|
||||
> `defaultConfig` errors immediately); the unit tests in
|
||||
> `tests/unit/devices.test.mjs` are the reference for electrical semantics.
|
||||
|
||||
## Module exports
|
||||
|
||||
```js
|
||||
export class Device { // 运行在 worker,与模拟器交互
|
||||
static typeId = 'led'; // 唯一、稳定、kebab-case
|
||||
static version = '1.0.0'; // 插件自身版本(卡片头展示,登记于 docs/CHANGELOG.md)
|
||||
static title = 'LED'; // UI 显示名
|
||||
static category = 'basic'; // Add 菜单分组
|
||||
static description = '...'; // 一行说明(tooltip)
|
||||
static defaultConfig = {...}; // 新建实例的默认配置
|
||||
static configSchema = [ // 通用配置编辑器字段(展开详情内,一条一行)
|
||||
export class Device { // runs in the worker, talks to the emulator
|
||||
static typeId = 'led'; // unique, stable, kebab-case
|
||||
static version = '1.0.0'; // plugin's own version (row header; logged in docs/CHANGELOG.md)
|
||||
static title = 'LED'; // UI display name
|
||||
static category = 'basic'; // Add-menu grouping
|
||||
static description = '...'; // one-line explanation (tooltip)
|
||||
static defaultConfig = {...}; // defaults for new instances
|
||||
static configSchema = [ // generic config editor fields (in the
|
||||
// expanded details, one per line)
|
||||
{ key: 'pin', label: 'GPIO pin', type: 'pin' },
|
||||
{ key: 'color', label: 'Colour', type: 'color' },
|
||||
{ key: 'name', label: 'Label', type: 'text' },
|
||||
// type 还支持: number(min/max/step)、select(options:[{value,label}])、checkbox
|
||||
// checkbox 可加 chip:'pull' + pullWhenChecked:'high'|'low'
|
||||
// → 设备行上显示 V/G 闲置电平徽章(点击翻转),详见下文"行式 UI"
|
||||
// also supported: number(min/max/step), select(options:[{value,label}]), checkbox
|
||||
// a checkbox may add chip:'pull' + pullWhenChecked:'high'|'low'
|
||||
// → the device row shows a V/G idle-pull badge (click to flip),
|
||||
// see "Row UI conventions" below
|
||||
];
|
||||
static pins(cfg) { return [cfg.pin]; } // 声明占用的 GPIO(用于冲突检测)
|
||||
static pins(cfg) { return [cfg.pin]; } // claimed GPIOs (conflict detection)
|
||||
|
||||
constructor(ctx, cfg) {} // cfg = { id, type, ...设备配置 }
|
||||
onPinState(pin, s) {} // 引脚输出变化回调 s={out, oe, cycles}
|
||||
onCommand(cmd, params) {} // 页面 View 发来的用户操作
|
||||
onEmuReady() {} // 模拟器(重)加载后:重新驱动输入电平
|
||||
getState() { return {...}; } // 可序列化状态,推给页面 View(~30fps)
|
||||
destroy() {} // 释放/恢复引脚
|
||||
constructor(ctx, cfg) {} // cfg = { id, type, ...device settings }
|
||||
onPinState(pin, s) {} // pin-output change callback s={out, oe, cycles}
|
||||
onCommand(cmd, params) {} // user action sent from the page View
|
||||
onEmuReady() {} // emulator (re)loaded: re-drive input levels
|
||||
getState() { return {...}; } // serialisable state pushed to the View (~30fps)
|
||||
destroy() {} // release / restore pins
|
||||
}
|
||||
|
||||
export class View { // 运行在页面,渲染设备视图
|
||||
constructor(el, sendCmd, host) {} // el=详情区主体容器; sendCmd(cmd, params);
|
||||
// host.updateConfig(patch) 请求改本设备配置
|
||||
rowIcon(el) {} // 可选:行内交互图标(el≈24px 容器),见下文
|
||||
render(state) {} // state 来自 Device.getState();行图标与详情主体一起驱动
|
||||
setConfig(cfg) {} // 可选:配置编辑后回调(如换 LED 颜色)
|
||||
export class View { // runs on the page, renders the device view
|
||||
constructor(el, sendCmd, host) {} // el = details body container;
|
||||
// sendCmd(cmd, params);
|
||||
// host.updateConfig(patch) requests a config change
|
||||
rowIcon(el) {} // optional: compact interactive row icon (~24px), see below
|
||||
render(state) {} // state comes from Device.getState(); drives icon and body together
|
||||
setConfig(cfg) {} // optional: called after config edits (e.g. LED colour)
|
||||
}
|
||||
```
|
||||
|
||||
## 行式 UI 约定(0.3.0+)
|
||||
## Row UI conventions (0.3.0+)
|
||||
|
||||
外设面板是三栏布局中的独立一栏(控制台 | 外设 | Debug),每种外设占一行:
|
||||
The peripherals pane is its own column in the three-pane layout
|
||||
(console | peripherals | debug); each device occupies one row:
|
||||
|
||||
```
|
||||
[交互图标] [名称] [GPIO号] [V/G] [▸展开] [×删除] [⠿拖动]
|
||||
[icon] [name] [GPIO#] [V/G] [▸ expand] [× remove] [⠿ drag]
|
||||
```
|
||||
|
||||
- **交互图标**(`View.rowIcon(el)`):可点击的迷你控件,操作语义由插件定义——
|
||||
LED 点击换色(取色器,经 `host.updateConfig({color})` 写回配置)、按键按下/
|
||||
松开(连发 press/release)、拨动开关点击 toggle。`render(state)` 里同步刷新
|
||||
图标状态(亮灭/按下/ON)。不实现 rowIcon 时显示首字母占位块。
|
||||
**调用顺序**:面板固定先 `rowIcon` 后 `setConfig`,配置式样可覆盖到图标;
|
||||
图标与详情主体是同一状态的两个视觉,插件必须共用同一 setter/render
|
||||
(参考 led.js 的 `setColour()`),禁止为图标单写一份样式逻辑。
|
||||
- **名称 / GPIO 方框 / V·G 徽章**:由面板通用渲染,点击原地修改
|
||||
(名称与 GPIO 走内联编辑,V/G 徽章直接翻转)。GPIO 方框取 schema 第一个
|
||||
`type:'pin'` 字段;V/G 徽章取标记 `chip:'pull'` 的 checkbox——勾选时若
|
||||
`pullWhenChecked:'high'`(默认)显示 V(上拉到 VCC),否则 G;未勾选相反。
|
||||
- **展开详情**:设备控件主体(`el`,即构造函数第一个参数)+ 全量配置编辑器
|
||||
(一条属性一行)。行内放不下的高级功能一律放详情。
|
||||
- **拖动排序** ⠿:按下拖柄才启用拖拽,松手回写 devices 顺序(不重建 worker)。
|
||||
- **Interactive icon** (`View.rowIcon(el)`): a clickable mini widget whose
|
||||
semantics the plugin defines — LED opens a colour picker on click (writes
|
||||
back via `host.updateConfig({color})`), buttons send press/release while
|
||||
held, the toggle switch flips on click. `render(state)` refreshes the icon
|
||||
state (lit / pressed / ON) as well. Without rowIcon a placeholder letter
|
||||
chip is shown.
|
||||
**Call order**: the panel always calls `rowIcon` before `setConfig`, so
|
||||
config-driven styling covers the icon; the icon and the details body are two
|
||||
visuals of the same state — plugins must share one setter/render
|
||||
(see led.js `setColour()`), never a separate styling path for the icon.
|
||||
- **Name / GPIO box / V·G badge**: rendered generically by the panel, edited
|
||||
in place on click (name and GPIO via inline editors, the V/G badge flips
|
||||
directly). The GPIO box maps to the schema's first `type:'pin'` field; the
|
||||
V/G badge maps to the checkbox marked `chip:'pull'` — when checked it shows
|
||||
V (pull-up to VCC) for `pullWhenChecked:'high'` (the default), otherwise G;
|
||||
unchecked is the opposite.
|
||||
- **Expanded details**: the device widget body (`el`, the constructor's first
|
||||
argument) + the full config editor (one property per line). Advanced
|
||||
features that do not fit in the row live here.
|
||||
- **Drag sorting** ⠿: dragging is armed only while the grip is held; on
|
||||
release the devices order is written back (no worker rebuild).
|
||||
|
||||
## ctx(worker 侧设备上下文)
|
||||
## ctx (worker-side device context)
|
||||
|
||||
| 成员 | 说明 |
|
||||
| member | description |
|
||||
|---|---|
|
||||
| `ctx.id` / `ctx.chip` / `ctx.gpioCount` | 设备 id、芯片型号、GPIO 数 |
|
||||
| `ctx.setPinInput(pin, high)` | 驱动引脚输入电平(固件可读到、可触发 GPIO 中断) |
|
||||
| `ctx.subscribe(pins)` / `ctx.unsubscribe()` | 订阅引脚输出变化(onPinState) |
|
||||
| `ctx.readMem(addr, len)` / `ctx.readU32(addr)` | 总线/MMIO 读取(LEDC duty、RMT RAM 等) |
|
||||
| `ctx.cycles()` | 模拟器周期计数 |
|
||||
| `ctx.markDirty()` | 请求尽快把 getState() 推给页面 |
|
||||
| `ctx.log(text, level?)` | 在 UART 控制台打一条 `[Peris]` 日志 |
|
||||
| `ctx.id` / `ctx.chip` / `ctx.gpioCount` | device id, chip model, GPIO count |
|
||||
| `ctx.setPinInput(pin, high)` | drive a pin's input level (firmware reads it; raises GPIO interrupts) |
|
||||
| `ctx.subscribe(pins)` / `ctx.unsubscribe()` | subscribe to pin-output changes (onPinState) |
|
||||
| `ctx.readMem(addr, len)` / `ctx.readU32(addr)` | bus/MMIO reads (LEDC duty, RMT RAM, …) |
|
||||
| `ctx.cycles()` | emulator cycle counter |
|
||||
| `ctx.markDirty()` | request getState() to be pushed to the page soon |
|
||||
| `ctx.log(text, level?)` | print a `[Peris]` line on the UART console |
|
||||
|
||||
## 消息协议(页面 ↔ worker)
|
||||
## Message protocol (page ↔ worker)
|
||||
|
||||
在原版 worker 协议之外增加(见 `peris/protocol.js`):
|
||||
Added on top of the stock worker protocol (see `peris/protocol.js`):
|
||||
|
||||
- 页面→worker:`peris_config {config}`(全量应用配置)、`peris_command {id, cmd, params}`
|
||||
- worker→页面:`peris_ready {ok, errors, devices}`、`peris_state {id, state}`、`peris_log {level, text}`
|
||||
- page→worker: `peris_config {config}` (apply a config in full),
|
||||
`peris_command {id, cmd, params}`
|
||||
- worker→page: `peris_ready {ok, errors, devices}`, `peris_state {id, state}`,
|
||||
`peris_log {level, text}`
|
||||
|
||||
## 配置文件
|
||||
## Config files
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -98,30 +116,46 @@ export class View { // 运行在页面,渲染设备视图
|
||||
}
|
||||
```
|
||||
|
||||
- 自动保存于 localStorage(`peris.config.v1`,含 `chip`,页面加载时自动选中
|
||||
对应芯片),面板头部的保存/打开图标导出/导入 `.json`。
|
||||
- **导出文件名**:面板头部输入框设置(即项目名;留空 = 自动名
|
||||
`peris-{chip}-{date}.json`),记忆于 localStorage;命名逻辑见
|
||||
`peris-panel.ts exportFileName()`。
|
||||
- **`firmware` 为备忘字段**:导出时记录最近加载固件的文件名/大小(/磁盘路径,
|
||||
仅当宿主环境暴露非标准 `File.path` 时存在——浏览器安全模型不提供全路径)。
|
||||
导入时忽略,不会自动加载固件;`chip` 在 localStorage 自动保存/恢复时会
|
||||
选中对应芯片,Import 导入的 JSON 中则为备忘(不改当前选择)。
|
||||
- v1 规则:每个 GPIO 同时只能被一个设备占用(任意方向);冲突会在面板顶部黄条提示。
|
||||
- Autosaved to localStorage (`peris.config.v1`, including `chip`, which is
|
||||
auto-selected when the page loads); the save/open icons in the panel head
|
||||
export/import `.json`.
|
||||
- **Export file name**: set in the panel-head input (it doubles as the project
|
||||
name; empty = auto name `peris-{chip}-{date}.json`), remembered in
|
||||
localStorage; naming logic in `peris-panel.ts exportFileName()`.
|
||||
- **`firmware` is an informational field**: records the name/size(/disk path)
|
||||
of the most recently loaded firmware at export time (the path exists only
|
||||
when the host exposes the non-standard `File.path` — the browser security
|
||||
model provides no full path). Import ignores it and never auto-loads
|
||||
firmware; `chip` selects the matching chip when the localStorage config is
|
||||
saved/restored, but in an imported JSON it is informational only (it does
|
||||
not change the current selection).
|
||||
- v1 rule: each GPIO can be claimed by at most one device (either direction);
|
||||
conflicts surface in the yellow bar at the top of the pane.
|
||||
|
||||
## 设备开发注意事项(源自 M0 实测,见 FEASIBILITY.md)
|
||||
## Device development notes (from M0 measurements; see FEASIBILITY.md)
|
||||
|
||||
1. **引脚输出**:固件主动 GPIO 输出在 `onPinState` 可见(out+oe);**LEDC/RMT 走 GPIO matrix 的信号不出现在引脚寄存器**,必须读 MMIO(LEDC duty 寄存器、RMT 通道 RAM),地址表在 `peris/mmio/`。
|
||||
2. **引脚输入**:`ctx.setPinInput` 会真实进入固件(读值+中断),按键类设备在 `onEmuReady` 里要重新驱动电平(模拟器重载后电平复位)。
|
||||
3. **时间**:`vTaskDelay` 期间模拟器时间快进,不要试图在 CPU 睡眠窗口做引脚级波形捕获;CPU 活跃的位绑定波形可在 run_batch 批间采样,但请节制(细批约 99 MIPS,粗批 5000+ MIPS)。
|
||||
4. 状态推送节流 ~30fps;`getState()` 返回的对象会 postMessage,保持小而可序列化。
|
||||
5. View 中触发**设备动作**统一走 `sendCmd(cmd, params)`;**修改配置**(如行内换色)
|
||||
走 `host.updateConfig(patch)`——配置的唯一事实源在页面 PerisPanel,worker 侧
|
||||
不得缓存/改配置。不要在页面侧直接操作模拟器。
|
||||
1. **Pin outputs**: firmware GPIO writes are visible in `onPinState`
|
||||
(out+oe); **signals routed through the GPIO matrix (LEDC/RMT) never appear
|
||||
in the pin registers** — you must read MMIO (LEDC duty registers, RMT
|
||||
channel RAM); address tables are in `peris/mmio/`.
|
||||
2. **Pin inputs**: `ctx.setPinInput` genuinely reaches the firmware (reads +
|
||||
interrupts); button-type devices must re-drive their level in
|
||||
`onEmuReady` (levels reset when the emulator reloads).
|
||||
3. **Time**: emulator time fast-forwards during `vTaskDelay`; do not attempt
|
||||
pin-level waveform capture inside CPU sleep windows. Bit-banged waveforms
|
||||
while the CPU is active can be sampled between run_batch calls, but keep
|
||||
it modest (~99 MIPS with fine batches, 5000+ MIPS with coarse ones).
|
||||
4. State pushes are throttled to ~30fps; the object returned by `getState()`
|
||||
is postMessaged — keep it small and serialisable.
|
||||
5. **Device actions** from the View always go through `sendCmd(cmd, params)`;
|
||||
**config changes** (e.g. picking a colour from the row icon) go through
|
||||
`host.updateConfig(patch)` — the single source of truth for config is the
|
||||
page's PerisPanel; the worker side must never cache/mutate it. Never touch
|
||||
the emulator from the page side.
|
||||
|
||||
## 示例
|
||||
## Examples
|
||||
|
||||
- `peris/devices/led.js` — 输出型设备(sigbus 订阅)
|
||||
- `peris/devices/button.js` — 输入型设备(setPinInput + onEmuReady)
|
||||
- `peris/devices/touch-button.js` — 继承 button 的 Device,换 View
|
||||
- `peris/devices/toggle-switch.js` — 保持型输入设备
|
||||
- `peris/devices/led.js` — output device (sigbus subscription)
|
||||
- `peris/devices/button.js` — input device (setPinInput + onEmuReady)
|
||||
- `peris/devices/touch-button.js` — inherits button's Device, different View
|
||||
- `peris/devices/toggle-switch.js` — latching input device
|
||||
@@ -0,0 +1,129 @@
|
||||
[English](DEVICE-API.md) | **简体中文**
|
||||
|
||||
# esp-emu-peris 外设接口规范(v1)
|
||||
|
||||
一个外设 = `peris/devices/` 下的一个 ES 模块 + `manifest.json` 里的一行。除此之外不需要改动项目任何其它文件。
|
||||
|
||||
> **语言**:插件用**原生 JavaScript**(丢文件即用,无需构建)。框架是 TypeScript 编译
|
||||
> 产物,完整契约类型在 `peris/core/types.d.ts`(`DeviceMeta`/`DeviceLike`/`DeviceCtx`/
|
||||
> `PinState` 等),插件里可通过 JSDoc 引用获得编辑器提示。注册表加载时会**运行时校验**
|
||||
> 元数据(缺 `typeId`/`title`/`pins()`/`defaultConfig` 立即报错),单元测试
|
||||
> `tests/unit/devices.test.mjs` 是插件电气语义的范例。
|
||||
|
||||
## 模块导出
|
||||
|
||||
```js
|
||||
export class Device { // 运行在 worker,与模拟器交互
|
||||
static typeId = 'led'; // 唯一、稳定、kebab-case
|
||||
static version = '1.0.0'; // 插件自身版本(行头展示,登记于 docs/CHANGELOG.zh-cn.md)
|
||||
static title = 'LED'; // UI 显示名
|
||||
static category = 'basic'; // Add 菜单分组
|
||||
static description = '...'; // 一行说明(tooltip)
|
||||
static defaultConfig = {...}; // 新建实例的默认配置
|
||||
static configSchema = [ // 通用配置编辑器字段(展开详情内,一条一行)
|
||||
{ key: 'pin', label: 'GPIO pin', type: 'pin' },
|
||||
{ key: 'color', label: 'Colour', type: 'color' },
|
||||
{ key: 'name', label: 'Label', type: 'text' },
|
||||
// type 还支持: number(min/max/step)、select(options:[{value,label}])、checkbox
|
||||
// checkbox 可加 chip:'pull' + pullWhenChecked:'high'|'low'
|
||||
// → 设备行上显示 V/G 闲置电平徽章(点击翻转),详见下文"行式 UI 约定"
|
||||
];
|
||||
static pins(cfg) { return [cfg.pin]; } // 声明占用的 GPIO(用于冲突检测)
|
||||
|
||||
constructor(ctx, cfg) {} // cfg = { id, type, ...设备配置 }
|
||||
onPinState(pin, s) {} // 引脚输出变化回调 s={out, oe, cycles}
|
||||
onCommand(cmd, params) {} // 页面 View 发来的用户操作
|
||||
onEmuReady() {} // 模拟器(重)加载后:重新驱动输入电平
|
||||
getState() { return {...}; } // 可序列化状态,推给页面 View(~30fps)
|
||||
destroy() {} // 释放/恢复引脚
|
||||
}
|
||||
|
||||
export class View { // 运行在页面,渲染设备视图
|
||||
constructor(el, sendCmd, host) {} // el=详情区主体容器; sendCmd(cmd, params);
|
||||
// host.updateConfig(patch) 请求改本设备配置
|
||||
rowIcon(el) {} // 可选:行内交互图标(el≈24px 容器),见下文
|
||||
render(state) {} // state 来自 Device.getState();行图标与详情主体一起驱动
|
||||
setConfig(cfg) {} // 可选:配置编辑后回调(如换 LED 颜色)
|
||||
}
|
||||
```
|
||||
|
||||
## 行式 UI 约定(0.3.0+)
|
||||
|
||||
外设面板是三栏布局中的独立一栏(控制台 | 外设 | Debug),每种外设占一行:
|
||||
|
||||
```
|
||||
[交互图标] [名称] [GPIO号] [V/G] [▸展开] [×删除] [⠿拖动]
|
||||
```
|
||||
|
||||
- **交互图标**(`View.rowIcon(el)`):可点击的迷你控件,操作语义由插件定义——
|
||||
LED 点击换色(取色器,经 `host.updateConfig({color})` 写回配置)、按键按下/
|
||||
松开(连发 press/release)、拨动开关点击 toggle。`render(state)` 里同步刷新
|
||||
图标状态(亮灭/按下/ON)。不实现 rowIcon 时显示首字母占位块。
|
||||
**调用顺序**:面板固定先 `rowIcon` 后 `setConfig`,配置式样可覆盖到图标;
|
||||
图标与详情主体是同一状态的两个视觉,插件必须共用同一 setter/render
|
||||
(参考 led.js 的 `setColour()`),禁止为图标单写一份样式逻辑。
|
||||
- **名称 / GPIO 方框 / V·G 徽章**:由面板通用渲染,点击原地修改
|
||||
(名称与 GPIO 走内联编辑,V/G 徽章直接翻转)。GPIO 方框取 schema 第一个
|
||||
`type:'pin'` 字段;V/G 徽章取标记 `chip:'pull'` 的 checkbox——勾选时若
|
||||
`pullWhenChecked:'high'`(默认)显示 V(上拉到 VCC),否则 G;未勾选相反。
|
||||
- **展开详情**:设备控件主体(`el`,即构造函数第一个参数)+ 全量配置编辑器
|
||||
(一条属性一行)。行内放不下的高级功能一律放详情。
|
||||
- **拖动排序** ⠿:按下拖柄才启用拖拽,松手回写 devices 顺序(不重建 worker)。
|
||||
|
||||
## ctx(worker 侧设备上下文)
|
||||
|
||||
| 成员 | 说明 |
|
||||
|---|---|
|
||||
| `ctx.id` / `ctx.chip` / `ctx.gpioCount` | 设备 id、芯片型号、GPIO 数 |
|
||||
| `ctx.setPinInput(pin, high)` | 驱动引脚输入电平(固件可读到、可触发 GPIO 中断) |
|
||||
| `ctx.subscribe(pins)` / `ctx.unsubscribe()` | 订阅引脚输出变化(onPinState) |
|
||||
| `ctx.readMem(addr, len)` / `ctx.readU32(addr)` | 总线/MMIO 读取(LEDC duty、RMT RAM 等) |
|
||||
| `ctx.cycles()` | 模拟器周期计数 |
|
||||
| `ctx.markDirty()` | 请求尽快把 getState() 推给页面 |
|
||||
| `ctx.log(text, level?)` | 在 UART 控制台打一条 `[Peris]` 日志 |
|
||||
|
||||
## 消息协议(页面 ↔ worker)
|
||||
|
||||
在原版 worker 协议之外增加(见 `peris/protocol.js`):
|
||||
|
||||
- 页面→worker:`peris_config {config}`(全量应用配置)、`peris_command {id, cmd, params}`
|
||||
- worker→页面:`peris_ready {ok, errors, devices}`、`peris_state {id, state}`、`peris_log {level, text}`
|
||||
|
||||
## 配置文件
|
||||
|
||||
```json
|
||||
{
|
||||
"perisVersion": 1,
|
||||
"chip": "esp32c3",
|
||||
"firmware": { "name": "merged-binary.bin", "size": 250048 },
|
||||
"devices": [ { "id": "led-1", "type": "led", "pin": 8, "color": "#ff4d4d", "name": "Red" } ]
|
||||
}
|
||||
```
|
||||
|
||||
- 自动保存于 localStorage(`peris.config.v1`,含 `chip`,页面加载时自动选中
|
||||
对应芯片);面板头部的保存/打开图标导出/导入 `.json`。
|
||||
- **导出文件名**:面板头部输入框设置(即项目名;留空 = 自动名
|
||||
`peris-{chip}-{date}.json`),记忆于 localStorage;命名逻辑见
|
||||
`peris-panel.ts exportFileName()`。
|
||||
- **`firmware` 为备忘字段**:导出时记录最近加载固件的文件名/大小(/磁盘路径,
|
||||
仅当宿主环境暴露非标准 `File.path` 时存在——浏览器安全模型不提供全路径)。
|
||||
导入时忽略,不会自动加载固件;`chip` 在 localStorage 自动保存/恢复时会
|
||||
选中对应芯片,Import 导入的 JSON 中则为备忘(不改当前选择)。
|
||||
- v1 规则:每个 GPIO 同时只能被一个设备占用(任意方向);冲突会在面板顶部黄条提示。
|
||||
|
||||
## 设备开发注意事项(源自 M0 实测,见 FEASIBILITY.zh-cn.md)
|
||||
|
||||
1. **引脚输出**:固件主动 GPIO 输出在 `onPinState` 可见(out+oe);**LEDC/RMT 走 GPIO matrix 的信号不出现在引脚寄存器**,必须读 MMIO(LEDC duty 寄存器、RMT 通道 RAM),地址表在 `peris/mmio/`。
|
||||
2. **引脚输入**:`ctx.setPinInput` 会真实进入固件(读值+中断),按键类设备在 `onEmuReady` 里要重新驱动电平(模拟器重载后电平复位)。
|
||||
3. **时间**:`vTaskDelay` 期间模拟器时间快进,不要试图在 CPU 睡眠窗口做引脚级波形捕获;CPU 活跃的位绑定波形可在 run_batch 批间采样,但请节制(细批约 99 MIPS,粗批 5000+ MIPS)。
|
||||
4. 状态推送节流 ~30fps;`getState()` 返回的对象会 postMessage,保持小而可序列化。
|
||||
5. View 中触发**设备动作**统一走 `sendCmd(cmd, params)`;**修改配置**(如行内换色)
|
||||
走 `host.updateConfig(patch)`——配置的唯一事实源在页面 PerisPanel,worker 侧
|
||||
不得缓存/改配置。不要在页面侧直接操作模拟器。
|
||||
|
||||
## 示例
|
||||
|
||||
- `peris/devices/led.js` — 输出型设备(sigbus 订阅)
|
||||
- `peris/devices/button.js` — 输入型设备(setPinInput + onEmuReady)
|
||||
- `peris/devices/touch-button.js` — 继承 button 的 Device,换 View
|
||||
- `peris/devices/toggle-switch.js` — 保持型输入设备
|
||||
+63
-50
@@ -1,75 +1,88 @@
|
||||
# esp-emulator 浏览器 API 摘要(wasm v0.51.0)
|
||||
**English** | [简体中文](EMU-API.zh-cn.md)
|
||||
|
||||
本文档是对 **闭源** `esp_emu_bg.wasm` 对 JS 暴露的全部能力的逆向整理,来源:官方
|
||||
`www/pkg/esp_emu.js`(wasm-bindgen 胶水)、官方文档(esp-emulator README/BROWSER.md)
|
||||
与本项目的 M0 实测(`tests/m0_probe.mjs`,结论详见 [FEASIBILITY.md](FEASIBILITY.md))。
|
||||
上游升级后请重跑探针核对本文。
|
||||
# esp-emulator browser API summary (wasm v0.51.0)
|
||||
|
||||
## 1. 加载与初始化
|
||||
A reverse-engineered inventory of everything the **closed-source**
|
||||
`esp_emu_bg.wasm` exposes to JS. Sources: the official `www/pkg/esp_emu.js`
|
||||
(wasm-bindgen glue), official docs (esp-emulator README/BROWSER.md), and this
|
||||
project's M0 measurements (`tests/m0_probe.mjs`, conclusions in
|
||||
[FEASIBILITY.md](FEASIBILITY.md)). Re-run the probe and re-check this document
|
||||
after an upstream upgrade.
|
||||
|
||||
## 1. Loading & initialisation
|
||||
|
||||
```js
|
||||
import init, { WasmEmulator, initSync } from './pkg/esp_emu.js';
|
||||
await init(); // fetch+instantiate esp_emu_bg.wasm(相对 esp_emu.js)
|
||||
// 或 Node 环境:initSync(fs.readFileSync('esp_emu_bg.wasm')) // 同步初始化
|
||||
await init(); // fetch+instantiate esp_emu_bg.wasm (relative to esp_emu.js)
|
||||
// or in Node: initSync(fs.readFileSync('esp_emu_bg.wasm')) // synchronous init
|
||||
const emu = new WasmEmulator('esp32c3'); // 'esp32c3'|'esp32c5'|'esp32c6'|'esp32h2'|'esp32p4'|'esp32s3'|'esp32s31'
|
||||
```
|
||||
|
||||
## 2. 方法一览(全部同步;**无任何回调,只能轮询**)
|
||||
## 2. Method inventory (all synchronous; **no callbacks — polling only**)
|
||||
|
||||
### 生命周期 / 加载
|
||||
| 方法 | 说明 |
|
||||
### Lifecycle / loading
|
||||
| method | notes |
|
||||
|---|---|
|
||||
| `load_firmware(Uint8Array)` | **必须是 merged flash image**(bootloader@0x0+分区表@0x8000+app@0x10000,`idf.py merge-bin` 生成);裸 app bin 无法启动 |
|
||||
| `load_rom_elf(Uint8Array)` / `load_default_rom()` / `has_default_rom()` | ROM;缺省用内嵌版本 |
|
||||
| `load_efuse(Uint8Array)` / `load_app_elf(Uint8Array)` | eFuse / app ELF(BLE 符号拦截用) |
|
||||
| `set_boot_from_rom(bool)` | 在 `load_firmware` 前调用 |
|
||||
| `restart()` / `needs_restart()` | 软复位(保留 flash)/ 固件调用了 esp_restart |
|
||||
| `load_firmware(Uint8Array)` | **must be a merged flash image** (bootloader@0x0 + partition table@0x8000 + app@0x10000, from `idf.py merge-bin`); a bare app bin never boots |
|
||||
| `load_rom_elf(Uint8Array)` / `load_default_rom()` / `has_default_rom()` | ROM; embedded version used by default |
|
||||
| `load_efuse(Uint8Array)` / `load_app_elf(Uint8Array)` | eFuse / app ELF (for BLE symbol interception) |
|
||||
| `set_boot_from_rom(bool)` | call before `load_firmware` |
|
||||
| `restart()` / `needs_restart()` | soft reset (flash preserved) / firmware called esp_restart |
|
||||
|
||||
### 执行(宿主驱动循环)
|
||||
| 方法 | 说明 |
|
||||
### Execution (host-driven loop)
|
||||
| method | notes |
|
||||
|---|---|
|
||||
| `run_batch(n) → string` | 执行 ≤n 条指令,返回这批的 **UART0 输出字符串**(唯一输出通道) |
|
||||
| `throttle_delay_ms(now) → number` | 距墙钟的漂移,宿主按此 sleep 以配速到实时 |
|
||||
| `cycles()` / `cycles_per_us()` | 周期计数 / 主频(C3=160) |
|
||||
| `run_batch(n) → string` | executes ≤n instructions, returns the batch's **UART0 output string** (the only output channel) |
|
||||
| `throttle_delay_ms(now) → number` | drift from wall clock; the host sleeps by this to pace to real time |
|
||||
| `cycles()` / `cycles_per_us()` | cycle counter / clock (C3 = 160) |
|
||||
|
||||
### CPU 状态
|
||||
| 方法 | 说明 |
|
||||
### CPU state
|
||||
| method | notes |
|
||||
|---|---|
|
||||
| `pc()` / `hart_pc(h)` / `num_harts()` / `is_xtensa()` | PC、每核 PC、核数、ISA |
|
||||
| `registers() → Uint32Array` / `get_reg(i)` | RV32: x0..x31;Xtensa: a0..a15 + PS,SAR,WINDOWBASE,WINDOWSTART,EXCCAUSE,EXCVADDR,EPC1,LBEG,LEND,LCOUNT |
|
||||
| `read_memory(addr, len) → Uint8Array` | **总线读,MMIO 可读**(LEDC/RMT/GPIO matrix 等),未映射返回 0,单次上限 4 KiB |
|
||||
| `pc()` / `hart_pc(h)` / `num_harts()` / `is_xtensa()` | PC, per-hart PC, hart count, ISA |
|
||||
| `registers() → Uint32Array` / `get_reg(i)` | RV32: x0..x31; Xtensa: a0..a15 + PS,SAR,WINDOWBASE,WINDOWSTART,EXCCAUSE,EXCVADDR,EPC1,LBEG,LEND,LCOUNT |
|
||||
| `read_memory(addr, len) → Uint8Array` | **bus read; MMIO is readable** (LEDC/RMT/GPIO matrix etc.), unmapped returns 0, 4 KiB per call |
|
||||
|
||||
### GPIO(本项目的主要接口)
|
||||
| 方法 | 说明 |
|
||||
### GPIO (this project's main interface)
|
||||
| method | notes |
|
||||
|---|---|
|
||||
| `gpio_set_input(pin, high)` | 驱动 pad 输入电平;固件可读到,**可触发 GPIO 中断**(M0 实测 6/6);默认电平高 |
|
||||
| `gpio_state() → [out_lo, out_hi, enable_lo, enable_hi]` | GPIO0-63 输出与输出使能寄存器镜像;**仅 CPU 直写 GPIO 出现于此**——LEDC/RMT 等经 GPIO matrix 的信号不体现(M0 实测) |
|
||||
| `gpio_set_input(pin, high)` | drives a pad input level; firmware reads it and **GPIO interrupts fire** (M0: 6/6); default level high |
|
||||
| `gpio_state() → [out_lo, out_hi, enable_lo, enable_hi]` | mirror of the GPIO0-63 output and output-enable registers; **only CPU-direct GPIO writes appear here** — signals through the GPIO matrix (LEDC/RMT etc.) do not (M0 measurement) |
|
||||
|
||||
### UART / 网络
|
||||
| 方法 | 说明 |
|
||||
### UART / network
|
||||
| method | notes |
|
||||
|---|---|
|
||||
| `uart_input(Uint8Array)` | 字节注入 UART0 RX |
|
||||
| `set_wifi_config(ssid, pwd)` / `set_wifi_auth(ssid, pwd, auth, sae_pwe)` | 模拟 soft AP |
|
||||
| `wifi_rx_push(Uint8Array)` / `wifi_tx_drain() → Uint8Array` | 以太网帧 ↔ WiFi;TX 为 u32 长度前缀拼接,需外部 WebSocket TAP 代理(`tools/ws-net-proxy.py`) |
|
||||
| `uart_input(Uint8Array)` | inject bytes into UART0 RX |
|
||||
| `set_wifi_config(ssid, pwd)` / `set_wifi_auth(ssid, pwd, auth, sae_pwe)` | simulated soft AP |
|
||||
| `wifi_rx_push(Uint8Array)` / `wifi_tx_drain() → Uint8Array` | ethernet frames ↔ WiFi; TX is u32-length-prefixed concatenation, needs an external WebSocket TAP proxy (`tools/ws-net-proxy.py`) |
|
||||
|
||||
## 3. M0 实测的寄存器级事实(ESP32-C3)
|
||||
## 3. Register-level facts measured in M0 (ESP32-C3)
|
||||
|
||||
| 事实 | 数值/说明 |
|
||||
| fact | value / notes |
|
||||
|---|---|
|
||||
| LEDC duty 寄存器 | `0x60019000 + 0x14*n + {0,4,8,C,10}`(CONF0/HPOINT/DUTY/CONF1/DUTY_R);**reg = duty×16**(bits[18:4]);`read_memory` 精确可读(M0 Q2b 53/53) |
|
||||
| RMT 通道 RAM | `0x60016400 + 0xC0*n`,48 字/通道;符号发送后**留存**可事后解码(M0 Q3b);字格式 `d0:15,l0:1,d1:15,l1:1` |
|
||||
| GPIO matrix 路由 | `GPIO_FUNCn_OUT_SEL = 0x60004000+0x554+4n`;LEDC0 信号=0x2d,RMT ch0=0x33 → **可自动发现接线** |
|
||||
| IO_MUX(C3) | GPIO0..11 连续于 `0x60009000+0x04+4n` |
|
||||
| 时间快进 | `vTaskDelay` 期间 `cycles()` 大跳、不消耗完整 CPU 周期 → **CPU 睡眠窗口的引脚波形不可捕获**(与采样率无关) |
|
||||
| 其它 | GPIO_COUNT:C3=22/C5=29/C6=31/H2=28/P4=55/S3=49(无 22-25)/S31=32 |
|
||||
| LEDC duty registers | `0x60019000 + 0x14*n + {0,4,8,C,10}` (CONF0/HPOINT/DUTY/CONF1/DUTY_R); **reg = duty×16** (bits[18:4]); precisely readable via `read_memory` (M0 Q2b 53/53) |
|
||||
| RMT channel RAM | `0x60016400 + 0xC0*n`, 48 words/channel; symbols **remain after transmission** and can be decoded afterwards (M0 Q3b); word format `d0:15,l0:1,d1:15,l1:1` |
|
||||
| GPIO matrix routing | `GPIO_FUNCn_OUT_SEL = 0x60004000+0x554+4n`; LEDC0 signal = 0x2d, RMT ch0 = 0x33 → **wiring can be auto-discovered** |
|
||||
| IO_MUX (C3) | GPIO0..11 contiguous at `0x60009000+0x04+4n` |
|
||||
| Time fast-forward | during `vTaskDelay` `cycles()` jumps ahead without spending full CPU cycles → **pin waveforms inside CPU sleep windows cannot be captured** (independent of sample rate) |
|
||||
| Misc | GPIO_COUNT: C3=22 / C5=29 / C6=31 / H2=28 / P4=55 / S3=49 (no 22-25) / S31=32 |
|
||||
|
||||
## 4. 官方 worker 消息协议(www/worker.js,本项目 worker 复刻并扩展)
|
||||
## 4. Official worker message protocol (www/worker.js; this project replicates and extends it)
|
||||
|
||||
- 页面→worker:`init{wasmUrl}`、`load{chip,firmware,ssid,password,rom?,efuse?}`、`start`、`stop`、`step`、`reset{chip}`、`uart_input{data}`、`gpio_input{pin,high}`、`read_mem{addr,len}`、`mem_watch{addr,len|null}`、`net_connect{url}`、`net_disconnect`、`set_batch_size{size}`
|
||||
- worker→页面:`ready`、`loaded{chip,pc,isXtensa,numHarts,cyclesPerUs}`、`uart_output{data}`、`status{pc,cycles,mips,now}`、`snapshot{regs,pcs,gpio,cycles,mem?}`(150ms 节流)、`mem{addr,bytes}`、`step{output,pc,cycles}`、`restarted`、`reset{reloaded,pc?}`、`net_status{connected}`、`error{message}`
|
||||
- page→worker: `init{wasmUrl}`, `load{chip,firmware,ssid,password,rom?,efuse?}`,
|
||||
`start`, `stop`, `step`, `reset{chip}`, `uart_input{data}`,
|
||||
`gpio_input{pin,high}`, `read_mem{addr,len}`, `mem_watch{addr,len|null}`,
|
||||
`net_connect{url}`, `net_disconnect`, `set_batch_size{size}`
|
||||
- worker→page: `ready`, `loaded{chip,pc,isXtensa,numHarts,cyclesPerUs}`,
|
||||
`uart_output{data}`, `status{pc,cycles,mips,now}`,
|
||||
`snapshot{regs,pcs,gpio,cycles,mem?}` (150ms throttle), `mem{addr,bytes}`,
|
||||
`step{output,pc,cycles}`, `restarted`, `reset{reloaded,pc?}`,
|
||||
`net_status{connected}`, `error{message}`
|
||||
|
||||
## 5. wasm 导出面之外(浏览器形态缺失,CLI 才有)
|
||||
## 5. Beyond the wasm surface (browser build lacks these; CLI-only)
|
||||
|
||||
`--uart1-tcp`(外部串口设备)、`--i2c-dev`(哑寄存器 I2C 从机)、`--rmt-loopback`、
|
||||
`--control-tcp`(flash 写擦)、`--ble-hci`、`--thread-sim`、GDB stub、快照保存。
|
||||
这些正是 M5 路线图等待上游暴露的能力(见 [ROADMAP.md](ROADMAP.md))。
|
||||
`--uart1-tcp` (external serial device), `--i2c-dev` (dumb-register I2C slave),
|
||||
`--rmt-loopback`, `--control-tcp` (flash write/erase), `--ble-hci`,
|
||||
`--thread-sim`, GDB stub, snapshot saving.
|
||||
These are exactly the capabilities the M5 roadmap waits on
|
||||
(see [ROADMAP.md](ROADMAP.md)).
|
||||
@@ -0,0 +1,77 @@
|
||||
[English](EMU-API.md) | **简体中文**
|
||||
|
||||
# esp-emulator 浏览器 API 摘要(wasm v0.51.0)
|
||||
|
||||
本文档是对 **闭源** `esp_emu_bg.wasm` 对 JS 暴露的全部能力的逆向整理,来源:官方
|
||||
`www/pkg/esp_emu.js`(wasm-bindgen 胶水)、官方文档(esp-emulator README/BROWSER.md)
|
||||
与本项目的 M0 实测(`tests/m0_probe.mjs`,结论详见 [FEASIBILITY.zh-cn.md](FEASIBILITY.zh-cn.md))。
|
||||
上游升级后请重跑探针核对本文。
|
||||
|
||||
## 1. 加载与初始化
|
||||
|
||||
```js
|
||||
import init, { WasmEmulator, initSync } from './pkg/esp_emu.js';
|
||||
await init(); // fetch+instantiate esp_emu_bg.wasm(相对 esp_emu.js)
|
||||
// 或 Node 环境:initSync(fs.readFileSync('esp_emu_bg.wasm')) // 同步初始化
|
||||
const emu = new WasmEmulator('esp32c3'); // 'esp32c3'|'esp32c5'|'esp32c6'|'esp32h2'|'esp32p4'|'esp32s3'|'esp32s31'
|
||||
```
|
||||
|
||||
## 2. 方法一览(全部同步;**无任何回调,只能轮询**)
|
||||
|
||||
### 生命周期 / 加载
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| `load_firmware(Uint8Array)` | **必须是 merged flash image**(bootloader@0x0+分区表@0x8000+app@0x10000,`idf.py merge-bin` 生成);裸 app bin 无法启动 |
|
||||
| `load_rom_elf(Uint8Array)` / `load_default_rom()` / `has_default_rom()` | ROM;缺省用内嵌版本 |
|
||||
| `load_efuse(Uint8Array)` / `load_app_elf(Uint8Array)` | eFuse / app ELF(BLE 符号拦截用) |
|
||||
| `set_boot_from_rom(bool)` | 在 `load_firmware` 前调用 |
|
||||
| `restart()` / `needs_restart()` | 软复位(保留 flash)/ 固件调用了 esp_restart |
|
||||
|
||||
### 执行(宿主驱动循环)
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| `run_batch(n) → string` | 执行 ≤n 条指令,返回这批的 **UART0 输出字符串**(唯一输出通道) |
|
||||
| `throttle_delay_ms(now) → number` | 距墙钟的漂移,宿主按此 sleep 以配速到实时 |
|
||||
| `cycles()` / `cycles_per_us()` | 周期计数 / 主频(C3=160) |
|
||||
|
||||
### CPU 状态
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| `pc()` / `hart_pc(h)` / `num_harts()` / `is_xtensa()` | PC、每核 PC、核数、ISA |
|
||||
| `registers() → Uint32Array` / `get_reg(i)` | RV32: x0..x31;Xtensa: a0..a15 + PS,SAR,WINDOWBASE,WINDOWSTART,EXCCAUSE,EXCVADDR,EPC1,LBEG,LEND,LCOUNT |
|
||||
| `read_memory(addr, len) → Uint8Array` | **总线读,MMIO 可读**(LEDC/RMT/GPIO matrix 等),未映射返回 0,单次上限 4 KiB |
|
||||
|
||||
### GPIO(本项目的主要接口)
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| `gpio_set_input(pin, high)` | 驱动 pad 输入电平;固件可读到,**可触发 GPIO 中断**(M0 实测 6/6);默认电平高 |
|
||||
| `gpio_state() → [out_lo, out_hi, enable_lo, enable_hi]` | GPIO0-63 输出与输出使能寄存器镜像;**仅 CPU 直写 GPIO 出现于此**——LEDC/RMT 等经 GPIO matrix 的信号不体现(M0 实测) |
|
||||
|
||||
### UART / 网络
|
||||
| 方法 | 说明 |
|
||||
|---|---|
|
||||
| `uart_input(Uint8Array)` | 字节注入 UART0 RX |
|
||||
| `set_wifi_config(ssid, pwd)` / `set_wifi_auth(ssid, pwd, auth, sae_pwe)` | 模拟 soft AP |
|
||||
| `wifi_rx_push(Uint8Array)` / `wifi_tx_drain() → Uint8Array` | 以太网帧 ↔ WiFi;TX 为 u32 长度前缀拼接,需外部 WebSocket TAP 代理(`tools/ws-net-proxy.py`) |
|
||||
|
||||
## 3. M0 实测的寄存器级事实(ESP32-C3)
|
||||
|
||||
| 事实 | 数值/说明 |
|
||||
|---|---|
|
||||
| LEDC duty 寄存器 | `0x60019000 + 0x14*n + {0,4,8,C,10}`(CONF0/HPOINT/DUTY/CONF1/DUTY_R);**reg = duty×16**(bits[18:4]);`read_memory` 精确可读(M0 Q2b 53/53) |
|
||||
| RMT 通道 RAM | `0x60016400 + 0xC0*n`,48 字/通道;符号发送后**留存**可事后解码(M0 Q3b);字格式 `d0:15,l0:1,d1:15,l1:1` |
|
||||
| GPIO matrix 路由 | `GPIO_FUNCn_OUT_SEL = 0x60004000+0x554+4n`;LEDC0 信号=0x2d,RMT ch0=0x33 → **可自动发现接线** |
|
||||
| IO_MUX(C3) | GPIO0..11 连续于 `0x60009000+0x04+4n` |
|
||||
| 时间快进 | `vTaskDelay` 期间 `cycles()` 大跳、不消耗完整 CPU 周期 → **CPU 睡眠窗口的引脚波形不可捕获**(与采样率无关) |
|
||||
| 其它 | GPIO_COUNT:C3=22/C5=29/C6=31/H2=28/P4=55/S3=49(无 22-25)/S31=32 |
|
||||
|
||||
## 4. 官方 worker 消息协议(www/worker.js,本项目 worker 复刻并扩展)
|
||||
|
||||
- 页面→worker:`init{wasmUrl}`、`load{chip,firmware,ssid,password,rom?,efuse?}`、`start`、`stop`、`step`、`reset{chip}`、`uart_input{data}`、`gpio_input{pin,high}`、`read_mem{addr,len}`、`mem_watch{addr,len|null}`、`net_connect{url}`、`net_disconnect`、`set_batch_size{size}`
|
||||
- worker→页面:`ready`、`loaded{chip,pc,isXtensa,numHarts,cyclesPerUs}`、`uart_output{data}`、`status{pc,cycles,mips,now}`、`snapshot{regs,pcs,gpio,cycles,mem?}`(150ms 节流)、`mem{addr,bytes}`、`step{output,pc,cycles}`、`restarted`、`reset{reloaded,pc?}`、`net_status{connected}`、`error{message}`
|
||||
|
||||
## 5. wasm 导出面之外(浏览器形态缺失,CLI 才有)
|
||||
|
||||
`--uart1-tcp`(外部串口设备)、`--i2c-dev`(哑寄存器 I2C 从机)、`--rmt-loopback`、
|
||||
`--control-tcp`(flash 写擦)、`--ble-hci`、`--thread-sim`、GDB stub、快照保存。
|
||||
这些正是 M5 路线图等待上游暴露的能力(见 [ROADMAP.zh-cn.md](ROADMAP.zh-cn.md))。
|
||||
+75
-51
@@ -1,65 +1,89 @@
|
||||
# M0 可行性验证报告(esp-emu-peris)
|
||||
**English** | [简体中文](FEASIBILITY.zh-cn.md)
|
||||
|
||||
日期:2026-10-29 · 环境:esp-emulator browser wasm v0.51.0(`_tools/esp-emu-browser/www/pkg`)
|
||||
固件:ESP-IDF master(`C:\esp\master\esp-idf`,经 `C:\Users\A\Desktop\quick\idfm.bat` 环境)
|
||||
复现:`node tests/m0_probe.mjs`(探针)+ `tests/firmware/peris_probe`(测试固件,`idf.py build && idf.py merge-bin` 后加载 `build/merged-binary.bin`)
|
||||
# M0 feasibility report (esp-emu-peris)
|
||||
|
||||
## 结论总表
|
||||
Date: 2026-10-29 · Environment: esp-emulator browser wasm v0.51.0
|
||||
(`_tools/esp-emu-browser/www/pkg`)
|
||||
Firmware: ESP-IDF master (`C:\esp\master\esp-idf`, env via
|
||||
`C:\Users\A\Desktop\quick\idfm.bat`)
|
||||
Reproduce: `node tests/m0_probe.mjs` (the probe) + `tests/firmware/peris_probe`
|
||||
(test firmware; `idf.py build && idf.py merge-bin`, then load
|
||||
`build/merged-binary.bin`)
|
||||
|
||||
| # | 问题 | 结论 | 证据 |
|
||||
## Summary of conclusions
|
||||
|
||||
| # | question | result | evidence |
|
||||
|---|---|---|---|
|
||||
| Q1a | 固件 GPIO 输出能否被 `gpio_state()` 轮询观察到 | **PASS** | 8 次翻转全部可见(采样粒度 5 万指令/批) |
|
||||
| Q1b | `gpio_set_input()` 驱动引脚能否触发固件 GPIO 中断 | **PASS** | 探针驱动 6 个下降沿,固件 ISR 计数 6/6 |
|
||||
| Q2a | LEDC PWM 波形是否体现在 GPIO 输出寄存器(pad 级轮询测占空比) | **FAIL** | 引脚 oe=1 但 out 恒 0;duty=50%/100% 期间估计值恒 0 |
|
||||
| Q2b | LEDC duty 寄存器能否用 `read_memory()` 读到且跟踪设定值 | **PASS** | 53/53 样本跟踪;`half(4096)→DUTY_R=0x10000=4096×16`,`max(8191)→0x1fff0` |
|
||||
| Q3a | RMT/WS2812 波形能否用小批量 `run_batch(40)` 在引脚级采样解码 | **FAIL** | 2472 批采样中 out4 恒 0,无任何电平变化 |
|
||||
| Q3b | RMT 通道 RAM 能否用 `read_memory()` 读出并解码 WS2812 帧 | **PASS** | 93 次快照解出 46 帧,匹配固件日志颜色 0x111111/0x222222/0x333333 |
|
||||
| Q1a | can firmware GPIO output be observed by polling `gpio_state()` | **PASS** | all 8 toggles visible (sampling granularity 50k instructions/batch) |
|
||||
| Q1b | can `gpio_set_input()` on a pin raise the firmware's GPIO interrupt | **PASS** | probe drove 6 falling edges; firmware ISR counted 6/6 |
|
||||
| Q2a | do LEDC PWM waveforms show up in the GPIO output registers (pad-level duty-cycle polling) | **FAIL** | pin oe=1 but out constantly 0; estimates stuck at 0 during duty=50%/100% |
|
||||
| Q2b | can LEDC duty registers be read via `read_memory()` and do they track the setpoint | **PASS** | 53/53 samples tracked; `half(4096)→DUTY_R=0x10000=4096×16`, `max(8191)→0x1fff0` |
|
||||
| Q3a | can RMT/WS2812 waveforms be sampled at pin level with small `run_batch(40)` calls | **FAIL** | out4 constant 0 across 2472 sampled batches, no level change at all |
|
||||
| Q3b | can RMT channel RAM be read via `read_memory()` and decoded into WS2812 frames | **PASS** | 46 frames decoded from 93 snapshots, colours matching the firmware log 0x111111/0x222222/0x333333 |
|
||||
|
||||
## 对外设方案的决定性影响
|
||||
## Decisive impact on the peripheral plans
|
||||
|
||||
- **LED/物理按键/触摸按键(GPIO 语义)**:直接可行——Q1a+Q1b 全通过。
|
||||
- **PWM 调光 LED**:走 **MMIO 寄存器路径**(Q2b),不走引脚采样(Q2a 已证不可行)。读
|
||||
`LEDC_CHn_DUTY_R` 即得精确占空比,无混叠、无开销。
|
||||
- **WS2812**:走 **RMT 通道 RAM 路径**(Q3b),不走引脚采样(Q3a 已证不可行)。符号在
|
||||
发送后仍留存于 RAM,可事后解码。
|
||||
- **位绑定(bit-bang)I2C/SPI/IR**:仍可行——Q1a 证明 CPU 主动翻转 GPIO_OUT 可见;
|
||||
CPU 在翻转时不睡眠,不存在下面的"时间快进"问题。高速采样仅在 CPU 活跃窗口有效。
|
||||
- **LED / push button / touch button (GPIO semantics)**: directly viable —
|
||||
Q1a + Q1b fully pass.
|
||||
- **PWM dimmed LED**: take the **MMIO register path** (Q2b), not pin sampling
|
||||
(Q2a proven unviable). Reading `LEDC_CHn_DUTY_R` gives the exact duty cycle
|
||||
— no aliasing, no overhead.
|
||||
- **WS2812**: take the **RMT channel RAM path** (Q3b), not pin sampling (Q3a
|
||||
proven unviable). Symbols remain in RAM after transmission and can be
|
||||
decoded afterwards.
|
||||
- **Bit-banged I2C/SPI/IR**: still viable — Q1a shows CPU-driven GPIO_OUT
|
||||
flips are visible; the CPU does not sleep while flipping, so the
|
||||
"time fast-forward" issue below does not apply. High-rate sampling only
|
||||
works inside CPU-active windows.
|
||||
|
||||
## 关键技术细节
|
||||
## Key technical details
|
||||
|
||||
### ESP32-C3 寄存器地址(来自 IDF soc 头文件)
|
||||
### ESP32-C3 register addresses (from IDF soc headers)
|
||||
|
||||
| 寄存器 | 地址 | 备注 |
|
||||
| register | address | notes |
|
||||
|---|---|---|
|
||||
| GPIO_OUT / GPIO_ENABLE | 0x60004004 / 0x60004020 | `gpio_state()` 即这两个字的镜像 |
|
||||
| GPIO_FUNCn_OUT_SEL_CFG | 0x60004000+0x554+4n | 低 9 位 = GPIO matrix 输出信号选择 |
|
||||
| IO_MUX_GPIO0..11_REG | 0x60009000+0x04+4n | bit10..12 MCU_SEL 等pad配置 |
|
||||
| LEDC_CHn_(CONF0/HPOINT/DUTY/CONF1/DUTY_R) | 0x60019000+0x14n(+0/4/8/C/10) | C3 用 LSCHn 命名 |
|
||||
| RMT_CHnCONF0 / 通道 RAM(RMTMEM) | 0x60016010+8n / 0x60016400+0xC0n | 每通道 48 字(192 字节) |
|
||||
| GPIO_OUT / GPIO_ENABLE | 0x60004004 / 0x60004020 | `gpio_state()` mirrors exactly these words |
|
||||
| GPIO_FUNCn_OUT_SEL_CFG | 0x60004000+0x554+4n | low 9 bits = GPIO-matrix output signal select |
|
||||
| IO_MUX_GPIO0..11_REG | 0x60009000+0x04+4n | bits 10..12 MCU_SEL and other pad config |
|
||||
| LEDC_CHn_(CONF0/HPOINT/DUTY/CONF1/DUTY_R) | 0x60019000+0x14n(+0/4/8/C/10) | C3 uses the LSCHn naming |
|
||||
| RMT_CHnCONF0 / channel RAM (RMTMEM) | 0x60016010+8n / 0x60016400+0xC0n | 48 words (192 bytes) per channel |
|
||||
|
||||
### 实测行为要点
|
||||
### Measured behaviour highlights
|
||||
|
||||
1. **LEDC duty 布局**:`DUTY_CH0` 字段在 bits[18:4],寄存器值 = duty × 16(与真机一致)。
|
||||
2. **GPIO matrix 路由可读**:固件把 GPIO9 配给 LEDC0 后 `FUNC9_OUT_SEL=0x2d`(=45,
|
||||
LEDC_LS_CH0_SIG),GPIO4 配给 RMT 后 `FUNC4_OUT_SEL=0x33`(=51,RMT_SIG_0)。
|
||||
**外设接线的自动发现**可据此实现:轮询所有引脚的 OUT_SEL + IO_MUX 即可知道每个
|
||||
pad 当前由哪个外设信号驱动。
|
||||
3. **RMT RAM 留存**:发送完成后符号不清除;48 字/通道;>192 符号的帧走 ping-pong
|
||||
(TX_THR_EVENT 重填+wrap),快照中可能同时存在相邻帧的片段,帧间以 reset 符号
|
||||
(低电平 ≥30µs,即 d0≥300 且 l0=0)分界。
|
||||
4. **位网格重同步**:本测试中 RAM 内位流相对 LED 字节网格缺头部 2 位(ping-pong 写入
|
||||
顺序所致),前置补 0 搜索(0..7)即可对齐;真实设备解码器需实现该重同步。
|
||||
5. **时间快进**:`vTaskDelay` 期间模拟器不消耗完整 CPU 周期(一批 `run_batch(40)` 中
|
||||
`cycles()` 可跳数千),因此 **CPU 睡眠期间的外设波形在引脚级不可捕获**——这是 Q3a
|
||||
失败的根本原因,与采样率无关。
|
||||
6. **性能**(Node 24,直接 wasm 调用):粗粒度(50k 指令/批)≈ **5000+ MIPS**;细粒度
|
||||
(40 指令/批 + 每批 `gpio_state()`)≈ **99 MIPS**(约 53 倍开销)。浏览器 worker 内
|
||||
大致同量级——细粒度采样只应作为 CPU 活跃窗口的临时手段,常驻采样用粗粒度+MMIO。
|
||||
7. **加载固件必须是合并镜像**(bootloader@0x0 + 分区表@0x8000 + app@0x10000),
|
||||
用 `idf.py merge-bin` 生成;裸 app bin 不能启动。
|
||||
1. **LEDC duty layout**: the `DUTY_CH0` field sits in bits[18:4]; register
|
||||
value = duty × 16 (same as real hardware).
|
||||
2. **GPIO-matrix routing is readable**: after the firmware maps GPIO9 to
|
||||
LEDC0, `FUNC9_OUT_SEL=0x2d` (=45, LEDC_LS_CH0_SIG); after mapping GPIO4 to
|
||||
RMT, `FUNC4_OUT_SEL=0x33` (=51, RMT_SIG_0). **Auto-discovery of peripheral
|
||||
wiring** can be built on this: poll every pin's OUT_SEL + IO_MUX to learn
|
||||
which peripheral signal currently drives each pad.
|
||||
3. **RMT RAM retention**: symbols are not cleared after transmission;
|
||||
48 words per channel; frames with >192 symbols use ping-pong
|
||||
(TX_THR_EVENT refill + wrap), so a snapshot may contain fragments of
|
||||
adjacent frames; frames are delimited by the reset symbol (low ≥30µs,
|
||||
i.e. d0≥300 with l0=0).
|
||||
4. **Bit-grid resynchronisation**: in this test the RAM bitstream was missing
|
||||
2 head bits relative to the LED byte grid (ping-pong write order); a
|
||||
leading-zero search (0..7) re-aligns it; real device decoders must
|
||||
implement that resync.
|
||||
5. **Time fast-forward**: during `vTaskDelay` the emulator does not spend
|
||||
full CPU cycles (`cycles()` can jump by thousands inside one
|
||||
`run_batch(40)`), therefore **peripheral waveforms during CPU sleep are
|
||||
not capturable at pin level** — the root cause of the Q3a failure,
|
||||
independent of sample rate.
|
||||
6. **Performance** (Node 24, direct wasm calls): coarse (50k instr/batch) ≈
|
||||
**5000+ MIPS**; fine (40 instr/batch + `gpio_state()` each batch) ≈
|
||||
**99 MIPS** (~53× overhead). A browser worker is in the same ballpark —
|
||||
fine-grained sampling should only be a temporary measure inside
|
||||
CPU-active windows; standing sampling uses coarse batches + MMIO.
|
||||
7. **Firmware must be a merged image** (bootloader@0x0 + partition
|
||||
table@0x8000 + app@0x10000), produced by `idf.py merge-bin`; a bare app
|
||||
bin does not boot.
|
||||
|
||||
## 已排除 / 仍受上游限制的能力
|
||||
## Ruled out / still limited by upstream
|
||||
|
||||
- 硬件 I2C/SPI 外接设备模型、真触摸外设、ADC、I2S、SPI/RGB565 屏、SD 卡、UART1:
|
||||
wasm 无对应 API,需等 esp-emulator 上游(见 docs/ROADMAP.md)。
|
||||
- 引脚级 PWM/RMT 波形观察:结构性不可行(Q2a/Q3a),不依赖采样率。
|
||||
- Hardware I2C/SPI external device models, a real touch peripheral, ADC,
|
||||
I2S, SPI/RGB565 displays, SD cards, UART1: no wasm API — waiting on
|
||||
esp-emulator upstream (see docs/ROADMAP.md).
|
||||
- Pin-level PWM/RMT waveform observation: structurally unviable
|
||||
(Q2a/Q3a), independent of sample rate.
|
||||
@@ -0,0 +1,67 @@
|
||||
[English](FEASIBILITY.md) | **简体中文**
|
||||
|
||||
# M0 可行性验证报告(esp-emu-peris)
|
||||
|
||||
日期:2026-10-29 · 环境:esp-emulator browser wasm v0.51.0(`_tools/esp-emu-browser/www/pkg`)
|
||||
固件:ESP-IDF master(`C:\esp\master\esp-idf`,经 `C:\Users\A\Desktop\quick\idfm.bat` 环境)
|
||||
复现:`node tests/m0_probe.mjs`(探针)+ `tests/firmware/peris_probe`(测试固件,`idf.py build && idf.py merge-bin` 后加载 `build/merged-binary.bin`)
|
||||
|
||||
## 结论总表
|
||||
|
||||
| # | 问题 | 结论 | 证据 |
|
||||
|---|---|---|---|
|
||||
| Q1a | 固件 GPIO 输出能否被 `gpio_state()` 轮询观察到 | **PASS** | 8 次翻转全部可见(采样粒度 5 万指令/批) |
|
||||
| Q1b | `gpio_set_input()` 驱动引脚能否触发固件 GPIO 中断 | **PASS** | 探针驱动 6 个下降沿,固件 ISR 计数 6/6 |
|
||||
| Q2a | LEDC PWM 波形是否体现在 GPIO 输出寄存器(pad 级轮询测占空比) | **FAIL** | 引脚 oe=1 但 out 恒 0;duty=50%/100% 期间估计值恒 0 |
|
||||
| Q2b | LEDC duty 寄存器能否用 `read_memory()` 读到且跟踪设定值 | **PASS** | 53/53 样本跟踪;`half(4096)→DUTY_R=0x10000=4096×16`,`max(8191)→0x1fff0` |
|
||||
| Q3a | RMT/WS2812 波形能否用小批量 `run_batch(40)` 在引脚级采样解码 | **FAIL** | 2472 批采样中 out4 恒 0,无任何电平变化 |
|
||||
| Q3b | RMT 通道 RAM 能否用 `read_memory()` 读出并解码 WS2812 帧 | **PASS** | 93 次快照解出 46 帧,匹配固件日志颜色 0x111111/0x222222/0x333333 |
|
||||
|
||||
## 对外设方案的决定性影响
|
||||
|
||||
- **LED/物理按键/触摸按键(GPIO 语义)**:直接可行——Q1a+Q1b 全通过。
|
||||
- **PWM 调光 LED**:走 **MMIO 寄存器路径**(Q2b),不走引脚采样(Q2a 已证不可行)。读
|
||||
`LEDC_CHn_DUTY_R` 即得精确占空比,无混叠、无开销。
|
||||
- **WS2812**:走 **RMT 通道 RAM 路径**(Q3b),不走引脚采样(Q3a 已证不可行)。符号在
|
||||
发送后仍留存于 RAM,可事后解码。
|
||||
- **位绑定(bit-bang)I2C/SPI/IR**:仍可行——Q1a 证明 CPU 主动翻转 GPIO_OUT 可见;
|
||||
CPU 在翻转时不睡眠,不存在下面的"时间快进"问题。高速采样仅在 CPU 活跃窗口有效。
|
||||
|
||||
## 关键技术细节
|
||||
|
||||
### ESP32-C3 寄存器地址(来自 IDF soc 头文件)
|
||||
|
||||
| 寄存器 | 地址 | 备注 |
|
||||
|---|---|---|
|
||||
| GPIO_OUT / GPIO_ENABLE | 0x60004004 / 0x60004020 | `gpio_state()` 即这两个字的镜像 |
|
||||
| GPIO_FUNCn_OUT_SEL_CFG | 0x60004000+0x554+4n | 低 9 位 = GPIO matrix 输出信号选择 |
|
||||
| IO_MUX_GPIO0..11_REG | 0x60009000+0x04+4n | bit10..12 MCU_SEL 等pad配置 |
|
||||
| LEDC_CHn_(CONF0/HPOINT/DUTY/CONF1/DUTY_R) | 0x60019000+0x14n(+0/4/8/C/10) | C3 用 LSCHn 命名 |
|
||||
| RMT_CHnCONF0 / 通道 RAM(RMTMEM) | 0x60016010+8n / 0x60016400+0xC0n | 每通道 48 字(192 字节) |
|
||||
|
||||
### 实测行为要点
|
||||
|
||||
1. **LEDC duty 布局**:`DUTY_CH0` 字段在 bits[18:4],寄存器值 = duty × 16(与真机一致)。
|
||||
2. **GPIO matrix 路由可读**:固件把 GPIO9 配给 LEDC0 后 `FUNC9_OUT_SEL=0x2d`(=45,
|
||||
LEDC_LS_CH0_SIG),GPIO4 配给 RMT 后 `FUNC4_OUT_SEL=0x33`(=51,RMT_SIG_0)。
|
||||
**外设接线的自动发现**可据此实现:轮询所有引脚的 OUT_SEL + IO_MUX 即可知道每个
|
||||
pad 当前由哪个外设信号驱动。
|
||||
3. **RMT RAM 留存**:发送完成后符号不清除;48 字/通道;>192 符号的帧走 ping-pong
|
||||
(TX_THR_EVENT 重填+wrap),快照中可能同时存在相邻帧的片段,帧间以 reset 符号
|
||||
(低电平 ≥30µs,即 d0≥300 且 l0=0)分界。
|
||||
4. **位网格重同步**:本测试中 RAM 内位流相对 LED 字节网格缺头部 2 位(ping-pong 写入
|
||||
顺序所致),前置补 0 搜索(0..7)即可对齐;真实设备解码器需实现该重同步。
|
||||
5. **时间快进**:`vTaskDelay` 期间模拟器不消耗完整 CPU 周期(一批 `run_batch(40)` 中
|
||||
`cycles()` 可跳数千),因此 **CPU 睡眠期间的外设波形在引脚级不可捕获**——这是 Q3a
|
||||
失败的根本原因,与采样率无关。
|
||||
6. **性能**(Node 24,直接 wasm 调用):粗粒度(50k 指令/批)≈ **5000+ MIPS**;细粒度
|
||||
(40 指令/批 + 每批 `gpio_state()`)≈ **99 MIPS**(约 53 倍开销)。浏览器 worker 内
|
||||
大致同量级——细粒度采样只应作为 CPU 活跃窗口的临时手段,常驻采样用粗粒度+MMIO。
|
||||
7. **加载固件必须是合并镜像**(bootloader@0x0 + 分区表@0x8000 + app@0x10000),
|
||||
用 `idf.py merge-bin` 生成;裸 app bin 不能启动。
|
||||
|
||||
## 已排除 / 仍受上游限制的能力
|
||||
|
||||
- 硬件 I2C/SPI 外接设备模型、真触摸外设、ADC、I2S、SPI/RGB565 屏、SD 卡、UART1:
|
||||
wasm 无对应 API,需等 esp-emulator 上游(见 docs/ROADMAP.zh-cn.md)。
|
||||
- 引脚级 PWM/RMT 波形观察:结构性不可行(Q2a/Q3a),不依赖采样率。
|
||||
+46
-32
@@ -1,44 +1,58 @@
|
||||
# 路线图
|
||||
**English** | [简体中文](ROADMAP.zh-cn.md)
|
||||
|
||||
## M2 · PWM 调光 LED(下一期)
|
||||
# Roadmap
|
||||
|
||||
- `pwm-led.js` + `rgb-led.js`:读 LEDC 各通道 `DUTY_R` 寄存器(地址表 `peris/mmio/`),
|
||||
精确占空比,无混叠(路线已在 M0 验证:Q2b PASS)。
|
||||
- 通道→引脚映射:读 `GPIO_FUNCn_OUT_SEL_CFG`(M0 实测 LEDC0=0x2d、RMT0=0x33),
|
||||
实现"自动发现接线"辅助;也允许手动指定通道/引脚。
|
||||
- 呼吸灯平滑渲染(canvas 亮度插值)。
|
||||
## M2 · PWM dimmed LED (next)
|
||||
|
||||
## M3 · WS2812 幻彩灯
|
||||
- `pwm-led.js` + `rgb-led.js`: read each LEDC channel's `DUTY_R` register
|
||||
(address tables in `peris/mmio/`) for exact duty cycles, no aliasing
|
||||
(approach verified in M0: Q2b PASS).
|
||||
- Channel→pin mapping: read `GPIO_FUNCn_OUT_SEL_CFG` (M0 measured LEDC0 =
|
||||
0x2d, RMT0 = 0x33) to implement "auto-discovered wiring" assistance;
|
||||
manual channel/pin selection also allowed.
|
||||
- Breathing effect with smooth rendering (canvas brightness interpolation).
|
||||
|
||||
- `ws2812.js`:轮询 RMT 通道 RAM(M0 验证 Q3b PASS),符号→位流→GRB 帧;
|
||||
ping-pong 分组 + reset 符号重同步 + 前置补零对齐(细节见 FEASIBILITY.md)。
|
||||
- 灯条/灯环/矩阵三种 View;GRB/RGB 顺序、亮度限制、Gamma 可配。
|
||||
- 性能策略:JS 解码先行;若实测不够,把"采样+解码"热路径用 C/C++ 编成独立 wasm
|
||||
辅助模块在 worker 内调用(不动模拟器的闭源 wasm,不影响设备 JS 接口)。
|
||||
## M3 · WS2812 addressable LEDs
|
||||
|
||||
## M4 · 位绑定协议设备 + IR
|
||||
- `ws2812.js`: poll the RMT channel RAM (M0 verified Q3b PASS),
|
||||
symbols → bitstream → GRB frames; ping-pong grouping + reset-symbol
|
||||
resync + leading-zero alignment (details in FEASIBILITY.md).
|
||||
- Strip / ring / matrix Views; GRB/RGB order, brightness cap and gamma
|
||||
configurable.
|
||||
- Performance strategy: JS decoding first; if measurements show it is not
|
||||
enough, compile the hot "sample + decode" path into a separate wasm helper
|
||||
module (C/C++) invoked from the worker (the emulator's closed-source wasm
|
||||
stays untouched, the device JS interface unchanged).
|
||||
|
||||
- `bitbang-i2c-slave.js`(SCL/SDA 上模拟 24Cxx/BME280 等)、`ir-nec.js`(捕获固件 RMT/位绑定发射)。
|
||||
- 前提:固件用 GPIO 位绑定(硬件 I2C/SPI 总线无法从外部接入,M0 已确认)。
|
||||
需要配套测试固件与文档说明此约束。
|
||||
- 高速采样调度(sigbus 扩展:临时小批量窗口,CPU 活跃期有效)。
|
||||
## M4 · Bit-bang protocol devices + IR
|
||||
|
||||
## M5 · 上游能力跟进(不可控)
|
||||
- `bitbang-i2c-slave.js` (emulating 24Cxx/BME280 etc. on SCL/SDA) and
|
||||
`ir-nec.js` (capturing firmware RMT/bit-bang transmissions).
|
||||
- Prerequisite: firmware uses GPIO bit-banging (hardware I2C/SPI buses
|
||||
cannot be tapped from outside — confirmed in M0). Needs accompanying test
|
||||
firmware and documentation of this constraint.
|
||||
- High-rate sampling scheduler (sigbus extension: temporary fine-batch
|
||||
windows, effective while the CPU is active).
|
||||
|
||||
以下能力当前 wasm 无 API,只能跟进 esp-emulator 新版本(升级流程见 UPGRADE.md):
|
||||
## M5 · Upstream capability tracking (not under our control)
|
||||
|
||||
| 能力 | 依赖的上游接口 | 现状 |
|
||||
These need APIs the wasm does not expose today; progress depends on new
|
||||
esp-emulator releases (upgrade procedure in UPGRADE.md):
|
||||
|
||||
| capability | required upstream interface | status |
|
||||
|---|---|---|
|
||||
| 硬件 I2C 设备模型 | i2c slave/设备桥 API(仅 CLI `--i2c-dev`,且只有哑寄存器) | ❌ |
|
||||
| 硬件 SPI 外设(SD 卡等) | spi 设备桥 | ❌ |
|
||||
| SPI/RGB565 显示屏 | spi/lcd_cam(外设本身未模拟) | ❌ |
|
||||
| 真触摸外设 | touch 外设模拟 | ❌(触摸按键以 GPIO 语义代替) |
|
||||
| ADC(电位器等) | adc 模拟 | ❌ |
|
||||
| I2S 音频 | i2s 模拟 | ❌ |
|
||||
| UART1 外设 | wasm 暴露 uart1(CLI 有 `--uart1-tcp`) | ❌ |
|
||||
| 状态快照保存/恢复 | save/load API | ❌ |
|
||||
| hardware I2C device models | i2c slave/device bridge API (CLI-only `--i2c-dev`, dumb registers only) | ❌ |
|
||||
| hardware SPI peripherals (SD card etc.) | spi device bridge | ❌ |
|
||||
| SPI/RGB565 displays | spi/lcd_cam (the peripheral itself is not emulated) | ❌ |
|
||||
| real touch peripheral | touch peripheral emulation | ❌ (touch button stands in with GPIO semantics) |
|
||||
| ADC (potentiometers etc.) | adc emulation | ❌ |
|
||||
| I2S audio | i2s emulation | ❌ |
|
||||
| UART1 peripheral | wasm-exposed uart1 (CLI has `--uart1-tcp`) | ❌ |
|
||||
| state snapshot save/restore | save/load API | ❌ |
|
||||
|
||||
## 维护项
|
||||
## Maintenance items
|
||||
|
||||
- `update_esp_emu_browser.bat`(在 `_tools`,当前为空):按 UPGRADE.md 流程脚本化。
|
||||
- `peris/mmio/`:补 C5/C6/H2/P4/S3 地址表(从各自 IDF soc 头文件提取,方法同 M0)。
|
||||
- `update_esp_emu_browser.bat` (in `_tools`, currently empty): script the
|
||||
UPGRADE.md procedure.
|
||||
- `peris/mmio/`: add the C5/C6/H2/P4/S3 address tables (extracted from each
|
||||
chip's IDF soc headers, same method as M0).
|
||||
@@ -0,0 +1,46 @@
|
||||
[English](ROADMAP.md) | **简体中文**
|
||||
|
||||
# 路线图
|
||||
|
||||
## M2 · PWM 调光 LED(下一期)
|
||||
|
||||
- `pwm-led.js` + `rgb-led.js`:读 LEDC 各通道 `DUTY_R` 寄存器(地址表 `peris/mmio/`),
|
||||
精确占空比,无混叠(路线已在 M0 验证:Q2b PASS)。
|
||||
- 通道→引脚映射:读 `GPIO_FUNCn_OUT_SEL_CFG`(M0 实测 LEDC0=0x2d、RMT0=0x33),
|
||||
实现"自动发现接线"辅助;也允许手动指定通道/引脚。
|
||||
- 呼吸灯平滑渲染(canvas 亮度插值)。
|
||||
|
||||
## M3 · WS2812 幻彩灯
|
||||
|
||||
- `ws2812.js`:轮询 RMT 通道 RAM(M0 验证 Q3b PASS),符号→位流→GRB 帧;
|
||||
ping-pong 分组 + reset 符号重同步 + 前置补零对齐(细节见 FEASIBILITY.zh-cn.md)。
|
||||
- 灯条/灯环/矩阵三种 View;GRB/RGB 顺序、亮度限制、Gamma 可配。
|
||||
- 性能策略:JS 解码先行;若实测不够,把"采样+解码"热路径用 C/C++ 编成独立 wasm
|
||||
辅助模块在 worker 内调用(不动模拟器的闭源 wasm,不影响设备 JS 接口)。
|
||||
|
||||
## M4 · 位绑定协议设备 + IR
|
||||
|
||||
- `bitbang-i2c-slave.js`(SCL/SDA 上模拟 24Cxx/BME280 等)、`ir-nec.js`(捕获固件 RMT/位绑定发射)。
|
||||
- 前提:固件用 GPIO 位绑定(硬件 I2C/SPI 总线无法从外部接入,M0 已确认)。
|
||||
需要配套测试固件与文档说明此约束。
|
||||
- 高速采样调度(sigbus 扩展:临时小批量窗口,CPU 活跃期有效)。
|
||||
|
||||
## M5 · 上游能力跟进(不可控)
|
||||
|
||||
以下能力当前 wasm 无 API,只能跟进 esp-emulator 新版本(升级流程见 UPGRADE.zh-cn.md):
|
||||
|
||||
| 能力 | 依赖的上游接口 | 现状 |
|
||||
|---|---|---|
|
||||
| 硬件 I2C 设备模型 | i2c slave/设备桥 API(仅 CLI `--i2c-dev`,且只有哑寄存器) | ❌ |
|
||||
| 硬件 SPI 外设(SD 卡等) | spi 设备桥 | ❌ |
|
||||
| SPI/RGB565 显示屏 | spi/lcd_cam(外设本身未模拟) | ❌ |
|
||||
| 真触摸外设 | touch 外设模拟 | ❌(触摸按键以 GPIO 语义代替) |
|
||||
| ADC(电位器等) | adc 模拟 | ❌ |
|
||||
| I2S 音频 | i2s 模拟 | ❌ |
|
||||
| UART1 外设 | wasm 暴露 uart1(CLI 有 `--uart1-tcp`) | ❌ |
|
||||
| 状态快照保存/恢复 | save/load API | ❌ |
|
||||
|
||||
## 维护项
|
||||
|
||||
- `update_esp_emu_browser.bat`(在 `_tools`,当前为空):按 UPGRADE.zh-cn.md 流程脚本化。
|
||||
- `peris/mmio/`:补 C5/C6/H2/P4/S3 地址表(从各自 IDF soc 头文件提取,方法同 M0)。
|
||||
+37
-22
@@ -1,29 +1,44 @@
|
||||
# 跟进上游 esp-emulator / esp-emu-browser 升级
|
||||
**English** | [简体中文](UPGRADE.zh-cn.md)
|
||||
|
||||
## 锁定版本
|
||||
# Tracking upstream esp-emulator / esp-emu-browser upgrades
|
||||
|
||||
当前基线:**esp-emulator v0.51.0**(`_tools/esp-emu-browser/www/pkg`,wasm-bindgen `--target web`)。
|
||||
## Pinned baseline
|
||||
|
||||
## 升级步骤
|
||||
Current baseline: **esp-emulator v0.51.0** (`_tools/esp-emu-browser/www/pkg`,
|
||||
wasm-bindgen `--target web`).
|
||||
|
||||
1. 下载新版 wasm 包(如 `esp-emu-<ver>-wasm.tar.gz`)解压出新的 `www/`
|
||||
(`_tools` 下已有 `update_esp_emu_browser.bat`,目前为空,可按此逻辑补全)。
|
||||
2. **先跑 M0 探针**:`node tests/m0_probe.mjs <新固件路径不动,直接对 pkg>`。
|
||||
探针直接 import 新 `pkg/esp_emu.js`,六项结论(Q1a/Q1b/Q2a/Q2b/Q3a/Q3b)若有变化,
|
||||
先更新 `docs/FEASIBILITY.md` 与 `peris/mmio/` 地址表,再动其它代码。
|
||||
3. **diff 官方 worker.js**:
|
||||
## Upgrade steps
|
||||
|
||||
1. Download the new wasm package (e.g. `esp-emu-<ver>-wasm.tar.gz`) and
|
||||
unpack the new `www/` (`_tools` already has
|
||||
`update_esp_emu_browser.bat`, currently empty — fill it following this
|
||||
procedure).
|
||||
2. **Run the M0 probe first**: `node tests/m0_probe.mjs <firmware path
|
||||
unchanged; run straight against the pkg>`. The probe imports the new
|
||||
`pkg/esp_emu.js` directly; if any of the six conclusions
|
||||
(Q1a/Q1b/Q2a/Q2b/Q3a/Q3b) changed, update `docs/FEASIBILITY.md` and the
|
||||
`peris/mmio/` address tables before touching other code.
|
||||
3. **Diff the official worker.js**:
|
||||
`diff <new_www>/worker.js peris/worker.js`
|
||||
我们对原文件的改动全部带 `peris:` 注释,逐块把上游新逻辑移植进 `peris/worker.js`:
|
||||
- 新增/变更的消息类型 → 同步到 peris/worker.js 与 peris/app.js 的 switch
|
||||
- 运行循环(run_batch/throttle/restart)变化 → 保持 `peris.onBatchEnd()` 钩子在每批之后
|
||||
4. **diff 官方 app.js / index.html**:页面功能(终端/图表/寄存器/内存面板)改动按需
|
||||
移植进 `peris/app.js` 与 `esp-emu-peris.html`(我们的文件是它们的超集复刻)。
|
||||
5. 若 `WasmEmulator` API 变化(新增 GPIO/PWM/RMT 接口!),优先改用官方 API 简化设备实现——
|
||||
那正是 ROADMAP.md 里等的东西。
|
||||
6. GUI 冒测(加载固件、LED/按键、配置导入导出),提交。
|
||||
All our changes to that file carry `peris:` comments; port the new
|
||||
upstream logic block by block:
|
||||
- new/changed message types → sync into the worker and the page's message
|
||||
routing switch
|
||||
- run-loop changes (run_batch/throttle/restart) → keep the
|
||||
`peris.onBatchEnd()` hook after every batch
|
||||
4. **Diff the official app.js / index.html**: port page features
|
||||
(terminal/chart/register/memory panels) into our UI modules and
|
||||
`esp-emu-peris.html` as needed (our files are superset replicas of
|
||||
theirs).
|
||||
5. If the `WasmEmulator` API changed (new GPIO/PWM/RMT interfaces!), prefer
|
||||
the official API to simplify device implementations — that is exactly
|
||||
what the roadmap has been waiting for.
|
||||
6. Manual GUI pass (firmware load, LED/button, config import/export),
|
||||
commit.
|
||||
|
||||
## 需要盯的上游能力(详见 ROADMAP.md)
|
||||
## Upstream capabilities to watch (see ROADMAP.md)
|
||||
|
||||
- GPIO 之外的外设 API(i2c/spi 设备模型、rmt 回调、touch、adc)
|
||||
- 事件回调机制(目前只能轮询)
|
||||
- 浏览器包的 UART1 / 快照保存
|
||||
- Peripheral APIs beyond GPIO (i2c/spi device models, rmt callbacks, touch,
|
||||
adc)
|
||||
- An event-callback mechanism (today it is polling only)
|
||||
- UART1 / snapshot saving in the browser package
|
||||
@@ -0,0 +1,31 @@
|
||||
[English](UPGRADE.md) | **简体中文**
|
||||
|
||||
# 跟进上游 esp-emulator / esp-emu-browser 升级
|
||||
|
||||
## 锁定版本
|
||||
|
||||
当前基线:**esp-emulator v0.51.0**(`_tools/esp-emu-browser/www/pkg`,wasm-bindgen `--target web`)。
|
||||
|
||||
## 升级步骤
|
||||
|
||||
1. 下载新版 wasm 包(如 `esp-emu-<ver>-wasm.tar.gz`)解压出新的 `www/`
|
||||
(`_tools` 下已有 `update_esp_emu_browser.bat`,目前为空,可按此逻辑补全)。
|
||||
2. **先跑 M0 探针**:`node tests/m0_probe.mjs <新固件路径不动,直接对 pkg>`。
|
||||
探针直接 import 新 `pkg/esp_emu.js`,六项结论(Q1a/Q1b/Q2a/Q2b/Q3a/Q3b)若有变化,
|
||||
先更新 `docs/FEASIBILITY.zh-cn.md` 与 `peris/mmio/` 地址表,再动其它代码。
|
||||
3. **diff 官方 worker.js**:
|
||||
`diff <new_www>/worker.js peris/worker.js`
|
||||
我们对原文件的改动全部带 `peris:` 注释,逐块把上游新逻辑移植进来:
|
||||
- 新增/变更的消息类型 → 同步到 worker 与页面的消息路由 switch
|
||||
- 运行循环(run_batch/throttle/restart)变化 → 保持 `peris.onBatchEnd()` 钩子在每批之后
|
||||
4. **diff 官方 app.js / index.html**:页面功能(终端/图表/寄存器/内存面板)改动按需
|
||||
移植进我们的 UI 模块与 `esp-emu-peris.html`(我们的文件是它们的超集复刻)。
|
||||
5. 若 `WasmEmulator` API 变化(新增 GPIO/PWM/RMT 接口!),优先改用官方 API 简化设备实现——
|
||||
那正是路线图等的东西。
|
||||
6. GUI 冒测(加载固件、LED/按键、配置导入导出),提交。
|
||||
|
||||
## 需要盯的上游能力(详见 ROADMAP.zh-cn.md)
|
||||
|
||||
- GPIO 之外的外设 API(i2c/spi 设备模型、rmt 回调、touch、adc)
|
||||
- 事件回调机制(目前只能轮询)
|
||||
- 浏览器包的 UART1 / 快照保存
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "esp-emu-peris",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Simulated peripherals for the esp-emulator browser (wasm) build",
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
* Project version (keep in sync with package.json and docs/CHANGELOG.md).
|
||||
* Device plugins carry their own per-device version in Device.version.
|
||||
*/
|
||||
export const PERIS_VERSION = '0.5.0';
|
||||
export const PERIS_VERSION = '0.5.1';
|
||||
Reference in new issue
Block a user