Turn the demo app into a Canon 450D intervalometer
Some checks failed
CI / build-and-deploy (push) Has been cancelled

Replaces the gPhoto2 demo UI (live view beside the raw config tree) with a
timelapse intervalometer. The home screen carries only what changes between
runs - interval, frame count, start delay, start/stop, countdown, progress
and a log - while every camera setting moves into a settings drawer.

- intervalometer.js: schedules frames on an absolute grid (start + n *
  interval) so transfer time doesn't accumulate as drift over a long run. An
  overrun logs and fires as soon as the camera is free rather than dropping a
  frame; three consecutive failures abort.
- storage.js: frames stream into a folder via the File System Access API,
  named for capture time (20260801-172713_00001.JPG) so sorting by name is
  sorting by time. Falls back to downloads. Also holds the screen wake lock,
  since background tabs get their timers throttled.
- config-utils.js: config tree lookups, shutter speed parsing, and bulb
  capability detection (bulb toggle or Canon eosremoterelease).
- home.js: sequence controls plus a read-only exposure readout and pre-flight
  warnings when the interval can't fit the exposure and transfer.
- settings.js / index.js: app prefs and the full config tree behind a drawer.
  Config polling now only runs while that drawer is open, leaving the USB link
  to the captures during a sequence.

Live view stays up between frames and steps aside only while the shutter
fires, which is what the EOS driver requires; it recovers afterwards with
backoff instead of hammering a busy camera.

Deployed as an assets-only Cloudflare Worker. The WASM is built with pthreads
and allocates a shared WebAssembly.Memory, so _headers reproduces the COOP/COEP
pair from serve.json - without cross-origin isolation the app fails to start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK7PKpRVoy69Fpa32R8abb
This commit is contained in:
Jon
2026-08-01 18:47:04 +01:00
parent ec3f4462b1
commit 1af4b215b2
15 changed files with 2405 additions and 208 deletions

View File

@@ -1,9 +1,20 @@
type AppState =
| { type: 'CameraPicker' }
| { type: 'Status'; message: string }
| {
type: 'Config';
config: import('web-gphoto2').Config;
capturePreview: (() => Promise<Blob>) | undefined;
triggerCapture: (() => Promise<File>) | undefined;
};
type LogEntry = {
id: number;
message: string;
kind: 'info' | 'warn' | 'error';
at: number;
};
type AppState = {
/** Which top-level screen is showing. */
view: 'status' | 'picker' | 'ready';
/** Shown while `view` is 'status'. */
message?: string;
config?: import('web-gphoto2').Config;
supportedOps?: import('web-gphoto2').SupportedOps;
prefs: import('./settings.js').Prefs;
seq: import('./intervalometer.js').SequenceState;
log: LogEntry[];
settingsOpen: boolean;
singleShotStatus: 'idle' | 'busy';
};