feat: add viaGateway mode for shared per-gateway service#777
Open
lackas wants to merge 3 commits into
Open
Conversation
Preparation for the viaGateway shared-service mode (next commit): when one ViCareServiceViaGateway instance serves all devices on a gateway, the service cannot answer per-device role questions because its roles list would be whichever device was processed first. Move role-based decisions onto the device-identity side of the architecture, where they belong. - DeviceConfig.hasRoles(roles) + DeviceConfig.isGateway() use self.roles instead of self.service.hasRoles / self.service._isGateway. - Device base class now takes optional roles list; all DeviceConfig.as* factories pass it through. Device.isLegacyDevice / isE3Device use self.roles. - HeatingDevice.get_heat_curve_formular uses self.roles for the heatpump/E3 check. - Service-side hasRoles / _isGateway are kept untouched for backwards compatibility (still used internally by the per-device service for URL shape selection). No public API removals. Test mocks updated to pass roles via the PyViCareDeviceConfig constructor instead of patching service.hasRoles.
Adds an opt-in service mode where ONE ViCareCachedServiceViaGateway
instance serves all devices on a gateway from a single bulk API call,
instead of N per-device calls per refresh.
Endpoint used (already verified byte-identical for overlapping features):
GET /features/installations/{id}/gateways/{serial}/features?includeDevicesFeatures=true
Public API:
vicare = PyViCare()
vicare.loadViaGateway(True) # default False; must precede initWith*
vicare.initWithCredentials(...)
Per-device entry points (device.getProperty, DeviceConfig.as*) are
unchanged. The shared service is wired in PyViCare.__extract_all_devices:
one service per gateway in viaGateway mode, one per device otherwise.
Architecture:
- ViCareServiceViaGateway: transport, hits the bulk URL for fetch_all_features
and getProperty; setProperty still uses the per-device URL (writes target
one specific feature on one specific device).
- ViCareCachedServiceViaGateway: caches both getProperty and
fetch_all_features against the same cached payload. The fetch_all_features
cache is the key optimization for HA Core's DataUpdateCoordinator pattern,
where each per-device coordinator calls service.clear_cache +
service.fetch_all_features per refresh -- without it, N coordinators on a
shared service would still trigger N bulk fetches per cycle.
- Defensive error handling (PACKAGE_NOT_PAID_FOR, DeviceCommunicationError,
InternalServerError) mirrors ViCareCachedService: serve stale cache on
transient failure, raise on first-fetch failure.
Carries the design intent of CFenner's stalled openviess#626 forward. The bulk
response test fixtures (heatbox1/heatbox2/tcu1) are lifted from that draft.
Implementation is fresh on current master because openviess#774 made the previous
sketch incompatible (accessor lives on Device now, service is stateless),
and the original draft did not actually share the service instance across
devices.
Tests: 20 new ones across three files covering URL building, per-device
filtering of the bulk response, shared-cache behavior across devices,
TTL respect, setProperty cache invalidation, stale-cache fallback on
transient errors, and end-to-end PyViCare wiring with mocked OAuth.
Refs: openviess#626 (CFenner draft)
Refs: home-assistant/core#173776 (raised the question)
CI pylint failed with C0415 (import-outside-toplevel).
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.
Summary
Adds an opt-in service mode where ONE
ViCareCachedServiceViaGatewayinstance serves all devices on a gateway from a single bulk API call, instead of N per-device calls per refresh.Per-device entry points (
device.getProperty,DeviceConfig.as*) are unchanged. Default behavior is unchanged.Why
The Viessmann API exposes a per-gateway bulk endpoint:
It returns all
isEnabled=truefeatures for every device on the gateway in one call. For overlapping features the property values + timestamps are byte-identical to per-device fetches.For free-tier users (1450 API calls/day) this is meaningful headroom: a 4-device gateway drops from 4 calls per refresh to 1.
Architecture
The shared service is wired in
PyViCare.__extract_all_devices: one service per gateway in viaGateway mode, one per device otherwise.setPropertystill uses the per-device URL in both modes (writes target one feature on one device; no bulk write endpoint exists).Both
getPropertyandfetch_all_featuresare cached against the same payload. Thefetch_all_featurescache is the key optimization for HA Core's DataUpdateCoordinator pattern (per-device coordinators callservice.clear_cache + service.fetch_all_featuresper refresh — without caching that path, N coordinators on a shared service would still trigger N bulk fetches per cycle).Defensive error handling (
PACKAGE_NOT_PAID_FOR,DeviceCommunicationError,InternalServerError) mirrorsViCareCachedService.Precursor refactor
Commit 1 moves role-based decisions (
hasRoles,isGateway,isLegacyDevice,isE3Device,get_heat_curve_formular) from Service to Device/DeviceConfig. In shared-service mode the service cannot answer per-device role questions because its roles list would be whichever device was processed first. Service-sidehasRoles/_isGatewayare kept for backwards compatibility (still used by the per-device service for URL shape selection).Public API unchanged. Test mocks updated to pass roles via the
PyViCareDeviceConfigconstructor instead of patchingservice.hasRoles.Credit
Carries the design intent of #626 by @CFenner forward. The bulk response test fixtures (heatbox1/heatbox2/tcu1) are lifted from that draft. Implementation is fresh on current master because #774 made the previous sketch incompatible (accessor lives on Device now, service is stateless), and the original draft did not actually share the service instance across devices.
Tests
20 new tests covering URL building, per-device filtering of the bulk response, shared-cache behavior across devices, TTL respect,
setPropertycache invalidation, stale-cache fallback on transient errors, end-to-end PyViCare wiring with mocked OAuth.749 → 750 total tests passing, ruff clean, pylint 10/10 on changed modules.
Refs: #626, #774, home-assistant/core#173776