Releases: eman/quilt-hp-python
Releases · eman/quilt-hp-python
Release list
v0.5.6
Fixed
LocalCommsStatusfields were mislabeled (previously guessed from limited
captures). Corrected to the verified field semantics: field 2health→
status, field 3link_state→visible_devices_count, field 4version→
expected_devices_count, field 5health_changed_ts→
last_session_change_ts, field 6connection_state(int32) →reason
(LocalCommsHealthReasonenum). Decoding still worked by field number, but the
model exposed wrong names/semantics and discarded the reason diagnostics.
QuiltSmartModule/Controllernow exposelocal_comms_visible_devices,
local_comms_expected_devices,local_comms_reason, and
local_comms_last_session_change(replacinglocal_comms_link_state,
local_comms_connection_state, andlocal_comms_version).
Added
IndoorUnitnow exposesmodel_sku,serial_number, andfirmware_version,
resolved fromHomeDatastoreSystem.indoor_unit_hardware(previously discarded).
This mirrorsOutdoorUnitandControllerand lets downstream consumers (e.g.
the Home Assistant integration) populate IDU device serial/firmware.- CLI
setcommand gained a--fanoption (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 aSpaceor space-ID string).LocalCommsHealthReasonenum (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=9confirmed (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-pagesonce on transient GitHub Pages backend
failures, failing the job only if both attempts fail.
v0.5.5
v0.5.4
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 usesHasField-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;AioRpcErroronly 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 realgrpc.aiosemantics.
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
viaon_errorinstead 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_callpassesCancelledErrorthrough 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_EXCEEDED→QuiltConnectionErrorandNOT_FOUND→
QuiltNotFoundErroreverywhere. - 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_connectedcallback, unsubscribe handles from allon_*
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
v0.5.1
Fixed
SpaceControls.display_setpoint_str()— removed dead unreachable code in the
fallback branch;temperature_setpoint_cis typedfloatso theNone-guard
lines were never executed (confirmed by coverage)SystemSnapshot.apply_outdoor_unit()— refactored to collect field patches into
anupdatesdict and calldataclasses.replace()once, consistent with all other
apply_*methods; previously calledreplace()twice in sequence, creating an
unnecessary intermediate objectQuiltClient.close()now setsself._token = None; previously left a stale token
accessible viaget_current_token()after the channel was closed
Changed
invoke_refresh_callback(formerly_invoke_refresh_callback) extracted from
transport.pyandservices/streaming.pyinto a single shared implementation in
tokens.py; the streaming copy lacked theWeakKeyDictionarysignature cache,
causinginspect.signature()to be called on every token-refresh eventFanSpeed.to_wire()andLouverAngle.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 frommodels/system.pyintomodels/_helpers.pyand
reused bylookup_hardware(); eliminates duplicated ID-normalisation logicQuiltClient.invalidate_snapshot()log level changed fromWARNINGtoDEBUG
v0.5.0
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 = 13LocalCommsHealthStatusenum (UNSPECIFIED=0,HEALTHY=1,DEGRADED=2,OFFLINE=3,STARTING_UP=4) — gate:mobile_local_control_health_enabledQuiltSmartModule.local_comms_health— extracted from newLocalCommsStatusnested message (proto field 8, subfield 2)Controller.local_comms_health— extracted from newLocalCommsStatusnested message (proto field 9, subfield 2)LocalCommsStatusproto message withupdated_ts,health,link_state,version,health_changed_ts,connection_statesubfields (fields 1–6; wire-confirmed)
Changed
APP_VERSIONbumped to1.0.26LocalCommsStatusis 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
v0.3.2
Added
LouverAngle.labelproperty and__str__with human-readable position names:
ANGLE1→"Horizontal",ANGLE2→"Slightly Down",ANGLE3→"Down",
ANGLE4→"Mostly Down",ANGLE5→"Straight Down"
v0.3.1
Fixed
RST_STREAM with error code 0(HTTP/2NO_ERROR, a normal server-side graceful reset) is now logged atDEBUGinstead ofWARNINGto reduce log noiseCANCELLED(server closed the stream normally, e.g. keepalive timeout or server rotation) is now logged atINFOinstead ofWARNINGUNAUTHENTICATEDreconnects handled by the automatic token refresh are now logged atINFOinstead ofWARNING
v0.3.0
Added
NotifierStreamhealth properties:is_connected,last_event_at,stream_stateNotifierStreamdebounce_sparameter to coalesce rapid update burstsMetricBucketStatusenum exposed onEnergyBucket.status(was untypedint)grpc_call()context manager inservicesfor consistent gRPC error translation and optional retry- Structured
logging.getLogger(__name__)across all modules (auth, client, transport, services, CLI) QuiltStreamErrorre-exported from the top-levelquilt_hppackage- Shared model helpers (
_helpers.py) for WiFi signal parsing and hardware lookup
Changed
ScheduleEvent.hvac_modeis now typed asHVACMode(wasint)- 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.WeakKeyDictionaryinstead ofdict[int, bool]— prevents unbounded growth and id-reuse bugs after GC login()clears the token cache so a re-login always fetches fresh credentials_GrpcCallContextavoids self-chaining when re-raising aQuiltErrorunchanged
Fixed
EnergyBucket.has_missing_energy_valuenow treatsNone(absent proto field) as missing, not justNaN; preventsTypeErrorinSpaceEnergyMetrics.total_kwhMetricBucketStatus()conversion inget_energy_metricscatchesValueErrorfor unknown server values and falls back toUNSPECIFIEDinstead of raisingWeakKeyDictionary.get()for the refresh-callback signature cache is now guarded againstTypeErrorfor 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
boolcoercion usesisinstance(v, bool)to avoidbool("false") == True - Zero-value proto3 fields (0 °C temperature, 0 dBm WiFi signal, 0% humidity) are now preserved instead of being dropped as falsy
NotifierStreamreconnect subscription is now protected by anasyncio.Lockto prevent concurrent subscribe/reconnect races- CLI enum lookups raise a clear error showing valid options on invalid input
auth.pynarrows broadexcept Exceptiontoexcept (QuiltAuthError, ClientError)to avoid swallowing unexpected errors