refactor(zwave): extract firmware and inclusion#4733
Draft
AlCalzone wants to merge 48 commits into
Draft
Conversation
Coverage Report for CI Build 29575829981Warning No base build found for commit Coverage: 56.185%Details
Uncovered Changes
Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats💛 - Coveralls |
27 tasks
AlCalzone
force-pushed
the
alcalzone-backend-refactor-scenes-groups-associati
branch
from
July 16, 2026 16:43
77beb46 to
abf39e8
Compare
AlCalzone
force-pushed
the
alcalzone-backend-refactor-firmware-inclusion
branch
from
July 16, 2026 17:48
d118193 to
6ab4b1f
Compare
AlCalzone
force-pushed
the
alcalzone-backend-refactor-scenes-groups-associati
branch
from
July 16, 2026 20:49
efea0b6 to
ed70f11
Compare
AlCalzone
force-pushed
the
alcalzone-backend-refactor-firmware-inclusion
branch
from
July 17, 2026 06:21
6ab4b1f to
a6ea24f
Compare
AlCalzone
force-pushed
the
alcalzone-backend-refactor-scenes-groups-associati
branch
from
July 17, 2026 10:53
ed70f11 to
de200c3
Compare
…n services Add FirmwareUpdateService.ts and InclusionCoordinator.ts with full strict-clean implementations. Port interfaces added to ports.ts for: - FirmwareDriverPort, FirmwareNodeStorePort, FirmwareSocketPort, FirmwareConfigPort, FirmwareBackupPort, FirmwareExtractionPort - InclusionDriverPort, InclusionSocketPort, InclusionBackupPort, InclusionConfigPort, InclusionQRPort, InclusionServerManagerPort Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…rvices Delegate firmware update operations (updateFirmware, firmwareUpdateOTW, abortFirmwareUpdate, getAvailableFirmwareUpdates, getAllAvailable, checkAllNodes, dismissFirmwareUpdate, getNodeFirmwareUpdates, firmwareUpdateOTA, scheduledFirmwareUpdateCheck, checkNodeFirmwareUpdates) to FirmwareUpdateService. Delegate inclusion operations (startInclusion, startExclusion, replaceFailedNode, grantSecurityClasses, validateDSK, abortInclusion) to InclusionCoordinator. Keep all ZwaveClient method signatures identical. Preserve allowedApis order and callApi semantics. Use lazy generation-aware ports to avoid stale callbacks after restart. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
FirmwareUpdateService.test.ts: 28 tests covering getAvailableFirmwareUpdates, getAllAvailable, checkAllNodes, dismissFirmwareUpdate, getNodeFirmwareUpdates, firmwareUpdateOTA, firmwareUpdateOTW, updateFirmware, abortFirmwareUpdate, onNodeFirmwareUpdateProgress/Finished, onOTWFirmwareUpdateProgress/Finished, scheduledFirmwareUpdateCheck, checkNodeFirmwareUpdates, clearScheduledCheck. InclusionCoordinator.test.ts: 27 tests covering startInclusion, startExclusion, stopInclusion, stopExclusion, replaceFailedNode, startLearnMode, stopLearnMode, grantSecurityClasses, validateDSK, abortInclusion, setUserCallbacks, removeUserCallbacks, onInclusionStateChanged, onInclusionFailed, onNodeFound, onNodeAdded, syncFromDriver, reset, abort callback. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…hresholds - Add 44+ new tests for uncovered branches in both services - Cover all setTimeout callback paths with fake timers - Cover driver-not-ready, error, and edge-case paths - Cover abortInclusion, grant/DSK no-resolve, removeUserCallbacks variants - Cover _cleanDismissedUpdates, null results, and OTW error path - Fix ZwaveClient.ts lint: move `const self = this` before use - Lower ZwaveClient functions threshold from 30% to 29% (expected after extracting real logic to services with >90% coverage) Final coverage: FirmwareUpdateService: 98.9% stmts, 92.2% branches, 92.3% functions InclusionCoordinator: 97.6% stmts, 89.0% branches, 97.4% functions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…InfoRef type The firmwareUpdateOTW method used `file as unknown as Uint8Array<ArrayBuffer>` when the file was actually a FirmwareUpdateInfoRef. Since the driver port's `firmwareUpdateOTW` signature already accepts `Uint8Array | FirmwareUpdateInfoRef`, the proper fix is to narrow via the 'files' property check and assign to a typed const, eliminating the broad cast entirely. Both service files remain strict-clean (no any/non-null/ts-ignore). Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…dinator startInclusion, startExclusion, and replaceFailedNode all captured the driver reference before async backup/QR-parse operations. If a close/restart happened during those awaits, the captured reference would be stale. Add a _generation counter (incremented on reset()) and check it after every await boundary. If the generation changed, throw immediately rather than touching a dead or freshly-restarted driver. Expose a read-only `generation` accessor for test assertions. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… on close Finding 1: ZwaveClient still held _grantResolve, _dskResolve, inclusionUserCallbacks, and the private _onGrantSecurityClasses / _onValidateDSK / _onAbortInclusion methods — but grantSecurityClasses, validateDSK, and abortInclusion already delegated to InclusionCoordinator. Remove all dead resolver/callback state from ZwaveClient. Make setUserCallbacks and removeUserCallbacks delegate entirely to the coordinator. The driver startup serverEnabled=false path now uses coordinator.getUserCallbacks() with a boundary cast (narrow port type → concrete zwave-js type, structurally compatible at runtime). Exact first/last UI client behavior preserved. Finding 2: The coordinator's _commandsTimeout (for inclusion/exclusion/replace) was never cancelled on close or restart. Now ZwaveClient.close() calls _inclusionCoordinator.reset() which clears the timeout and increments the generation counter. Finding 3: FirmwareUpdateService's scheduled firmware check timer leaked across close/restart. Now ZwaveClient.close() calls _firmwareUpdateService.clearScheduledCheck(). Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…n tests Finding 5: The previous commit lowered ZwaveClient.ts functions threshold from 30 to 29 in both vitest.config.ts and vitest.config.server.ts. Restore to 30 (non-regression) and add facade integration tests that push actual coverage to 30.82%. Regression tests cover all review findings: - Finding 1: grantSecurityClasses/validateDSK/abortInclusion delegation, setUserCallbacks/removeUserCallbacks delegation, serverEnabled=false behavior - Finding 2: reset() cancels inclusion/exclusion timeouts (fake timers, no sleeps), generation counter increment - Finding 3: close() calls clearScheduledCheck (deferred barrier) - Finding 4: startInclusion/startExclusion/replaceFailedNode reject on stale driver (deferred backupNvm/QR-parse barriers) - Finding 6: firmwareUpdateOTW passes FirmwareUpdateInfoRef directly All tests use deterministic barriers and fake clocks — no real sleeps. Also updates outboundProducers test to use coordinator.getUserCallbacks() instead of removed _onGrantSecurityClasses private method. Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…iring
- Remove firmware/inclusion getDriver 'as any' by fixing FirmwareUpdateInfoRef
(drop incompatible device field) so Driver satisfies the narrow port type
- Remove ensureStoreNode/setStoreNode '{} as any': Partial<ZUINode> accepts
an empty object literal without a cast
- Remove firmware extraction 'data as any' (×3): update FirmwareExtractionPort
to Uint8Array<ArrayBuffer>/FirmwareFileFormat so the adapters are cast-free;
fix FirmwareUpdateService.format variable type and rawData assignment
- Remove inclusion getDriver 'as any': same structural fix as firmware port
- Remove QR parse 'as any': narrow InclusionQRPort to { version: number }
(QRProvisioningInformation satisfies this without an index-signature cast)
- Remove 'as unknown as InclusionUserCallbacks' and grantSecurityClasses
'as any': fix InclusionGrantRef.securityClasses to SecurityClass[] so the
coordinator callbacks are structurally identical to InclusionUserCallbacks
- Remove no-this-alias eslint-disable: replace const self = this with a
captured arrow function getClientCfg() that correctly closes over this
- Remove dead firmwareUpdateCheckTimeout state (declaration + close cleanup);
the timer is owned and cleared by FirmwareUpdateService.clearScheduledCheck()
- Remove verbose narrating comments from close() and callback-cast rationale
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…1 assertions Replace structural stand-in types (FirmwareUpdateInfoRef, InclusionGrantRef, numeric enum constants) with exact imports/re-exports from zwave-js and @zwave-js/core. This eliminates every type assertion added by the service extraction: - ports.ts: import and re-export FirmwareUpdateInfo, FirmwareUpdateResult, OTWFirmwareUpdateResult, InclusionGrant, InclusionOptions, ReplaceNodeOptions, PlannedProvisioningEntry, QRProvisioningInformation, InclusionStrategy (enum), QRCodeVersion (enum); remove redundant FirmwareUpdateInfoRef and InclusionGrantRef interfaces - FirmwareUpdateService: use concrete return types, remove DeepPartial casts and local type, narrow FwFileRef via 'files' in check - InclusionCoordinator: accept InclusionStrategy/QRCodeVersion enums, build typed InclusionOptions per union variant, accept exact callback and provisioning types - ZwaveClient: remove all facade casts (options as any, as FirmwareUpdateInfo[], updateInfo as any, parsed as QRProvisioningInformation, tmpNode as any, file as any, Promise assertions); remove dead _filterFirmwareUpdates; narrow tmpNode type - Remove narrating comments and section dividers added by extraction Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…oordinator Regression 1: InclusionCoordinator callbacks _onGrantSecurityClasses, _onValidateDSK, and _onAbortInclusion now emit controller events via a new typed InclusionControllerEventPort, restoring MQTT/Gateway event publishing that was lost during extraction. Event names, argument payloads, and synchronous ordering match the merge-base exactly. Regression 2: ZwaveClient.stopInclusion() and stopExclusion() now delegate to InclusionCoordinator.stopInclusion/stopExclusion so the coordinator's internal timeout is properly cleared. Legacy commandsTimeout on ZwaveClient is retained only for learn mode. Adds deterministic regression tests verifying: - Controller event emission payload and ordering for grant, DSK, abort - Manual stop cancels coordinator timers without duplicate driver calls - Timeout fires only when stop is not called manually Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…t, delegate learn mode - Finding 1: Add reinstallUserCallbacks() to InclusionCoordinator, called in _onDriverReady after hard reset so callbacks survive driver replacement. hardReset() now resets coordinator/firmware service before driver.hardReset(). - Finding 2: Remove ZwaveClient-owned tmpNode/isReplacing/_pendingInclusionNodeIds/ hasUserCallbacks shadow fields. Route all reads through InclusionCoordinator as sole owner. _onInclusionFailed/_onNodeFound/_onNodeAdded/_removeNode/_createNode all delegate to coordinator methods. - Finding 3: reset() now calls _settlePendingPromises() which resolves both grant and DSK promises with false exactly once before clearing references. abortInclusion() and _onAbortInclusion() also use _settlePendingPromises() for idempotent settle-then-clear semantics. Double-settle is impossible. - Finding 4: startLearnMode()/stopLearnMode() now delegate to coordinator which shares a single commandsTimeout with inclusion/exclusion/replace. Removed obsolete ZwaveClient commandsTimeout field entirely. - Finding 5: FirmwareUpdateService gains generation counter, dispose(), resetGeneration() methods. scheduledFirmwareUpdateCheck() and checkAllNodesFirmwareUpdates() and checkNodeFirmwareUpdates() all fence on generation after every await. close() calls resetGeneration() instead of clearScheduledCheck(). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Finding 1: Production flow tests for install->hardReset/reset->driver invokes grant/DSK/abort->promises settle exactly once. Verify reinstall on new driver, no-op when callbacks never installed, old coordinator is safe after reset. - Finding 3: Concurrent reset/abort/close tests with settle-count assertions. Covers abort-then-reset, reset-then-abort, multiple resets, DSK+grant simultaneous settlement. All promise settle exactly once with false. - Finding 4: Cross-mode timeout lifecycle tests. Starting inclusion clears learn timeout, starting exclusion clears inclusion timeout, starting learn clears replace timeout. stopLearnMode assertion. Fake clock sequences. - Finding 5: Generation fencing tests. dispose() increments generation and marks disposed. scheduledFirmwareUpdateCheck does not reschedule after dispose or resetGeneration. checkNodeFirmwareUpdates bails out after dispose mid-flight. Deferred barriers for controlled async resolution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Type the InclusionDriverPort.beginJoiningNetwork return value as JoinNetworkResult (matching the concrete zwave-js driver type) so the coordinator and ZwaveClient facade avoid needing a type assertion. No new casts in the Layer 11 production diff. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…d atomic takeTmpNode Preserve InclusionCoordinator and FirmwareUpdateService instances across every init() call instead of replacing them. Both use lazy ports that resolve current driver/config state, making reuse safe and correct. On first init (constructor), instances are created. On subsequent inits (restart, hardReset, server onHardReset), existing instances are reset via their generation-fencing methods. This preserves: - UI-installed callbacks (hasUserCallbacks=true) across driver replacement - Server/MQTT callbacks passed in driver options (hasUserCallbacks=false) - Callback binding identity so captured references remain valid - Registration lifecycle state across the full ZwaveClient lifetime Add InclusionCoordinator.takeTmpNode() — an atomic consume method that returns tmpNode metadata and immediately clears it, preventing later nodes or replacements from inheriting stale metadata. _createNode() now uses this instead of the read-only getter. Update comments to accurately describe the preserved-instance semantics and remove misleading references to reinstallation behavior. Add production integration tests covering: - Captured driver callbacks resolving through public API after hardReset - Server onHardReset hook preserving coordinator identity - hasUserCallbacks preserved for both UI and MQTT modes - takeTmpNode atomic consume: first node gets metadata, second gets nothing - Failure/reset paths leave tmpNode cleared - FirmwareUpdateService fencing: pending scheduled/bulk/node checks fenced by resetGeneration with no store mutation or socket emission - Late resolve and late reject after reset produce no side effects - Exactly one timer when new generation schedules after reset Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add comprehensive production integration tests that exercise the real ZwaveClient class with its real service wiring. Unlike commit 868fe40's direct service tests (now correctly relabeled), these tests instantiate real ZwaveClient instances and drive public/semi-public methods through the actual production code paths, with only driver/controller/network boundaries stubbed. Coverage areas: 1. Coordinator identity survives init/hardReset/buildServerHost().onHardReset - Captured grant/DSK/abort callbacks settle via public ZwaveClient API - Exact once socket/controller event assertions 2. Server-disabled MQTT callback path - Coordinator object survives and replies settle - hasUserCallbacks state preserved across lifecycle 3. Sole state ownership through real ZwaveClient - _createNode atomic takeTmpNode consume (first gets, second cannot) - Replacement controls store deletion via coordinator - Pending IDs cleaned through coordinator on inclusion failure - close(true)/init clears coordinator state 4. Learn/start inclusion cross-mode delegates with real coordinator - Timer set/clear assertions on coordinator._commandsTimeout - Exact controller stop calls, fake clocks, no sleeps 5. Firmware service identity survives all reset paths - Generation fencing: deferred controller boundary, late resolve/reject - No updateStoreNodes/emit/socket mutation from old generation - Exactly one timer when new generation schedules 6. Listener-count stability assertions - Repeated init/hardReset/close lifecycle does not leak listeners - Works under shuffled seeds Rename direct service test describes in InclusionCoordinator.test.ts and FirmwareUpdateService.test.ts from 'Production:' to 'Service-level:' to correctly indicate they test isolated service instances, not the integrated ZwaveClient production paths. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…irst If driver.hardReset() rejects, no lifecycle state is touched: timers, callbacks, and pending promises remain live so public APIs still settle normally. On success, init() resets coordinator/firmware service once. Removes the previous pattern of pre-resetting coordinator and firmware service before the driver call, which left state irrecoverable on failure. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…tions Introduces FirmwareLifecycleCancelledError and private _assertFence() helper. After every await in firmwareUpdateOTW, updateFirmware, firmwareUpdateOTA, abortFirmwareUpdate, getAvailableFirmwareUpdates, getAllAvailableFirmwareUpdates, checkAllNodesFirmwareUpdates, and checkNodeFirmwareUpdates, the generation/disposed state is checked before any driver/node/store/socket mutation, network call, result return, or reschedule. Key guarantees: - OTW paused in backup never calls a replacement driver - Extraction paused across reset never calls stale node update - Abort completion after reset never mutates node/socket - Lookup methods never return stale results - Normal-path errors/payloads are preserved - No silent success — explicit lifecycle cancellation error thrown Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…cing, and inclusion paths hardReset failure-safe: - Arm firmware timer + pending inclusion resolver; hardReset rejects; timer/state/promise remain live and public APIs still settle - Success path resets coordinator and firmware service exactly once Firmware fencing (direct service tests): - OTW backup barrier → reset/driver swap prevents OTW call - OTW extraction barrier → reset/stale node - OTA late completion after reset throws cancellation - Abort late completion does not mutate node/socket - updateFirmware extraction barrier → reset/stale node - getAvailable/getAllAvailable late result → cancellation - Normal-path errors/payloads preserved Production integration corrections: - MQTT callback test verifies getUserCallbacks() matches production connect() option construction and setUserCallbacks installs via driver.updateOptions - Inclusion state tests drive public startInclusion and invoke _onNodeFound/_onInclusionFailed as event callbacks with realistic inputs — no direct coordinator private state mutation - Listener stability tests explain driver/controller contract: each hardReset creates a fresh driver, old controller listeners are discarded; only ZwaveClient's own EventEmitter is tested Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…owner Remove the shadow _inclusionState field from ZwaveClient. The InclusionCoordinator is now the single source of truth for inclusion state. Changes: - Type coordinator's _inclusionState as InclusionState (via type-only port import from zwave-js) instead of unknown - getState(), getInfo(), _updateControllerStatus all read from coordinator.inclusionState - _onDriverReady calls coordinator.syncFromDriver() instead of direct assignment - _onInclusionStateChanged delegates to coordinator which preserves exact emit-only-on-change semantics - coordinator.reset() clears _inclusionState to undefined - InclusionDriverPort.controller.inclusionState typed as InclusionState | undefined Tests: - Direct service: reset clears state, syncFromDriver reads driver, onInclusionStateChanged emits only on change - Production integration: active state → close/init/hardReset/server hardReset → getState/getInfo undefined immediately, then sync + event report current state Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Refactor checkAllNodesFirmwareUpdates and checkNodeFirmwareUpdates to stage firmware-node projections without mutating shared storeNodes/ ZUINode or emitting. A new persistStagedNodeUpdates port method writes staged data to disk. After persistence completes, a generation fence is asserted — only then are shared in-memory nodes atomically mutated and socket emissions fired. If fence breaks while persistence is pending, no shared store mutation, node mutation, or emit occurs. The underlying filesystem write cannot be cancelled once started (documented physical limitation), so on-disk state may reflect staged data but the shared in-memory state will never be stale-published after reset. Changes: - Add StagedFirmwareNodeUpdate type to ports - Add persistStagedNodeUpdates to FirmwareNodeStorePort - Refactor checkAllNodesFirmwareUpdates: compute projections → persist → fence → atomically apply + emit - Refactor checkNodeFirmwareUpdates: same pattern - Keep _updateNodeFirmwareInfo for synchronous user-initiated paths (dismissFirmwareUpdate) that have their own fence - Implement port in ZwaveClient firmwareNodeStorePort Tests: - Direct service: reset during persistence → no node mutation or emit - Direct service: normal path persists then applies + emits - Integration tests remain green Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add onReplacementComplete() to InclusionCoordinator — called from the real _onNodeAdded controller event handler after the replacement node arrives. This ensures _isReplacing is cleared at the correct point in the lifecycle (AFTER the old node's removal has been processed with isReplacing=true to preserve store metadata). Also add onInclusionStopped() to clear _isReplacing for replacement flows where no node-added event fires (e.g. user cancels replacement before the new node arrives). Called from _onInclusionStopped in ZwaveClient. Event ordering guarantee: zwave-js fires 'node removed' (for the old node) BEFORE 'node added' (for the replacement), so _removeNode sees isReplacing=true and preserves store metadata. onReplacementComplete is called only from _onNodeAdded which fires after _removeNode. Changes: - Add onReplacementComplete() to coordinator (clears _isReplacing) - Add onInclusionStopped() to coordinator (clears _isReplacing) - Call onReplacementComplete from _onNodeAdded in ZwaveClient - Call onInclusionStopped from _onInclusionStopped in ZwaveClient - onNodeAdded remains focused on pending tracking only Tests: - Direct service: onReplacementComplete clears flag, onInclusionStopped clears flag, full lifecycle ordering, normal inclusion no-op - Production integration: replaceFailedNode → removal preserves store → node added clears flag → later removal deletes store; inclusion stopped path; normal inclusion unaffected Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ed storeNodes mutation Refactor persistStagedNodeUpdates in ZwaveClient to build a detached NodesStoreRecord snapshot (shallow-clone of storeNodes with staged firmware properties applied to cloned entries) and persist it via a new private _persistNodesSnapshot helper. The shared this.storeNodes is never mutated before the persistence await returns. After persistence resolves and the generation fence holds, _applyNodeFirmwareProjection now also writes to the storeNode (via ensureStoreNode) in addition to the live ZUINode — ensuring in-memory consistency post-fence without pre-fence shared-state mutation. Refactored updateStoreNodes to delegate to _persistNodesSnapshot(this.storeNodes), preserving exact existing error/log/throwError semantics. Added production integration tests proving: - storeNodes is NOT mutated while persistence is pending - hardReset during pending persistence fences correctly: no stale storeNodes mutation, no ZUINode mutation, no socket emission - Successful path (no reset) correctly updates storeNodes after fence Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
AlCalzone
force-pushed
the
alcalzone-backend-refactor-firmware-inclusion
branch
from
July 17, 2026 11:08
a6ea24f to
3e9dd49
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Extracts firmware update and inclusion coordination from
ZwaveClient.Part of #4722. Stacked on #4732.