Skip to content

Releases: eman/quilt-hp-python

v0.5.6

Choose a tag to compare

@github-actions github-actions released this 04 Jul 15:58

Fixed

  • LocalCommsStatus fields were mislabeled (previously guessed from limited
    captures). Corrected to the verified field semantics: field 2 health
    status, field 3 link_statevisible_devices_count, field 4 version
    expected_devices_count, field 5 health_changed_ts
    last_session_change_ts, field 6 connection_state (int32) → reason
    (LocalCommsHealthReason enum). Decoding still worked by field number, but the
    model exposed wrong names/semantics and discarded the reason diagnostics.
    QuiltSmartModule/Controller now expose local_comms_visible_devices,
    local_comms_expected_devices, local_comms_reason, and
    local_comms_last_session_change (replacing local_comms_link_state,
    local_comms_connection_state, and local_comms_version).

Added

  • IndoorUnit now exposes model_sku, serial_number, and firmware_version,
    resolved from HomeDatastoreSystem.indoor_unit_hardware (previously discarded).
    This mirrors OutdoorUnit and Controller and lets downstream consumers (e.g.
    the Home Assistant integration) populate IDU device serial/firmware.
  • CLI set command gained a --fan option (AUTO, QUIET, LOW, MEDIUM,
    HIGH, BLAST) that applies the chosen fan speed to every indoor unit in the
    target space.
  • SystemSnapshot.indoor_units_for_space() returns all indoor units linked to a
    space (accepts a Space or space-ID string).
  • LocalCommsHealthReason enum (12 values). ZENOH_SESSION_DOWN
    identifies the local mesh transport as Eclipse Zenoh, not NATS (port 7447
    is Zenoh's default).
  • IndoorUnitConditions.compressor_minimum_run_time (proto field 13).
  • Marked SpaceSettings.safety_heating=9 confirmed (was "unconfirmed").

CI

  • Docs deploy workflow upgraded to the current Node 24 GitHub Pages actions
    (configure-pages@v6, upload-pages-artifact@v4, deploy-pages@v5),
    removing the deprecated Node 20 runtime path.
  • Docs deploy now retries deploy-pages once on transient GitHub Pages backend
    failures, failing the job only if both attempts fail.

v0.5.5

Choose a tag to compare

@github-actions github-actions released this 03 Jul 19:51
chore: bump version to 0.5.5

v0.5.4

Choose a tag to compare

@github-actions github-actions released this 03 Jul 16:33

Fixes all findings from a full architecture/code/performance/bug-hunt evaluation.

Critical

  • Proto3 absence detection never fired on real protos. Truthiness and
    getattr-with-default checks always pass on generated messages (unset
    sub-messages return truthy default instances), so sparse stream diffs zeroed
    room state, IDU controls, sensor readings, and controller temperatures on
    merge. All model parsing now uses HasField-based helpers, and
    SystemSnapshot.apply_* preserves identity/relationship fields, controller
    temperatures, ODU telemetry, and QSM LED state. New test suite
    (tests/test_models_real_proto_merge.py) reproduces the notifier stream's
    serialize/parse path with real generated protos.
  • Transport UNAUTHENTICATED retry was dead code. In grpc.aio, awaiting the
    interceptor continuation returns the call object; AioRpcError only surfaces
    when the call is awaited — so token refresh/retry never executed and clients
    broke permanently ~1h after token expiry. The interceptor now awaits the call
    and retries once. Tests updated to model real grpc.aio semantics.

High

  • Stream reconnect backoff/budget reset after a healthy connection (routine LB
    stream recycling no longer escalates to permanent 60s delays or kills the
    stream after N lifetime disconnects); non-gRPC failures reconnect and surface
    via on_error instead of dying silently; malformed events are skipped;
    jitter added; prompt reconnect after successful token refresh.
  • authenticate() no longer returns a locally-unexpired token the server just
    rejected; rotated Cognito refresh tokens are persisted; expiry measured from
    token receipt.
  • grpc_call passes CancelledError through untranslated (asyncio.timeout/
    cancellation semantics restored).
  • TUI: fatal stream death now shows a disconnect indicator and auto-recovers;
    first-time users get an OTP modal; boot failures show an error screen
    instead of a stuck spinner.

Medium / Low

  • Unified error translation: all hds/user RPCs route through grpc_call;
    UNAVAILABLE/DEADLINE_EXCEEDEDQuiltConnectionError and NOT_FOUND
    QuiltNotFoundError everywhere.
  • Single-flight guards on token refresh and cold snapshot fetch;
    get_system_id(home=...) no longer poisons the default-system cache;
    close() stops tracked streams; duplicate authorization metadata
    eliminated.
  • Stream API: on_connected callback, unsubscribe handles from all on_*
    registrations, apply_software_update_info, descriptor-derived wire-scan
    field numbers.
  • CLI/TUI: app-owned snapshot (stacked screens no longer go stale), cursor
    preservation, setpoint bounds, energy period validation, app-level °C/°F,
    LED brightness restoration, 0.0 readings rendered correctly, token store
    corruption recovery + fsync + flock, config dir 0700, dead code removal.
  • Docs: README no longer demonstrates raw-stream usage; stream/token types
    re-exported at package top level; stale OutdoorUnit/Controller reference
    listings corrected; contradictory proto comments fixed.

Intentionally unchanged: Python >= 3.14 floor and boto3 dependency (HA 2026.3+
runs Python 3.14).

v0.5.3

Choose a tag to compare

@github-actions github-actions released this 01 Jul 04:28
chore: bump version to 0.5.3

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 01 Jul 01:47

Fixed

  • SpaceControls.display_setpoint_str() — removed dead unreachable code in the
    fallback branch; temperature_setpoint_c is typed float so the None-guard
    lines were never executed (confirmed by coverage)
  • SystemSnapshot.apply_outdoor_unit() — refactored to collect field patches into
    an updates dict and call dataclasses.replace() once, consistent with all other
    apply_* methods; previously called replace() twice in sequence, creating an
    unnecessary intermediate object
  • QuiltClient.close() now sets self._token = None; previously left a stale token
    accessible via get_current_token() after the channel was closed

Changed

  • invoke_refresh_callback (formerly _invoke_refresh_callback) extracted from
    transport.py and services/streaming.py into a single shared implementation in
    tokens.py; the streaming copy lacked the WeakKeyDictionary signature cache,
    causing inspect.signature() to be called on every token-refresh event
  • FanSpeed.to_wire() and LouverAngle.to_wire() now reference module-level
    constant dicts (_FAN_SPEED_WIRE_MAP, _LOUVER_ANGLE_WIRE_MAP) instead of
    re-allocating the mapping on every call
  • _id_variants() moved from models/system.py into models/_helpers.py and
    reused by lookup_hardware(); eliminates duplicated ID-normalisation logic
  • QuiltClient.invalidate_snapshot() log level changed from WARNING to DEBUG

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 05 Jun 02:09

Added protocol support

  • HVACMode.DRY = 8 — dehumidification mode; gate: mobile_dry_mode_selection_enabled. Fan non-interactive (QSM forces ~600 RPM). No user-configurable temperature setpoint; built-in temperature floor is server-side.
  • HVACState.DRY = 11, DRY_DEFERRED = 12, DRY_PREPARING = 13
  • LocalCommsHealthStatus enum (UNSPECIFIED=0, HEALTHY=1, DEGRADED=2, OFFLINE=3, STARTING_UP=4) — gate: mobile_local_control_health_enabled
  • QuiltSmartModule.local_comms_health — extracted from new LocalCommsStatus nested message (proto field 8, subfield 2)
  • Controller.local_comms_health — extracted from new LocalCommsStatus nested message (proto field 9, subfield 2)
  • LocalCommsStatus proto message with updated_ts, health, link_state, version, health_changed_ts, connection_state subfields (fields 1–6; wire-confirmed)

Changed

  • APP_VERSION bumped to 1.0.26
  • LocalCommsStatus is a nested message (not a simple enum) on both QSM and Controller — the earlier inferred field type was incorrect; wire-confirmed via 2026-06-04 mitmproxy capture

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 16 May 21:10
chore: bump version to 0.4.0

v0.3.2

Choose a tag to compare

@github-actions github-actions released this 14 May 16:09

Added

  • LouverAngle.label property and __str__ with human-readable position names:
    ANGLE1"Horizontal", ANGLE2"Slightly Down", ANGLE3"Down",
    ANGLE4"Mostly Down", ANGLE5"Straight Down"

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 14 May 15:34

Fixed

  • RST_STREAM with error code 0 (HTTP/2 NO_ERROR, a normal server-side graceful reset) is now logged at DEBUG instead of WARNING to reduce log noise
  • CANCELLED (server closed the stream normally, e.g. keepalive timeout or server rotation) is now logged at INFO instead of WARNING
  • UNAUTHENTICATED reconnects handled by the automatic token refresh are now logged at INFO instead of WARNING

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 13 May 00:42

Added

  • NotifierStream health properties: is_connected, last_event_at, stream_state
  • NotifierStream debounce_s parameter to coalesce rapid update bursts
  • MetricBucketStatus enum exposed on EnergyBucket.status (was untyped int)
  • grpc_call() context manager in services for consistent gRPC error translation and optional retry
  • Structured logging.getLogger(__name__) across all modules (auth, client, transport, services, CLI)
  • QuiltStreamError re-exported from the top-level quilt_hp package
  • Shared model helpers (_helpers.py) for WiFi signal parsing and hardware lookup

Changed

  • ScheduleEvent.hvac_mode is now typed as HVACMode (was int)
  • Token temp file created with os.open(..., 0o600) so permissions are secure from creation (no transient world-readable window)
  • Signature cache in transport layer uses weakref.WeakKeyDictionary instead of dict[int, bool] — prevents unbounded growth and id-reuse bugs after GC
  • login() clears the token cache so a re-login always fetches fresh credentials
  • _GrpcCallContext avoids self-chaining when re-raising a QuiltError unchanged

Fixed

  • EnergyBucket.has_missing_energy_value now treats None (absent proto field) as missing, not just NaN; prevents TypeError in SpaceEnergyMetrics.total_kwh
  • MetricBucketStatus() conversion in get_energy_metrics catches ValueError for unknown server values and falls back to UNSPECIFIED instead of raising
  • WeakKeyDictionary.get() for the refresh-callback signature cache is now guarded against TypeError for non-weakrefable callables
  • AUTO mode setpoint deadband clamp now runs before setpoint selection, ensuring the correct (clamped) value is sent to the device
  • CLI settings bool coercion uses isinstance(v, bool) to avoid bool("false") == True
  • Zero-value proto3 fields (0 °C temperature, 0 dBm WiFi signal, 0% humidity) are now preserved instead of being dropped as falsy
  • NotifierStream reconnect subscription is now protected by an asyncio.Lock to prevent concurrent subscribe/reconnect races
  • CLI enum lookups raise a clear error showing valid options on invalid input
  • auth.py narrows broad except Exception to except (QuiltAuthError, ClientError) to avoid swallowing unexpected errors