Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ jobs:
cache: npm

- run: npm ci
- run: npm run lint
- run: npm run build

test:
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
with:
node-version: 20
registry-url: https://registry.npmjs.org
cache: npm

- name: Verify tag matches package.json version
run: |
Expand All @@ -28,9 +29,6 @@ jobs:
fi

- run: npm ci
- run: npm run lint
- run: npm run build
- run: npm test

- name: Publish (beta)
if: github.event.release.prerelease == true
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ re/
node_modules/
dist/
coverage/
*.tsbuildinfo
*.tgz
*.log
.DS_Store
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A [Homebridge](https://homebridge.io) plugin that brings [June](https://juneoven
- **Optional cook doorbell** — a HomeKit Doorbell that rings on the events you choose (cook done and/or preheat-ready), giving you a doorbell-style notification on your phone and Apple TV. Off by default; enable and pick triggers in the Config UI.
- **Optional food-probe temperature sensor** — exposes the oven's food-probe temperature as a HomeKit Temperature Sensor so you can automate on "probe reached 145°F." Off by default.
- **Optional cook-mode switches** — add a switch for any cook mode you like (bake, roast, broil, air fry, toast, or any other mode id the oven accepts), each with its own temperature. Turning one on starts that cook; they're mutually exclusive. None are added unless you configure them.
- **Optional interior camera** — shows the oven's interior camera (a still refreshed ~1×/sec while cooking, which is the oven's native feed). Snapshots work out of the box; tap-to-view live streaming uses system `ffmpeg` if present. Enable the camera *and* the doorbell together to get a **Video Doorbell**, so the "cook done" notification arrives with a photo of your food. Off by default.
- **Optional interior camera** — shows the oven's interior camera (a still refreshed ~1×/sec while cooking, which is the oven's native feed). Snapshots work out of the box; tap-to-view live streaming uses system `ffmpeg` if present. Enable the camera *and* the doorbell together to get a **Video Doorbell**, so the "cook done" notification arrives with a photo of your food. Off by default. Because HomeKit only renders cameras on *external* accessories, the camera / video-doorbell is published as a standalone accessory: after enabling it, add it in the Home app with **Add Accessory → More options…** using the same Homebridge PIN.
- **In-plugin pairing** — pair from the Config UI with an 8-digit code, the same flow the June app uses. No account credentials are ever entered into or stored by the plugin.
- **Editable per-oven settings in the Config UI** — rename accessories, toggle sensors, set default cook mode/temperature (in whichever unit you prefer), and check an oven's live connection status, all without hand-editing `config.json`.
- **Automatic token refresh** and a persistent, signed WebSocket connection to June's messaging service for live status and commands.
Expand Down
6 changes: 3 additions & 3 deletions docs/superpowers/plans/2026-07-08-doorbell-probe-polish.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

---

### Task 1: Remove Unsupported Door-Open Trigger
## Task 1: Remove Unsupported Door-Open Trigger

**Files:**
- Modify: `src/protocol.ts`
Expand Down Expand Up @@ -42,7 +42,7 @@ Remove `doorOpen` from `config.schema.json` properties and form list. Remove sta
Run: `npm test -- src/protocol.test.ts`
Expected: PASS.

### Task 2: Singular Probe Accessory and Legacy Cleanup
## Task 2: Singular Probe Accessory and Legacy Cleanup

**Files:**
- Modify: `src/platform.ts`
Expand Down Expand Up @@ -70,7 +70,7 @@ In `src/platform.ts`, ensure the previous UUID generated from `${client.config.o
Run: `npm test -- src/platform.test.ts`
Expected: PASS.

### Task 3: Full Verification
## Task 3: Full Verification

**Files:**
- No new files beyond Tasks 1-2.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Expanded HomeKit Features Implementation Plan

> **Historical plan:** This records the initial implementation sequence, including protocol assumptions that were later replaced by live captures. The shipped contract is documented in the expanded-features design and README. Camera and ffmpeg streaming were added after the original scoped tasks once Spike A confirmed the `10011` still-image feed.
>
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Add opt-in cook-done doorbell, food-probe temperature sensors, and config-driven cook-mode switches to `homebridge-june-oven`, and document why timer/progress are intentionally omitted.
Expand All @@ -13,7 +15,7 @@
- Node floors: `18.20.4+`, `20.19.0+`, `22.12.0+`, or `24+`. Homebridge `>=1.8.0`.
- Every new feature is **opt-in**, default off (doorbell) or empty (modes, probes). Upgrading an existing install must change no behavior.
- Temperature on the wire is milli-°C; convert with existing helpers (`fahrenheitToMilliC`, `milliCToCelsius`). HomeKit temperature characteristics are Celsius.
- No new runtime dependencies. Camera/streaming/ffmpeg are OUT of scope for this plan (deferred to Spike A — see spec).
- No new npm runtime dependencies. Camera/streaming/ffmpeg were deferred from the original task sequence to Spike A, then delivered in the same feature branch after the camera feed was confirmed.
- Test runner: `npm test` (vitest run). Type-check: `npm run lint` (`tsc --noEmit`).
- Commit after each task with a `feat:`/`docs:` message.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# June Oven — Expanded HomeKit Features Design

**Date:** 2026-07-08
**Status:** Approved design, pending implementation plan
**Status:** Implemented; updated to match the shipped contract

## Goal

Expand All @@ -11,9 +11,9 @@ is **opt-in** via the Config UI (default off) so existing users see no behavior

Four features, in priority order:

1. **Cook-done doorbell** — ships as a Doorbell, and becomes a Video Doorbell when the interior
camera is also enabled. Offered as an option alongside the existing ready/done sensors; users can
enable either, both, or neither.
1. **Cook-event doorbell** — rings for configured cook-done and/or preheat-ready events. It can
operate as a plain Doorbell or combine with the interior camera as a Video Doorbell. Offered
alongside the existing ready/done sensors; users can enable either, both, or neither.
2. **Interior camera** (snapshot + ffmpeg live view) — BUILT. Spike A solved; snapshot verified
live end-to-end. Combines with the doorbell into a Video Doorbell.
3. **Food probe temperature sensor**
Expand Down Expand Up @@ -63,8 +63,6 @@ frida, or app login required; our plugin's existing WebSocket already receives i
`10013` `sensor_data.probe` is an **array** of `{ id: "left"|"right", value: <milli-C> }`. There is
no `food_present` field; probe presence = the array has entries. Confirmed live: cavity `61100`
(142°F) vs. probe `{id:"left",value:18200}` (65°F water). `parseProbeTelemetry` parses this shape.
- **Food-probe temperature fields** in `10013` — confirmed as `sensor_data.probe`, an array of
`{id, value}` entries where `value` is milli-°C.
- **Door-open** is only observable as a command *rejection reason* (`10020 status:"door-open"`),
not as a push event. A live door-state signal is unconfirmed.
- **The full mode list** — only `bake`/`roast` are confirmed on-oven; the `/2/devices/{id}/features`
Expand All @@ -77,40 +75,43 @@ no `food_present` field; probe presence = the array has entries. Confirmed live:
WebSocket during any cook.
- **Spike B — probe field path. ✅ DONE (2026-07-08).** Decoded live; `sensor_data.probe` is an
array of `{id, value}` (see above). `parseProbeTelemetry` updated and unit-tested to match.
## Feature 1 — Cook-done doorbell
## Feature 1 — Cook-event doorbell

A **Doorbell** accessory that fires a `ProgrammableSwitchEvent` (single-press) when a configured
trigger occurs, producing a HomeKit doorbell notification on phone + Apple TV. When the camera is
also enabled, a Camera service attaches to the same accessory so HomeKit treats it as a Video
Doorbell and includes the latest oven still in the notification. It is offered as an option
**alongside** the existing ready/done occupancy sensors; users can enable either, both, or neither.
trigger occurs, producing a HomeKit doorbell notification on phone + Apple TV. It operates as a
plain doorbell when the camera is disabled; enabling the camera attaches it to the same accessory
as a Video Doorbell, so rich notifications can include the interior still. It is offered alongside
the existing ready/done occupancy sensors; users can enable either, both, or neither.

- Triggers are **configurable** (all default off):
- `done` — the cook-complete transition the client already computes (`JuneTelemetry.done`).
- `ready` — preheat-complete / ready-to-load (`JuneTelemetry.ready`).
- Config (per oven): `doorbell` object — `enabled` (bool, default false), `triggers` (object with
`done`/`ready` booleans, all default false), `name`.

Enabling the camera is what promotes plain doorbell to video doorbell; the doorbell can still be
used without the camera for image-less HomeKit notifications.
**Video Doorbell integration is shipped.** When both options are enabled, the Camera is published
as a separate external accessory, requiring users to add it in the Home app with the Homebridge
PIN. The doorbell operates standalone whether the camera is enabled or not.
Comment on lines +92 to +94

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep the camera accessory contract consistent.

Lines 92–94 correctly describe a separately published camera, but Lines 115–116 still say it is implemented as part of the same accessory as the doorbell. Update the later statement so users are not given conflicting pairing instructions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/specs/2026-07-08-june-expanded-homekit-features-design.md`
around lines 92 - 94, Update the later camera integration statement near the
doorbell pairing instructions to consistently describe the camera as a
separately published external accessory, matching the contract established in
the “Video Doorbell integration is shipped” section. Remove or revise wording
that says the camera is part of the same accessory, while preserving the
standalone doorbell behavior and Home app pairing guidance.


## Feature 2 — Interior camera (snapshot-only)
## Feature 2 — Interior camera (snapshot + still-based live view)

A Camera service whose `handleSnapshotRequest` returns the latest interior still as a JPEG. The
JPEG snapshot is the real feature: it drives the camera tile preview and the food photo in the
doorbell's rich notification. **No real video capture and no HKSV.**
JPEG snapshot drives the camera tile preview and the food photo in the doorbell's rich
notification. Tap-to-view live streaming synthesizes H.264/SRTP from the still feed; there is no
continuous source video or HKSV recording.

HomeKit API constraint: hap-nodejs's `CameraController` cannot be registered snapshot-only — it
requires a streaming delegate (`handleStreamRequest`). We satisfy this with a **minimal
HomeKit API constraint: hap-nodejs's `CameraController` requires a streaming delegate
(`handleStreamRequest`). We satisfy this with a **minimal
ffmpeg-from-still stub**: when the user taps the tile to "go live," ffmpeg loops the latest JPEG
into an H.264 stream. This is the only place ffmpeg runs; the still refreshes so it looks live.
There is deliberately no continuous/HKSV recording.

- Snapshot source: the URL learned from Spike A's `10011` frame. The client keeps the most recent
signed still URL from the telemetry stream; the snapshot handler fetches it (with a short cache
to avoid hammering the CDN) and returns the bytes. When no cook is active / no still is
available, return a static placeholder image.
- Config (per oven): `camera.enabled` (bool, default false), `camera.name`, `camera.ffmpegPath`.
trusted still URL from the telemetry stream; the snapshot handler fetches it and returns the
bytes. The live-view path refreshes its input as new still URLs arrive. When no cook is active
or no still is available, the snapshot handler returns a static placeholder image.
- Depends on Spike A. Config (per oven): `camera.enabled` (bool, default false), `camera.name`,
and `camera.ffmpegPath` (defaults to `ffmpeg`; used only for tap-to-view streaming).
- Implemented as part of the same accessory as the doorbell (feature 1) when both are enabled;
camera-only (no doorbell triggers) is also valid.

Expand All @@ -119,8 +120,11 @@ There is deliberately no continuous/HKSV recording.
Expose the oven's food probe temperature as a HomeKit **Temperature Sensor** service, so users
can build automations like "notify when probe reaches 145 °F."

- Source: `10013` telemetry `sensor_data.probe` array from Spike B. The June oven has a single
food probe; the accessory keeps the last reported value between updates.
- Source: `10013` telemetry `sensor_data.probe` array from Spike B. Although entries carry an
`id` such as `left` or `right`, the tested oven exposes one physical food probe; the ID is a
channel label, not evidence of two simultaneously available probes. The parser intentionally
uses the first numeric entry, and the singular accessory keeps that last reported value between
updates.
- Opt-in. Config (per oven): `probeSensors.enabled` (bool, default false). Optionally a
`probeSensors.name` display name.
- New `JuneTelemetry` fields (`probeC`, `probePresent`) populated in
Expand Down Expand Up @@ -149,8 +153,8 @@ mode list can't be predicted or enumerated from the capture.
- `src/june-client.ts`: extend `JuneTelemetry` with probe fields, latest-snapshot-URL state, and
a `ready`-distinct `preheatComplete` if needed; parse probe + camera fields in `handleMessage`;
expose a `startMode(primitiveType, tempF)` helper for mode switches.
- `src/accessories/`: new `doorbell.ts` (Doorbell service now; attaches a Camera service later
when the camera is enabled, becoming a Video Doorbell), `probe-sensor.ts`, `mode-switch.ts`.
- `src/accessories/`: `doorbell.ts` (a Doorbell service that combines with the camera when enabled,
becoming a Video Doorbell), `camera.ts`, `probe-sensor.ts`, and `mode-switch.ts`.
Existing `thermostat.ts`, `preheat-switch.ts`, `sensors.ts` unchanged.
- `src/platform.ts`: construct the new opt-in accessories based on config.
- `config.schema.json`: add `doorbell`, `camera`, `probeSensors`, `modes` per-oven properties and
Expand All @@ -159,7 +163,7 @@ mode list can't be predicted or enumerated from the capture.
## Out of scope

- HKSV / secure video recording.
- Live H.264/HLS streaming.
- Continuous source video or HLS streaming (the live view is synthesized from stills).
- Timer as a HomeKit control (command exists; no natural HomeKit surface — revisit later).
- Cook progress % as a dedicated characteristic (no native HomeKit type; used internally only).

Expand All @@ -168,10 +172,9 @@ command works but HomeKit has no timer/countdown surface for a thermostat-style
cook progress has no native HomeKit characteristic (so it is only used internally to derive
ready/done). This is a documentation deliverable of this work, not a code feature.

## Open risks
## Remaining risks

- The whole camera path hinges on Spike A; if `10011` turns out to deliver something other than a
fetchable still URL, feature 2 (and the doorbell thumbnail) may need rework.
- Door-open trigger may prove impossible; it ships as a no-op toggle if Spike C fails.
- Encrypted HomeKit live streaming still needs end-to-end validation with a real Home hub and iOS
device; snapshot delivery and the oven's `10011` still feed are confirmed.
- Mutually-exclusive mode switches are a HomeKit UX compromise (no native radio group); acceptable
and matches how other appliance plugins model modes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Code Optimization and Hardening Design

**Date:** 2026-07-22
**Status:** Approved for implementation

## Goal

Reduce avoidable network, timer, and stream-resource work while making the
camera and pairing lifecycles explicit enough to prevent races. Preserve the
June wire contract and Homebridge behavior.

## Design

- Extend HTTP request ownership through response-body consumption. Shared
response helpers will enforce size limits and cancel unread error bodies.
- Give camera frame downloads one source-level cache and in-flight request so
snapshots and live sessions share immutable still bytes. Keep stream teardown
idempotent and settle a pending HomeKit start exactly once.
- Replace pairing's deadline-as-liveness sentinel with explicit cancellation
ownership and listener checks so destroyed sessions cannot create eviction
timers or publish terminal state after removal.
- Add bounded, jittered reconnect/startup retry scheduling and clear command
acknowledgement timers when commands settle.
- Make the UI pairing poll self-scheduling, remove its duplicate timeout helper,
and retain the existing configuration behavior.
- Remove proven dead dependencies/exports and redundant release verification,
while retaining small bounded collection operations whose simpler form is
clearer.

## Verification

Each behavior change gets a focused regression test. The final gate is the full
Vitest suite, TypeScript type-check, production build, diff check, and the
GitHub PR checks after pushing.
Loading