Skip to content

Add Shelly Gen 1 device wizard#602

Merged
akadlec merged 8 commits intomainfrom
claude/hopeful-mendel-73bd10
May 1, 2026
Merged

Add Shelly Gen 1 device wizard#602
akadlec merged 8 commits intomainfrom
claude/hopeful-mendel-73bd10

Conversation

@akadlec
Copy link
Copy Markdown
Contributor

@akadlec akadlec commented Apr 30, 2026

Summary

  • Adds a discovery wizard to the shelly-v1 plugin, mirroring the existing shelly-ng wizard so Gen 1 devices can be adopted through the same three-step flow (Discovery → Categories → Results)
  • Reuses the main connector's already-running CoAP/mDNS browser via a new subscribeToAddedDevice() / getKnownDevices() pair on ShelliesAdapterService — no parallel network listeners
  • Supports manual hostname + password entry for protected devices, with HTTP Basic auth validation against /status

Implementation notes

Backend (apps/backend/src/plugins/devices-shelly-v1/)

  • services/shelly-v1-discovery.service.ts — session lifecycle (30s scan window, 5min finished-TTL), seed from getKnownDevices() + live subscription, manual lookup probes /shelly then validates auth via /status
  • services/shellies-adapter.service.ts — added getKnownDevices() and subscribeToAddedDevice() with cross-restart subscriber retention
  • controllers/shelly-v1-devices.controller.ts — new endpoints: POST /discovery, GET /discovery/{id}, POST /discovery/{id}/manual
  • models/shelly-v1.model.ts + models/shelly-v1-response.model.ts — discovery device / session models and response wrapper, registered in devices-shelly-v1.openapi.ts
  • Identifier built as uppercase MAC without separators to match the shellies library's device.id format used by auto-discovery

Admin (apps/admin/src/plugins/devices-shelly-v1/)

  • composables/useDevicesWizard.ts — session polling (1s), client-side scan-progress ticking that's resilient to clock skew, race-resilient adopt with create→update fallback, opt-in updates for already-registered devices
  • components/shelly-v1-devices-wizard.vue — three-step wizard with sortable categories table, select-all checkbox, and table-format results step
  • Schemas / types / transformers extended for the new discovery payloads
  • Locales updated for en-US, cs-CZ, de-DE, es-ES, pl-PL, sk-SK

Test plan

  • Backend: pnpm --filter ./apps/backend exec jest src/plugins/devices-shelly-v1 — 105/105 pass locally
  • Admin: pnpm --filter ./apps/admin run test:unit — 1330/1330 pass locally
  • Lint + type-check clean for both apps
  • Manual: open Devices → wizard/devices-shelly-v1-plugin with a Gen 1 device on the LAN and confirm it appears via mDNS, can be adopted with category, and shows up in the devices list
  • Manual: enter a password-protected device manually with wrong password (expect needs_password), then with the right password (expect ready)
  • Manual: re-run wizard with an already-adopted device (expect already_registered with opt-in update)

🤖 Generated with Claude Code


Note

Medium Risk
Adds new backend discovery-session APIs and a new in-memory discovery service wired into the Shelly V1 connector, plus a new admin wizard flow that can create or update devices; mistakes could affect device adoption/update behavior but changes are scoped to the Shelly V1 plugin.

Overview
Adds a Shelly Gen 1 (V1) discovery/adoption wizard in the admin UI (Discovery → Categories → Results), including polling scan progress, manual hostname/password lookups, per-device selection/category/name editing, and a results table.

Introduces new backend support for the wizard via POST /devices/discovery, GET /devices/discovery/:id, and POST /devices/discovery/:id/manual, backed by a short-lived discovery session service that reuses the existing Shellies connector event stream (new getKnownDevices()/subscribeToAddedDevice()), probes devices over HTTP for enrichment/auth validation, and exposes typed OpenAPI models.

Refactors Shelly V1 descriptor matching into shared findShellyV1Descriptor() and updates the platform/mapper/service to use it, and extends admin schemas/transformers/openapi aliases and i18n strings to support the new discovery payloads.

Reviewed by Cursor Bugbot for commit e5b4a62. Bugbot is set up for automated code reviews on this repo. Configure here.

Mirrors the Shelly NG discovery wizard for Gen 1 devices: scans the
local network via the main connector's CoAP/mDNS browser, supports
manual hostname + password entry, and adopts selected devices in batch.

The wizard shares the proven session-based UX from shelly-ng (30s scan
window, 5min finished-session TTL, client-side progress ticking,
race-resilient adopt with create→update fallback, opt-in updates for
already-registered devices) but speaks the Gen 1 HTTP API (Basic auth
with `admin` username, `/shelly` for identification, `/status` for auth
validation) instead of NG's RPC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@github-actions github-actions Bot added backend Backend app related admin Admin app related labels Apr 30, 2026
@akadlec akadlec self-assigned this Apr 30, 2026
Comment thread apps/admin/src/plugins/devices-shelly-v1/locales/de-DE.json
- Align discovery `findDescriptor` with the substring + name fallback used
  by `device-mapper.service.ts` and `shelly-v1.device.platform.ts`. The
  wizard must agree with the main connector — strict equality would mark
  variants whose `type` carries an extra suffix as `unsupported` even though
  the connector adopts them fine.
- Translate the wizard strings the previous commit left in English in
  `de-DE`, `es-ES`, and `pl-PL` (messages, texts, buttons, statuses).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Arm the finish timer and register the session BEFORE the seed loop so
  the wall-clock session lifetime matches `expiresAt`. The seed loop's
  per-device HTTP probe + DB lookup was pushing the real expiry several
  seconds past `expiresAt`, leaving the frontend's progress bar pinned
  at 100% while polling still reported `running`.
- Drop the unused `passwords` map and `storePassword()` helper. Nothing
  ever read from them — the frontend tracks per-hostname passwords in
  its own reactive store and re-sends them with each adopt request.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Run the seed loop concurrently and don't block the start response on
  it. Each `handleLibDevice` does a 3s HTTP probe, so awaiting them
  sequentially would push the start response past the scan duration with
  even a few unreachable devices in the lib's registry. Per-device
  snapshots still land in `session.devices` as they finish, and the
  frontend's 1Hz polling picks them up. `start()` is now synchronous;
  the controller drops its `await` accordingly.
- Fix `@ValidateNested({ each: true })` on the discovery device's
  `authentication` property — that's a single nested object, not an
  array, so `each: true` was a no-op at best and a bug at worst.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Extract `findDescriptor` to a shared `descriptor.utils.ts` and route the
  three (now four) callers through it: `ShellyV1DiscoveryService`,
  `DeviceMapperService`, `ShellyV1DevicePlatform`, and `ShellyV1Service`.
  All four were doing variants of the same model-substring + name-fallback
  match — divergence was the exact failure mode that surfaced once already
  in this PR's review thread.
- Switch the discovery endpoints to `toInstance(...)` for parity with the
  rest of the V1 controller (`POST /info`, `GET /supported`). The discovery
  models now carry `@Expose({ name: 'snake_case' })` / `@ApiProperty({ name:
  'snake_case' })` aliases on every multi-word field, so the wire format
  matches the snake_case convention the V1 plugin already uses elsewhere.
  The frontend's `snakeToCamel` already handles both shapes, so the change
  is non-breaking.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread apps/admin/src/plugins/devices-shelly-v1/composables/useDevicesWizard.ts Outdated
`addManualDevice` was caching the user's entered password in
`passwordByHostname` whenever the backend returned a session snapshot —
no matter what the inspect step actually said about validity. For an
`already_registered` device, the DB-hit status takes priority over
`needs_password`, so a wrong password against an existing device still
produced `status: 'already_registered'` with `authentication.valid: false`.
A subsequent Adopt click then read the bad password back out and passed
it to `updateRegistered`, overwriting the correct on-disk password with
the wrong one.

Now we only persist the password when the inspect step didn't actively
report invalid credentials. Manual entries against unauthenticated
devices and against existing devices with a correct password still flow
through; bad-password attempts no longer poison the store.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Stop the discovery poll timer when `refreshDiscovery` rejects. The most
  likely cause is a 404 after the backend GC'd the session, and the catch
  block was swallowing the error and letting the 1Hz interval keep firing
  until the component unmounted.
- Tighten the `selectedDevices` filter to reject `undefined`, not just
  `null`. The map is typed `DevicesModuleDeviceCategory | null` but a
  missing key reads as `undefined` at runtime, so the previous `!== null`
  guard could let half-initialized rows through and `adoptSelected` would
  then cast `undefined` straight to `DevicesModuleDeviceCategory` on the
  way to the backend.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit cdf62c3. Configure here.

Comment thread apps/admin/src/openapi.constants.ts
Clicking "Scan again" called `start()` a second time, which built a fresh
`subscribeToAddedDevice` listener on top of the previous one without
canceling the old session. The lib then double-probed every newly-seen
device, the orphan finish-timer kept ticking until it fired, and the
frontend (which only tracks the latest session id) had no view of the
duplicate work.

Now `start()` first walks `this.sessions` and finishes anything still
`running` — clearing its timer, detaching its `add` listener, and
scheduling its 5-minute cleanup — before creating the new session. The
finish path is fire-and-forget because its synchronous parts complete
before the next loop tick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akadlec akadlec merged commit 1152e34 into main May 1, 2026
12 checks passed
@akadlec akadlec deleted the claude/hopeful-mendel-73bd10 branch May 1, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

admin Admin app related backend Backend app related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant