17 Commits
Author SHA1 Message Date
darceye 81f6b60c3a Add a screenshot 2026-11-02 11:34:21 +08:00
darceye 7f5f1aaa88 Logic analyzer probe freedoms: passive pads, any-GPIO trigger, 1 h window (0.11.0)
- passive probes (new `static passive = true` device metadata): a pure
  listener never drives an input and claims no pad, so it may clamp onto
  ANY GPIO - including pads other devices own (an LED, an I2C pair, a
  MAX7219 bus) and other analyzers. The runtime's pin-conflict check and
  the wired-AND arbitration skip passive pads; the panel's usedPins
  ignores them, so other devices' pickers never show them as "in use";
  the analyzer's channel grid never disables a GPIO (pads owned elsewhere
  get a dashed "shared" hint). Replaces the analyzer's sharedBus flag.
- trigger on any GPIO: trigCh (channel index) becomes trigPin (GPIO
  number) - the trigger pad gets its own subscription and need not be an
  analysed channel (off-channel triggers land in no lane, channel levels
  still snapshot at capture start). The window dropdown lists every valid
  GPIO. Tooltip documents the gpio_state visibility caveat: a trigger
  fires only when the FIRMWARE drives the pad - pure-input pads (driven
  by our button devices) never toggle the output registers.
- window cap lifted 1 s -> 1 h of guest time (was a conservative guard;
  dense signals still freeze earlier at the 20 000-edge buffer with the
  buffer-full note - the natural limit, since Saleae streams into host
  RAM while we capture into a fixed buffer).
- tests: runtime-level passive sharing (probe onto an LED pad + I2C pair
  + second probe, zero conflicts), off-channel trigger, 1 h clamp,
  trigger cases migrated to trigPin; device bumped to 1.1.0; GUI checked
  (shared chip clickable, 23 trigger options, 5000 ms window accepted).
- docs (both languages): CHANGELOG 0.11.0 + device version table,
  DEVICE-API passive metadata + instrument section, README capability
  row, ROADMAP probe-freedoms bullet, AGENT rule 3.11; version synced to
  0.11.0.
2026-11-02 10:58:17 +08:00
darceye 58452f4031 serve.py self-bootstraps the esp-emulator browser build (0.10.3)
- default vendor www/pkg missing -> serve.py runs tools/fetch_esp_emu.py
  itself (same interpreter, honours https_proxy) and serves afterwards;
  custom --www targets are never auto-fetched, they still error clearly
- dropped the misleading 'npm run build' hints from all error messages
  (the release package has no npm); python tools/fetch_esp_emu.py is the
  single canonical instruction
2026-11-02 10:06:23 +08:00
darceye b5a40dec72 Fresh-install fixes + runtime-only release package (0.10.2)
- m1_smoke/m0_probe: skip with instructions when the gitignored ESP-IDF
  firmware artifact is missing (fresh clone npm test crashed with ENOENT);
  explicit firmware paths still hard-fail
- package_release.py: runtime allowlist — entry html, build/, peris/,
  examples/, serve.py, install scripts + fetch tool, LICENSE, READMEs
  (sources/tests/docs stay in the repo; 122 -> ~60 files)
- preserve hook: npm run serve fetches the browser build first
2026-11-02 09:56:38 +08:00
darceye 74e87e892d install.sh/bat: skip non-working Windows Store python3 stub (verify interpreter) 2026-11-02 09:39:38 +08:00
darceye 81915288f5 Fetch esp-emulator browser build from GitHub releases; release packaging (0.10.1)
- tools/fetch_esp_emu.py: download esp-emu-<ver>-wasm.tar.gz from the
  espressif/esp-emulator release into vendor/esp-emu-browser/www (proxy-aware
  via https_proxy, sha256-verified against SHA256SUMS, cached in
  vendor/downloads/, idempotent, --version to switch baseline)
- npm run build/test trigger the fetch automatically; serve.py, sync_www.py
  and the m0/m1 tests default to the vendored copy (ESP_EMU_WWW/--www
  override) — no machine-local absolute paths left in the dev flow
- install.sh/install.bat + tools/package_release.py (npm run package):
  release archives ship the pre-compiled UI + the esp-emulator fetch script
- docs updated (README/AGENT/UPGRADE/ROADMAP/CHANGELOG, EN+zh)
2026-11-02 09:38:03 +08:00
darceye 9cd5863d36 MAX7219 display + logic analyzer with Saleae-style floating window (0.9.0 + 0.10.0)
0.9.0 - MAX7219 Display (max7219.js v1.0.0, engine in max7219-lib.js):
- 8-digit LED driver on a firmware bit-banged DIN/CLK/LOAD bus (hardware
  SPI stays invisible, M0). Level-driven shift engine samples DIN on the
  CLK rising edge, latches MSB-first 16-bit words on the LOAD rising edge;
  misaligned frames are dropped and counted in `dropped`, never garbled.
  Datasheet register model: Code B decode (independent DP), intensity PWM,
  scan limit, shutdown/display test, power-on defaults. 8x8 matrix and
  7-segment Views auto-selected from the decode-mode register; daisy
  chains share the three pads (sharedBus) and latch their chain position.
  Lease sizing measured against the tightest real firmware (gun 0.1.0,
  delay-free pulses ~20 instructions apart): fine at the scheduler floor
  20, arm 60. Smoke S12 boots the gun 0.1.0 release (machine-local, skips
  when absent) with zero drops; examples/esp32s3-gun.json wires it.

0.10.0 - Logic analyzer (logic-analyzer.js v1.0.0, logic in la-lib.js):
- Multi-channel GPIO waveform capture: up to 8 pads, channel+edge or
  immediate trigger, 1-1000 ms guest-time window, auto re-arm, Z shown
  when the pad is released; rides the M4 scheduler (arm 1000, fine lease
  per resolution preset 20/100/300, renewed per edge); 20 000-edge buffer
  freezes with an overflow flag. sharedBus makes it a pure tap on other
  bus devices' pads (smoke S13 arms it on the P4 soft-I2C bus: SCL 1950 /
  SDA 352 alternating edges in a 40 ms window, frozen afterwards).
- Floating analyzer window: draggable, resizable, non-modal popup
  (position fixed, no backdrop - the page underneath keeps every pointer
  event; geometry clamped + persisted). One drawWaveform painter feeds
  the popup, the details mini-preview and the row icon (AGENT 3.8);
  channels picked from a GPIO grid, occupied pins greyed.
- Saleae-style plot interactions: wheel zoom anchored at the cursor (1 ns
  floor), left/middle-drag pan, double-click/F fit; hover measurement
  shades the pulse and reads level + width + period + frequency + PWM
  duty (rise-to-rise cycle); A/B cursors with delta-t and 1/delta-t;
  lane selection + </> buttons and arrow keys jump view-centre onto the
  previous/next edge; lane gutter shows the right-edge level; a fresh
  capture re-fits the view and drops cursors (capId).
- Device API v1 additions (backwards compatible): ctx.pinLevel(pin)
  snapshot read of the driven level; ViewHost.pinInfo() for multi-pin
  pickers; View.destroy() teardown on row removal / list rebuild so Views
  owning DOM outside their row (floating windows) clean it up.
- Tests: la-lib units (parsePins, levelAt, axis ticks, fit span, pixel
  transform, zoom/pan view model, pulseAround, pwmOf, fmtHz/levelChar),
  the capture state machine via fake ctx (immediate/triggered starts,
  direction filtering incl. Z-as-falling, window end, stop/clear/auto,
  buffer overflow, onEmuReady reset, capId), sigbus.pinLevel against a
  fake emulator; smoke S13 + S1 grows to 12 devices;
  examples/esp32c3-probe.json wires the analyzer on the I2C pair.
- Docs (both languages): CHANGELOG 0.9.0 + 0.10.0 + device version table,
  ROADMAP shipped sections, DEVICE-API instrument section + new ctx/host/
  View members, ARCHITECTURE GUI checklist + floating-window note,
  README capability table + tree, AGENT rule 3.11 + GUI-smoke bug-table
  rows; version synced to 0.10.0 in all three places.
2026-11-02 00:05:48 +08:00
darceye 1da996e657 Probe firmware P1b digital-in sampling + all-device importable example (0.8.1) 2026-10-31 16:23:09 +08:00
darceye 812acbadd6 M4 I2C slave + IR NEC: sampling scheduler, bit-bang bus devices, shared-bus wiring (0.8.0) 2026-10-31 15:55:45 +08:00
darceye 3458a9018a M2 PWM/RGB LEDs: LEDC register-level devices with auto-discovered wiring (0.6.0)
- pwm-led.js + rgb-led.js: duty read from the LEDC DUTY_R register every
  batch (exact, no aliasing - M0 Q2b path; pad-level PWM never reaches the
  GPIO registers). Channel->pin wiring auto-discovered from the GPIO-matrix
  FUNCn_OUT_SEL_CFG registers and followed across firmware rewiring; manual
  channel selection too. RGB View mixes the three channels additively.
- Smooth breathing rendering: ~30fps state pushes are interpolated on a
  canvas; one rAF animator per plugin repaints bulb + row icon through a
  single _apply path (AGENT 3.8)
- Device API v1 additions (backwards compatible): optional onBatch() tick
  after every run_batch, ctx.mmio (chip register tables via mmioFor); C3
  table gains LEDC timer conf addresses + full ch0-5 signal ids (45-50);
  shared device-side logic in peris/devices/ledc-lib.js (outside the
  manifest, imported by both plugins and the tests)
- Test firmware: P2 now drives LEDC ch1 (GPIO6, mirrored ramp) + ch2
  (GPIO7, half duty) alongside ch0; ###P2 markers unchanged, all six M0
  conclusions re-verified. New tests/firmware/merge.ps1 (merge-bin -o is
  relative to the build dir)
- Tests: fake-MMIO unit tests (DUTY_R decode across resolutions/timers,
  matrix scan, auto/rebind, render math); smoke S7/S8 on real firmware
  (auto-binding + duty tracking incl. mirrored r+g sum); terminal banner
  padding adjusted for the version tag
- Docs (both languages): ROADMAP M2 -> shipped, CHANGELOG 0.6.0 + device
  version table, DEVICE-API onBatch/ctx.mmio + register-level section,
  ARCHITECTURE data flow, README capability table, AGENT rule 3.9 +
  gotchas, FEASIBILITY follow-up note; examples/esp32c3-probe.json gains
  the two LEDC devices; version synced to 0.6.0
2026-10-30 23:36:25 +08:00
darceye 9284a9c480 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
2026-10-30 21:33:38 +08:00
darceye 8b1855b229 Chip auto-restore from cache + compact icon toolbar (0.5.0)
- localStorage config now carries chip; on load the panel re-selects the
  Target chip (select + machine info), so the GPIO grid / register table /
  pin options match the wiring without picking the chip again; unknown
  chip names are ignored safely
- Panel head merged into one row: PERIPHERALS title + project-name input
  + save/open icon buttons (Export/Import); toolbar reduced to the add
  select + icon-only Add button
- PerisPanel construction moved before initGpioDefaults so the restored
  chip feeds the initial debug panels; deps gain selectChip(chip)
- Docs: CHANGELOG 0.5.0, DEVICE-API config section, README
2026-10-30 21:18:06 +08:00
darceye cf6b7d54de Export enhancements: custom file name + firmware provenance in JSON (0.4.0)
- Peripherals toolbar second row: export file name input (empty = auto
  peris-{chip}-{date}.json); illegal chars stripped, single .json suffix;
  remembered in localStorage (peris.exportName.v1); pure exportFileName()
  unit-tested
- Exported JSON now records firmware {name, size, path?} alongside chip;
  browsers do not expose disk paths, path filled only when the host
  provides non-standard File.path/filePath; import ignores it
- app.ts tracks last loaded firmware info, clears on firmware-less reset
- Docs: DEVICE-API config section, CHANGELOG 0.4.0, README
2026-10-30 20:51:19 +08:00
darceye 293d76acf7 Row-based peripherals UI (0.3.0) + same-logic reuse fixes (0.3.1)
- Three-pane layout: console | peripherals | debug, all visible at once
- One compact row per device: interactive icon (View.rowIcon), inline
  name/GPIO/V-G pull chip editing, expand/remove/drag-sort; details area
  holds the device view + config editor (one property per line)
- View API: optional rowIcon(el) + host.updateConfig(patch); schema
  chip:'pull'/pullWhenChecked declare the V/G badge
- Reuse fixes: single LED setColour for bulb+row dot, panel calls
  rowIcon before setConfig; fillPinOptions() shared by editor and row
  chip, editors rebuilt on chip change (refreshForChip)
- AGENT.md: new rule 8 (reuse same-semantic code) + two bug records;
  npm test passes explicit file list (Git Bash dir-resolution quirk)
- Devices: led/button/touch-button/toggle-switch 1.1.0 (led 1.1.1)
2026-10-30 19:37:34 +08:00
darceye e29f2e81e6 Build layout, versioning, and knowledge docs
- Build output moved to build/ (tsc outDir; worker-client, registry
  DEVICES_DIR, html script src, tests, sync_www ITEMS updated; build/
  gitignored, stale peris/core+ui removed)
- Version management: src/core/version.ts (project, 0.2.0) shown in the
  header and terminal banner; per-device Device.version validated by the
  registry and shown on card headers; docs/CHANGELOG.md records both
  (project + device table)
- docs/EMU-API.md: full reverse-engineered summary of the closed-source
  esp_emu_bg.wasm surface (lifecycle, execution, CPU state, GPIO/UART/
  WiFi, M0-verified MMIO facts, stock worker protocol, CLI-only gaps)
- AGENT.md: environment pitfalls (mirrors, idfm.bat/MSYSTEM, merge-bin),
  emulator behavior facts, architecture invariants, past-bug table,
  test conventions and versioning rules for future agents
- README/ARCHITECTURE/DEVICE-API updated for the new layout
2026-10-29 22:57:37 +08:00
darceye ca0432dabd Refactor: layered TypeScript architecture + test pyramid
Architecture (docs/ARCHITECTURE.md):
- Framework (worker host + page UI) rewritten in TypeScript under src/,
  compiled per-file to peris/core + peris/ui (tsc, no bundler; ESM layout
  unchanged so the drop-into-www deployment model still works)
- Monolithic app.js split into 7 UI modules: app orchestrator,
  worker-client (sole Worker owner), terminal, chart, debug-panels,
  peris-panel, schema-driven config-editor; device plugins stay plain JS
  (drop-in model) with runtime meta validation in the registry
- peris-specific CSS extracted to peris/peris.css; upstream style block
  stays verbatim in the HTML for easy re-diff
- chips/MMIO tables single-sourced in TS data modules

Type contracts (src/core/types.ts): DeviceMeta/DeviceLike/DeviceCtx/
PinState/PerisConfig + normaliseConfig + validateDeviceModule, shipped
as .d.ts for plugin authors

Tests:
- tests/unit (node:test, zero deps): sigbus bit dispatch, chips/mmio
  tables, config normalisation, module validation, device electrical
  semantics (button active-low levels, LED invert, toggle persistence)
- integration smoke + M0 probe unchanged in role, wired into npm scripts

Bug found & fixed by the refactor: throttled flushStates silently
dropped the last dirty device state when changes stopped inside the
33 ms window - now schedules a trailing flush so the page always
converges

npm run build/check/test/serve/sync (typescript via npmmirror)
2026-10-29 22:28:27 +08:00
darceye a9d26722d7 M0+M1: feasibility probe and peripherals framework
M0 (docs/FEASIBILITY.md, verified against esp-emulator wasm v0.51.0):
- GPIO outputs observable via gpio_state(); gpio_set_input() drives pads
  and raises guest interrupts (button-class peripherals work)
- LEDC PWM and RMT waveforms are NOT visible in the pad registers; duty
  is readable from LEDC registers and WS2812 frames decode from RMT
  channel RAM (per-chip MMIO map in peris/mmio/)
- Node probe + ESP-IDF test firmware (gpio/ledc/rmt three-phase loop)

M1 peripherals framework:
- esp-emu-peris.html entry (stock layout + Peripherals/Debug tabs),
  peris/worker.js (stock worker clone + per-batch pin sampling hooks)
- device plugin API v1 (docs/DEVICE-API.md) with manifest registry:
  add a peripheral = one module + one manifest line
- devices: led, button, touch-button, toggle-switch; wiring config
  import/export + localStorage autosave; pin-conflict reporting
- serve.py merged dev server, sync_www.py publisher (www only gains
  files, stock ones untouched), xterm vendored locally (no CDN)
- tests/m1_smoke.mjs integration smoke (config/LED state/button ISR/
  toggle/conflict/unknown-type) - all green
2026-10-29 19:22:45 +08:00