From 29928beacf901c6e466586546edc67d4b8ce70ab Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Mon, 3 Aug 2026 10:44:21 -0700 Subject: [PATCH 1/3] feat: add CommandService/RequestFastUpdates and system diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two additions surfaced from reverse-engineering com.quilt.android 255. CommandService / RequestFastUpdates - New core.protos.home_datastore.CommandService with RequestFastUpdates, wrapped by QuiltClient.request_fast_updates(reason=..., system_id=...). Asks the cloud to raise a system's telemetry cadence — the lever the mobile app pulls on user activity or a degraded local mesh. New FastUpdateReason enum (UNSPECIFIED / LOCAL_COMMS_UNHEALTHY / USER_ACTIVITY). Verified reachable against production. Diagnostics view - QuiltClient.get_diagnostics() and SystemSnapshot.diagnostics() return a new SystemDiagnostics: the per-indoor-unit fault/condition matrix (including the outdoor-unit / refrigerant conditions surfaced through each IDU), refrigerant-circuit temperatures, and per-unit power. New IndoorUnitDiagnostics / OutdoorUnitDiagnostics models and IndoorUnitConditions.active / .states() helpers. New `quilt diagnostics` CLI command (--faults-only, --output json). The outdoor unit's own raw board sensors are withheld from the cloud plane and are flagged as such rather than reported. Tests, reference docs, and CHANGELOG updated. ruff / mypy / pytest (89% cov) / mkdocs --strict all green. --- CHANGELOG.md | 23 ++ docs/how-to/cli-scripting.md | 19 ++ docs/reference/client.md | 53 ++++ docs/reference/grpc-services-matrix.md | 8 + docs/reference/models.md | 84 ++++++ proto/cleaned/quilt_hds.proto | 28 ++ src/quilt_hp/_proto/quilt_hds_pb2.py | 100 ++++--- src/quilt_hp/_proto/quilt_hds_pb2.pyi | 58 ++++ src/quilt_hp/_proto/quilt_hds_pb2_grpc.py | 72 +++++ src/quilt_hp/cli/main.py | 91 ++++++ src/quilt_hp/client.py | 48 +++ src/quilt_hp/models/__init__.py | 12 + src/quilt_hp/models/diagnostics.py | 123 ++++++++ src/quilt_hp/models/enums.py | 20 ++ src/quilt_hp/models/indoor_unit.py | 54 ++-- src/quilt_hp/models/system.py | 25 ++ src/quilt_hp/services/command.py | 62 ++++ tests/test_command.py | 86 ++++++ tests/test_diagnostics.py | 346 ++++++++++++++++++++++ 19 files changed, 1246 insertions(+), 66 deletions(-) create mode 100644 src/quilt_hp/models/diagnostics.py create mode 100644 src/quilt_hp/services/command.py create mode 100644 tests/test_command.py create mode 100644 tests/test_diagnostics.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d1f2674..213884f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ ## [Unreleased] +### Added +- **Diagnostics view.** `QuiltClient.get_diagnostics()` and + `SystemSnapshot.diagnostics()` return a new `SystemDiagnostics` — the + installer-style diagnostic picture assembled from data the cloud API already + returns on the indoor units: the per-IDU fault/condition matrix (including the + outdoor-unit / refrigerant conditions surfaced through each IDU, e.g. + `outdoor_unit_communication_error`, `defrost_cycle`, `oil_return`), + refrigerant-circuit temperatures (coil / gas-pipe / liquid-pipe / inlet / + outlet) and humidity, and per-unit power. New models `IndoorUnitDiagnostics`, + `OutdoorUnitDiagnostics`, `SystemDiagnostics`, plus `IndoorUnitConditions.active` + / `.states()` helpers. New CLI command `quilt diagnostics` (with `--faults-only` + and `--output json`). The outdoor unit's own raw sensors (compressor Hz, + pressures, discharge temp) remain withheld from the cloud plane and are flagged + as such rather than reported. +- `QuiltClient.request_fast_updates(reason=..., system_id=...)` and the + underlying `CommandService.request_fast_updates()` wrapper — calls the new + `core.protos.home_datastore.CommandService/RequestFastUpdates` RPC to ask the + cloud to raise a system's telemetry cadence. This is the same lever the Quilt + mobile app pulls (new in app versionCode 255) when the user is active or a + device's local mesh is degraded; the effect is a faster stream of updates over + `NotifierStream`. New `FastUpdateReason` enum (`UNSPECIFIED`, + `LOCAL_COMMS_UNHEALTHY`, `USER_ACTIVITY`) in `quilt_hp.models.enums`. + ## [0.5.7] - 2026-07-20 ### Added diff --git a/docs/how-to/cli-scripting.md b/docs/how-to/cli-scripting.md index b3df915..f96c954 100644 --- a/docs/how-to/cli-scripting.md +++ b/docs/how-to/cli-scripting.md @@ -49,6 +49,25 @@ quilt snapshot | jq '[.indoor_units[] | select(.state.is_online == false)]' --- +## Check diagnostics + +```bash +# Human-readable: per-IDU fault conditions, refrigerant temps, and power +quilt diagnostics + +# Only indoor units with an active fault +quilt diagnostics --faults-only + +# JSON for scripting — e.g. list every active fault system-wide +quilt diagnostics --output json | jq '.indoor_units[] | select(.active_faults | length > 0) | {name, active_faults}' +``` + +The outdoor unit's own raw sensors (compressor Hz, pressures, discharge temp) +are withheld from the cloud plane; the diagnostic conditions and refrigerant +pipe temperatures for each ODU circuit are surfaced through its indoor units. + +--- + ## Control spaces from the shell ```bash diff --git a/docs/reference/client.md b/docs/reference/client.md index eb273e2..b1fd452 100644 --- a/docs/reference/client.md +++ b/docs/reference/client.md @@ -215,6 +215,29 @@ def invalidate_snapshot(self) -> None Discards the cached snapshot. The next `get_snapshot()` call fetches fresh data from the server. +--- + +### `get_diagnostics` + +```python +async def get_diagnostics(self, system_id: str | None = None) -> SystemDiagnostics +``` + +Fetches the installer-style diagnostic view — a convenience wrapper over +`get_snapshot().diagnostics()`. Returns a [`SystemDiagnostics`](models.md#systemdiagnostics) +with, per indoor unit: the fault/condition matrix (including the outdoor-unit and +refrigerant conditions surfaced through each IDU), refrigerant-circuit temperatures +(coil / gas-pipe / liquid-pipe / inlet / outlet) and humidity, and per-unit power. + +The outdoor unit's own raw sensors (compressor Hz, pressures, discharge temp) are +withheld from the cloud plane and are **not** included; `OutdoorUnitDiagnostics` +reports `raw_sensors_available=False`. + +**Parameters:** +- `system_id`: explicit system ID; defaults to the client's resolved system. + +**Raises:** `QuiltError` if the RPC fails. + ### `close` ```python @@ -510,6 +533,36 @@ Returns hourly energy consumption for all spaces. --- +## Telemetry cadence + +### `request_fast_updates` + +```python +async def request_fast_updates( + self, + *, + reason: FastUpdateReason = FastUpdateReason.USER_ACTIVITY, + system_id: str | None = None, +) -> None +``` + +Asks the cloud to raise the telemetry cadence for a system by calling +`CommandService/RequestFastUpdates` — the same lever the mobile app pulls when +the user is active or a device's local mesh is degraded. New in the Quilt app +versionCode 255. + +The RPC response is empty; the effect is a faster stream of state updates over +a `NotifierStream`, so pair this with `stream()`. There is no local endpoint — +the request flows through the cloud API like every other call. + +**Parameters:** +- `reason`: `FastUpdateReason.USER_ACTIVITY` (default), `LOCAL_COMMS_UNHEALTHY`, or `UNSPECIFIED`. Imported from `quilt_hp.models.enums`. +- `system_id`: explicit system ID; defaults to the client's resolved system. + +**Raises:** `QuiltError` if the client is not connected or the RPC fails. + +--- + ## Streaming ### `stream` diff --git a/docs/reference/grpc-services-matrix.md b/docs/reference/grpc-services-matrix.md index da6d777..6f58633 100644 --- a/docs/reference/grpc-services-matrix.md +++ b/docs/reference/grpc-services-matrix.md @@ -20,6 +20,14 @@ Defined in `quilt_hds.proto`. Package: `core.protos.home_datastore`. | `DeleteScheduleWeek` | `DeleteScheduleWeekRequest` | `Empty` | `HomeDatastoreService.delete_schedule_week()` | | `UpdateLocation` | `UpdateLocationRequest` | `Location` | `HomeDatastoreService.update_location_schedule_execution()`; pauses/resumes schedules | +## CommandService + +Defined in `quilt_hds.proto`. Package: `core.protos.home_datastore`. New in the Quilt app versionCode 255; cloud stub only (no local endpoint). + +| Method | Request | Response | Library wrapper | +| --- | --- | --- | --- | +| `RequestFastUpdates` | `RequestFastUpdatesRequest` | `Empty` | `CommandService.request_fast_updates()` → `None`; also `QuiltClient.request_fast_updates()` | + ## SystemInformationService Defined in `quilt_services.proto`. Package: `core.protos.app`. diff --git a/docs/reference/models.md b/docs/reference/models.md index 92d11bc..9c6be3e 100644 --- a/docs/reference/models.md +++ b/docs/reference/models.md @@ -62,6 +62,21 @@ service = UserService(channel) | `get_user_attributes()` | Returns `UserAttributes` including declared user type. | | `patch_user_attributes(declared_user_type)` | Updates user attributes. | +### `CommandService` + +```python +from quilt_hp.services.command import CommandService + +service = CommandService(channel) +``` + +Wraps the `CommandService` gRPC stub (new in the Quilt app versionCode 255). +Registered in the cloud stub only — there is no local endpoint. + +| Method | Description | +|--------|-------------| +| `request_fast_updates(system_id, reason=FastUpdateReason.USER_ACTIVITY)` | Asks the cloud to raise the telemetry cadence for a system. Returns `None`; the effect is a faster stream of updates over `NotifierStream`. | + ### `NotifierStream` ```python @@ -573,6 +588,74 @@ Firmware/software update record associated with an indoor unit, outdoor unit, co --- +### SystemDiagnostics + +```python +@dataclass(slots=True) +class SystemDiagnostics: + indoor_units: list[IndoorUnitDiagnostics] + outdoor_units: list[OutdoorUnitDiagnostics] + + @property + def active_faults(self) -> list[tuple[str, str]]: ... # (indoor_unit_id, condition_name) + @property + def has_faults(self) -> bool: ... +``` + +The installer-style diagnostic view, assembled from data the cloud API already +returns. Obtain one from `SystemSnapshot.diagnostics()` or +`QuiltClient.get_diagnostics()`. + +#### IndoorUnitDiagnostics + +```python +@dataclass(slots=True) +class IndoorUnitDiagnostics: + indoor_unit_id: str + name: str + space_id: str + space_name: str + online: bool + hvac_state: HVACState + active_faults: list[str] # condition names currently ACTIVE + conditions: dict[str, ConditionState] # every condition → state (empty if none reported) + coil_temperature_c: float | None + gas_pipe_temperature_c: float | None + liquid_pipe_temperature_c: float | None + inlet_temperature_c: float | None + outlet_temperature_c: float | None + inlet_humidity_pct: float | None + hvac_power_w: float | None +``` + +Per-indoor-unit diagnostics. The condition matrix includes the outdoor-unit and +refrigerant conditions surfaced through the IDU (`outdoor_unit_communication_error`, +`abnormal_outdoor_air_temperature`, `defrost_cycle`, `oil_return`, `coil_preheat`, +`modbus_communication_error`, `compressor_minimum_run_time`, …). Refrigerant temps +and power come from `IndoorUnit.performance_data` / `performance_metrics` and are +`None` when the unit reported none. + +The underlying `IndoorUnitConditions` model exposes `active` (list of ACTIVE +condition names) and `states()` (dict of every condition → `ConditionState`). + +#### OutdoorUnitDiagnostics + +```python +@dataclass(slots=True) +class OutdoorUnitDiagnostics: + outdoor_unit_id: str + hvac_state: HVACState + raw_sensors_available: bool +``` + +Per-outdoor-unit diagnostics. `raw_sensors_available` is `False` over the cloud +plane — the ODU's own `performance_data` (compressor Hz, suction/discharge +pressures, coil/discharge temps) is withheld from the mobile API; that telemetry +is only reachable on the local/hardware track. The refrigerant conditions and +pipe temperatures for this ODU's circuit are surfaced through its indoor units. + +--- + ## Enum types All enums live in `quilt_hp.models.enums` and subclass `IntEnum`, mirroring Quilt's wire values. @@ -596,5 +679,6 @@ All enums live in `quilt_hp.models.enums` and subclass `IntEnum`, mirroring Quil | `HvacControllerType` | Controller algorithm variant | `PASS_THROUGH_TEMPERATURE`, `INTEGRAL_TEMPERATURE_V1`, `INTEGRAL_TEMPERATURE_V2` | | `FallbackControlCommand` | Offline fallback command sent to an IDU | `COMPLETE`, `EXIT` | | `RemoteSensorControlMode` | Whether a remote sensor participates in control | `DISABLED`, `ENABLED` | +| `FastUpdateReason` | Why `request_fast_updates()` is asking the cloud to raise the telemetry cadence | `UNSPECIFIED`, `LOCAL_COMMS_UNHEALTHY`, `USER_ACTIVITY` | `FanSpeed.to_wire()` and `FanSpeed.from_wire()` handle the Quilt protocol's `(fan_speed_mode, fan_speed_percent)` encoding. `LouverAngle.to_wire()` and `LouverAngle.from_wire()` do the same for fixed louver positions. diff --git a/proto/cleaned/quilt_hds.proto b/proto/cleaned/quilt_hds.proto index 6e9bddc..445634b 100644 --- a/proto/cleaned/quilt_hds.proto +++ b/proto/cleaned/quilt_hds.proto @@ -1045,3 +1045,31 @@ service HomeDatastoreService { rpc ListLocations(ListLocationsRequest) returns (ListLocationsResponse); } +// --------------------------------------------------------------------------- +// CommandService — imperative device commands (new in com.quilt.android 255) +// --------------------------------------------------------------------------- +// +// A separate service in the same home_datastore package. Registered in the +// cloud gRPC stub only (api.prod.quilt.cloud) — there is no local endpoint. +// Field numbers, method name, and enum values APK-confirmed from +// com.quilt.android versionCode 255 (request class sh1, reason enum qh1). + +// Why the app is asking the cloud to raise the per-system telemetry cadence. +enum FastUpdateReason { + FAST_UPDATE_REASON_UNSPECIFIED = 0; + // Local Zenoh mesh is degraded/offline, so the app leans on the cloud to + // poll the devices faster instead (there is no app-side local fast path). + FAST_UPDATE_REASON_LOCAL_COMMS_UNHEALTHY = 1; + // The user is actively interacting in the app. + FAST_UPDATE_REASON_USER_ACTIVITY = 2; +} + +message RequestFastUpdatesRequest { + string system_id = 1; + FastUpdateReason reason = 2; +} + +service CommandService { + rpc RequestFastUpdates(RequestFastUpdatesRequest) returns (google.protobuf.Empty); +} + diff --git a/src/quilt_hp/_proto/quilt_hds_pb2.py b/src/quilt_hp/_proto/quilt_hds_pb2.py index 502dc60..226e490 100644 --- a/src/quilt_hp/_proto/quilt_hds_pb2.py +++ b/src/quilt_hp/_proto/quilt_hds_pb2.py @@ -27,7 +27,7 @@ from google.protobuf import field_mask_pb2 as google_dot_protobuf_dot_field__mask__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fquilt_hds.proto\x12\x1a\x63ore.protos.home_datastore\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\"\xc5\x02\n\x10LocalCommsStatus\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x42\n\x06status\x18\x02 \x01(\x0e\x32\x32.core.protos.home_datastore.LocalCommsHealthStatus\x12\x1d\n\x15visible_devices_count\x18\x03 \x01(\x05\x12\x1e\n\x16\x65xpected_devices_count\x18\x04 \x01(\x05\x12:\n\x16last_session_change_ts\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x42\n\x06reason\x18\x06 \x01(\x0e\x32\x32.core.protos.home_datastore.LocalCommsHealthReason\"\x96\x01\n\x0e\x45ntityMetadata\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12.\n\ncreated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x11\n\tsystem_id\x18\x04 \x01(\t\"i\n\x12LocationAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x15\n\rtz_identifier\x18\x03 \x01(\t\"\x8d\x01\n\x10LocationControls\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12I\n\x12schedule_execution\x18\x02 \x01(\x0e\x32-.core.protos.home_datastore.ScheduleExecution\"\xca\x01\n\x08Location\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x42\n\nattributes\x18\x02 \x01(\x0b\x32..core.protos.home_datastore.LocationAttributes\x12>\n\x08\x63ontrols\x18\x03 \x01(\x0b\x32,.core.protos.home_datastore.LocationControls\"z\n\x12SpaceRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x17\n\x0fparent_space_id\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x81\x03\n\rSpaceSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08timezone\x18\x04 \x01(\t\x12<\n\toccupancy\x18\x05 \x01(\x0e\x32).core.protos.home_datastore.OccupancyMode\x12L\n\x14hvac_controller_type\x18\x06 \x01(\x0e\x32..core.protos.home_datastore.HvacControllerType\x12\x1a\n\x12occupied_timeout_s\x18\x07 \x01(\x02\x12\x1c\n\x14unoccupied_timeout_s\x18\x08 \x01(\x02\x12\x45\n\x0esafety_heating\x18\t \x01(\x0e\x32-.core.protos.home_datastore.SafetyHeatingMode\"\xb8\x03\n\rSpaceControls\x12\x37\n\thvac_mode\x18\x01 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x1e\n\x16temperature_setpoint_c\x18\x02 \x01(\x02\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x1e\x63ooling_temperature_setpoint_c\x18\x04 \x01(\x02\x12&\n\x1eheating_temperature_setpoint_c\x18\x05 \x01(\x02\x12\x1a\n\x12\x63omfort_setting_id\x18\x06 \x01(\t\x12\x39\n\nboost_mode\x18\x07 \x01(\x0e\x32%.core.protos.home_datastore.BoostMode\x12T\n\x18\x63omfort_setting_override\x18\x08 \x01(\x0e\x32\x32.core.protos.home_datastore.ComfortSettingOverride\x12!\n\x19\x63omfort_setting_id_string\x18\t \x01(\t\"\xd2\x01\n\nSpaceState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16setpoint_temperature_c\x18\x02 \x01(\x02\x12\x1d\n\x15\x61mbient_temperature_c\x18\x03 \x01(\x02\x12\x39\n\nhvac_state\x18\x04 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\x12\x1a\n\x12\x63omfort_setting_id\x18\x05 \x01(\t\"\xbb\x02\n\x05Space\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x45\n\rrelationships\x18\x02 \x01(\x0b\x32..core.protos.home_datastore.SpaceRelationships\x12;\n\x08settings\x18\x03 \x01(\x0b\x32).core.protos.home_datastore.SpaceSettings\x12;\n\x08\x63ontrols\x18\x04 \x01(\x0b\x32).core.protos.home_datastore.SpaceControls\x12\x35\n\x05state\x18\x05 \x01(\x0b\x32&.core.protos.home_datastore.SpaceState\"\xe6\x01\n\x17IndoorUnitRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x10\n\x08space_id\x18\x02 \x01(\t\x12\x17\n\x0foutdoor_unit_id\x18\x03 \x01(\t\x12\x13\n\x0bhardware_id\x18\x04 \x01(\t\x12\x1d\n\x15quilt_smart_module_id\x18\x05 \x01(\t\x12.\n\nupdated_ts\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17\x66irmware_update_info_id\x18\x07 \x01(\t\"\x9e\x02\n\x12IndoorUnitSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12(\n light_brightness_default_percent\x18\x03 \x01(\x02\x12.\n\nupdated_ts\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15presence_fence_left_m\x18\x05 \x01(\x02\x12\x1e\n\x16presence_fence_right_m\x18\x06 \x01(\x02\x12 \n\x18presence_fence_forward_m\x18\x07 \x01(\x02\x12*\n\"radar_sensor_distance_from_floor_m\x18\x08 \x01(\x02\"\xc3\x03\n\x12IndoorUnitControls\x12\x16\n\x0eled_color_code\x18\x03 \x01(\r\x12$\n\x1cled_color_brightness_percent\x18\x04 \x01(\x02\x12@\n\x0e\x66\x61n_speed_mode\x18\x05 \x01(\x0e\x32(.core.protos.home_datastore.FanSpeedMode\x12\x19\n\x11\x66\x61n_speed_percent\x18\x06 \x01(\x02\x12.\n\nupdated_ts\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x45\n\x0blouver_mode\x18\n \x01(\x0e\x32\x30.core.protos.home_datastore.IndoorUnitLouverMode\x12\x1d\n\x15louver_fixed_position\x18\x0b \x01(\x02\x12\x41\n\rled_animation\x18\x0c \x01(\x0e\x32*.core.protos.home_datastore.LightAnimation\x12\x39\n\tled_state\x18\r \x01(\x0e\x32&.core.protos.home_datastore.LightState\"\x9c\x04\n\x0fIndoorUnitState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16temperature_setpoint_c\x18\x02 \x01(\x02\x12\x1d\n\x15\x61mbient_temperature_c\x18\x03 \x01(\x02\x12\x39\n\nhvac_state\x18\x04 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\x12\x1e\n\x16\x66\x61n_speed_setpoint_rpm\x18\x05 \x01(\x02\x12\x15\n\rfan_speed_rpm\x18\x06 \x01(\x02\x12 \n\x18light_brightness_percent\x18\x07 \x01(\x02\x12 \n\x18presence_detection_level\x18\x08 \x01(\x02\x12\x1b\n\x13inlet_temperature_c\x18\t \x01(\x02\x12\x1c\n\x14outlet_temperature_c\x18\n \x01(\x02\x12 \n\x18\x61mbient_humidity_percent\x18\x0b \x01(\x02\x12\x37\n\thvac_mode\x18\x0c \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12(\n calculated_ambient_temperature_c\x18\r \x01(\x02\x12$\n\x1clouver_angle_up_down_degrees\x18\x0e \x01(\x02\"\xbf\x07\n\x14IndoorUnitConditions\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x41\n\rmode_conflict\x18\x02 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12\x42\n\x0e\x61nti_cold_wind\x18\x03 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12T\n abnormal_outdoor_air_temperature\x18\x04 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12M\n\x19hvac_mode_switching_delay\x18\x05 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12\x41\n\rdefrost_cycle\x18\x06 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12\x42\n\x0esafety_heating\x18\x07 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12>\n\noil_return\x18\x08 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12N\n\x1amodbus_communication_error\x18\t \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12@\n\x0c\x63oil_preheat\x18\n \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12K\n\x17mode_conflict_avoidance\x18\x0b \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12T\n outdoor_unit_communication_error\x18\x0c \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12O\n\x1b\x63ompressor_minimum_run_time\x18\r \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\"\xc9\x01\n\x17IndoorUnitPresenceState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12>\n\x10sensor0_presence\x18\x02 \x01(\x0e\x32$.core.protos.home_datastore.Presence\x12>\n\x10sensor1_presence\x18\x03 \x01(\x0e\x32$.core.protos.home_datastore.Presence\"\x9a\x01\n\x12IndoorUnitCommands\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12T\n\x18\x66\x61llback_control_command\x18\x02 \x01(\x0e\x32\x32.core.protos.home_datastore.FallbackControlCommand\"\xad\x02\n\x1cIndoorUnitHardwareAttributes\x12\x11\n\tmodel_sku\x18\x01 \x01(\t\x12\x15\n\rserial_number\x18\x02 \x01(\t\x12\x31\n\rproduction_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x66irmware_version\x18\x04 \x01(\t\x12\x19\n\x11\x66\x61n_speed_max_rpm\x18\x05 \x01(\x02\x12.\n\nupdated_ts\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12!\n\x19indoor_unit_serial_number\x18\x07 \x01(\t\x12(\n quilt_smart_module_serial_number\x18\x08 \x01(\t\"\x9e\x01\n\x12IndoorUnitHardware\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12L\n\nattributes\x18\x02 \x01(\x0b\x32\x38.core.protos.home_datastore.IndoorUnitHardwareAttributes\"\xf4\x02\n\x14IndoorUnitHvacInputs\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x1e\x65xternal_ambient_temperature_c\x18\x02 \x01(\x02\x12\"\n\x1a\x61mbient_temperature_source\x18\x03 \x01(\x05\x12\x1e\n\x16temperature_setpoint_c\x18\x04 \x01(\x02\x12\x37\n\thvac_mode\x18\x05 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12L\n\x14hvac_controller_type\x18\x06 \x01(\x0e\x32..core.protos.home_datastore.HvacControllerType\x12\x39\n\nhvac_state\x18\x07 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\"\xd1\x03\n\x19IndoorUnitPerformanceData\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16measurement_interval_s\x18\x02 \x01(\x02\x12\x1c\n\x14\x65nergy_measurement_j\x18\x03 \x01(\x02\x12\x37\n\thvac_mode\x18\x04 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x1c\n\x14\x61\x63tual_fan_speed_rpm\x18\x05 \x01(\x02\x12\x1c\n\x14outlet_temperature_c\x18\x06 \x01(\x02\x12\x1a\n\x12inlet_humidity_pct\x18\x07 \x01(\x02\x12\x1b\n\x13inlet_temperature_c\x18\x08 \x01(\x02\x12\x1a\n\x12\x63oil_temperature_c\x18\t \x01(\x02\x12\x1e\n\x16gas_pipe_temperature_c\x18\n \x01(\x02\x12!\n\x19liquid_pipe_temperature_c\x18\x0b \x01(\x02\x12\x39\n\nhvac_state\x18\x0c \x01(\x0e\x32%.core.protos.home_datastore.HVACState\"\x8a\x03\n\x1cIndoorUnitPerformanceMetrics\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16measurement_duration_s\x18\x0b \x01(\x02\x12\x16\n\x0e\x65nergy_total_j\x18\x04 \x01(\x02\x12\x12\n\ncapacity_w\x18\x05 \x01(\x02\x12\"\n\x1a\x63oefficient_of_performance\x18\x06 \x01(\x02\x12\x37\n\thvac_mode\x18\x07 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x14\n\x0chvac_power_w\x18\x08 \x01(\x02\x12\x13\n\x0bled_power_w\x18\t \x01(\x02\x12\x39\n\nhvac_state\x18\n \x01(\x0e\x32%.core.protos.home_datastore.HVACState\x12\x15\n\rhvac_energy_j\x18\x0c \x01(\x02\x12\x14\n\x0cled_energy_j\x18\r \x01(\x02\"\x8a\x01\n\x13IndoorUnitOccupancy\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x43\n\x0foccupancy_state\x18\x02 \x01(\x0e\x32*.core.protos.home_datastore.OccupancyState\"\xd6\x06\n\nIndoorUnit\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12J\n\rrelationships\x18\x02 \x01(\x0b\x32\x33.core.protos.home_datastore.IndoorUnitRelationships\x12@\n\x08settings\x18\x03 \x01(\x0b\x32..core.protos.home_datastore.IndoorUnitSettings\x12@\n\x08\x63ontrols\x18\x04 \x01(\x0b\x32..core.protos.home_datastore.IndoorUnitControls\x12:\n\x05state\x18\x05 \x01(\x0b\x32+.core.protos.home_datastore.IndoorUnitState\x12\x45\n\x0bhvac_inputs\x18\x06 \x01(\x0b\x32\x30.core.protos.home_datastore.IndoorUnitHvacInputs\x12\x45\n\x08presence\x18\x07 \x01(\x0b\x32\x33.core.protos.home_datastore.IndoorUnitPresenceState\x12\x44\n\nconditions\x18\x08 \x01(\x0b\x32\x30.core.protos.home_datastore.IndoorUnitConditions\x12@\n\x08\x63ommands\x18\t \x01(\x0b\x32..core.protos.home_datastore.IndoorUnitCommands\x12O\n\x10performance_data\x18\n \x01(\x0b\x32\x35.core.protos.home_datastore.IndoorUnitPerformanceData\x12U\n\x13performance_metrics\x18\x0b \x01(\x0b\x32\x38.core.protos.home_datastore.IndoorUnitPerformanceMetrics\x12\x42\n\toccupancy\x18\x0c \x01(\x0b\x32/.core.protos.home_datastore.IndoorUnitOccupancy\"\xaf\x01\n\x18OutdoorUnitRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x10\n\x08space_id\x18\x02 \x01(\t\x12\x13\n\x0bhardware_id\x18\x03 \x01(\t\x12.\n\nupdated_ts\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17\x66irmware_update_info_id\x18\x05 \x01(\t\"h\n\x13OutdoorUnitSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"}\n\x10OutdoorUnitState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x39\n\nhvac_state\x18\x02 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\"\xba\x02\n\x1aOutdoorUnitPerformanceData\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16measurement_interval_s\x18\x02 \x01(\x02\x12\x1c\n\x14\x65nergy_measurement_j\x18\x03 \x01(\x02\x12\x1f\n\x17\x63ompressor_frequency_hz\x18\x04 \x01(\x02\x12\x1d\n\x15\x61mbient_temperature_c\x18\x05 \x01(\x02\x12\x1a\n\x12\x63oil_temperature_c\x18\x06 \x01(\x02\x12\x1d\n\x15\x65xhaust_temperature_c\x18\x07 \x01(\x02\x12\x19\n\x11high_pressure_kpa\x18\x08 \x01(\x02\x12\x18\n\x10low_pressure_kpa\x18\t \x01(\x02\"\xc6\x01\n\x1dOutdoorUnitHardwareAttributes\x12\x11\n\tmodel_sku\x18\x01 \x01(\t\x12\x15\n\rserial_number\x18\x02 \x01(\t\x12\x31\n\rproduction_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x66irmware_version\x18\x04 \x01(\t\x12.\n\nupdated_ts\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa0\x01\n\x13OutdoorUnitHardware\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12M\n\nattributes\x18\x02 \x01(\x0b\x32\x39.core.protos.home_datastore.OutdoorUnitHardwareAttributes\"\xe8\x02\n\x0bOutdoorUnit\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12K\n\rrelationships\x18\x02 \x01(\x0b\x32\x34.core.protos.home_datastore.OutdoorUnitRelationships\x12\x41\n\x08settings\x18\x03 \x01(\x0b\x32/.core.protos.home_datastore.OutdoorUnitSettings\x12;\n\x05state\x18\x04 \x01(\x0b\x32,.core.protos.home_datastore.OutdoorUnitState\x12P\n\x10performance_data\x18\x05 \x01(\x0b\x32\x36.core.protos.home_datastore.OutdoorUnitPerformanceData\"\xcf\x01\n\x17\x43ontrollerRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x10\n\x08space_id\x18\x02 \x01(\t\x12\x13\n\x0bhardware_id\x18\x03 \x01(\t\x12.\n\nupdated_ts\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17software_update_info_id\x18\x05 \x01(\t\x12\x1f\n\x17\x66irmware_update_info_id\x18\x06 \x01(\t\"g\n\x12\x43ontrollerSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa8\x01\n\x0f\x43ontrollerState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15\x61mbient_temperature_c\x18\x02 \x01(\x02\x12\x16\n\x0etemperature_f3\x18\x03 \x01(\x02\x12\x16\n\x0etemperature_f4\x18\x04 \x01(\x02\x12\x16\n\x0etemperature_f5\x18\x05 \x01(\x02\"\x9d\x01\n\x12\x43ontrollerControls\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12W\n\x1aremote_sensor_control_mode\x18\x02 \x01(\x0e\x32\x33.core.protos.home_datastore.RemoteSensorControlMode\"\xde\x02\n\tWifiState\x12\x43\n\nwifi_state\x18\x01 \x01(\x0e\x32/.core.protos.home_datastore.WifiConnectionState\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\r\n\x05\x62ssid\x18\x03 \x01(\t\x12\x15\n\rfrequency_mhz\x18\x04 \x01(\x05\x12\x12\n\npassphrase\x18\x05 \x01(\t\x12\x18\n\x10signal_level_dbm\x18\x06 \x01(\x05\x12\x17\n\x0fnoise_level_dbm\x18\x07 \x01(\x05\x12\x17\n\x0frx_invalid_frag\x18\x08 \x01(\x05\x12\x1c\n\x14tx_excessive_retries\x18\t \x01(\x05\x12\x14\n\x0cipv4_address\x18\n \x01(\t\x12\x14\n\x0cipv6_address\x18\x0b \x01(\t\x12.\n\nupdated_ts\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xc5\x01\n\x1c\x43ontrollerHardwareAttributes\x12\x11\n\tmodel_sku\x18\x01 \x01(\t\x12\x15\n\rserial_number\x18\x02 \x01(\t\x12\x31\n\rproduction_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x66irmware_version\x18\x04 \x01(\t\x12.\n\nupdated_ts\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x9e\x01\n\x12\x43ontrollerHardware\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12L\n\nattributes\x18\x02 \x01(\x0b\x32\x38.core.protos.home_datastore.ControllerHardwareAttributes\"\xdd\x04\n\nController\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12J\n\rrelationships\x18\x02 \x01(\x0b\x32\x33.core.protos.home_datastore.ControllerRelationships\x12@\n\x08settings\x18\x03 \x01(\x0b\x32..core.protos.home_datastore.ControllerSettings\x12:\n\x05state\x18\x04 \x01(\x0b\x32+.core.protos.home_datastore.ControllerState\x12@\n\x11hosted_wifi_state\x18\x05 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12<\n\rap_wifi_state\x18\x06 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12=\n\x0ep2p_wifi_state\x18\x07 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12@\n\x08\x63ontrols\x18\x08 \x01(\x0b\x32..core.protos.home_datastore.ControllerControls\x12H\n\x12local_comms_status\x18\t \x01(\x0b\x32,.core.protos.home_datastore.LocalCommsStatus\"U\n\x16RemoteSensorAttributes\x12\x0b\n\x03mac\x18\x01 \x01(\t\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb5\x01\n\x11RemoteSensorState\x12\x1d\n\x15\x61mbient_temperature_c\x18\x01 \x01(\x02\x12\x18\n\x10humidity_percent\x18\x02 \x01(\x02\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15\x62\x61ttery_level_percent\x18\x04 \x01(\x02\x12\x18\n\x10signal_level_dbm\x18\x05 \x01(\x05\"c\n\x19RemoteSensorRelationships\x12\x16\n\x0eindoor_unit_id\x18\x01 \x01(\t\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"l\n#ControllerRemoteSensorRelationships\x12\x15\n\rcontroller_id\x18\x01 \x01(\t\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x91\x01\n\x14RemoteSensorControls\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12I\n\x0c\x63ontrol_mode\x18\x02 \x01(\x0e\x32\x33.core.protos.home_datastore.RemoteSensorControlMode\"\xe2\x02\n\x0cRemoteSensor\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x46\n\nattributes\x18\x02 \x01(\x0b\x32\x32.core.protos.home_datastore.RemoteSensorAttributes\x12<\n\x05state\x18\x03 \x01(\x0b\x32-.core.protos.home_datastore.RemoteSensorState\x12L\n\rrelationships\x18\x04 \x01(\x0b\x32\x35.core.protos.home_datastore.RemoteSensorRelationships\x12\x42\n\x08\x63ontrols\x18\x05 \x01(\x0b\x32\x30.core.protos.home_datastore.RemoteSensorControls\"\xf6\x02\n\x16\x43ontrollerRemoteSensor\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x46\n\nattributes\x18\x02 \x01(\x0b\x32\x32.core.protos.home_datastore.RemoteSensorAttributes\x12<\n\x05state\x18\x03 \x01(\x0b\x32-.core.protos.home_datastore.RemoteSensorState\x12V\n\rrelationships\x18\x04 \x01(\x0b\x32?.core.protos.home_datastore.ControllerRemoteSensorRelationships\x12\x42\n\x08\x63ontrols\x18\x05 \x01(\x0b\x32\x30.core.protos.home_datastore.RemoteSensorControls\"\xe2\x03\n\x18\x43omfortSettingAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0c\n\x04name\x18\x02 \x01(\t\x12@\n\x0e\x66\x61n_speed_mode\x18\x03 \x01(\x0e\x32(.core.protos.home_datastore.FanSpeedMode\x12\x19\n\x11\x66\x61n_speed_percent\x18\x04 \x01(\x02\x12&\n\x1eheating_temperature_setpoint_c\x18\x05 \x01(\x02\x12&\n\x1e\x63ooling_temperature_setpoint_c\x18\x06 \x01(\x02\x12<\n\x04type\x18\x07 \x01(\x0e\x32..core.protos.home_datastore.ComfortSettingType\x12\x37\n\thvac_mode\x18\x08 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x45\n\x0blouver_mode\x18\t \x01(\x0e\x32\x30.core.protos.home_datastore.IndoorUnitLouverMode\x12\x1d\n\x15louver_fixed_position\x18\n \x01(\x02\"_\n\x1b\x43omfortSettingRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08space_id\x18\x02 \x01(\t\"\xe6\x01\n\x0e\x43omfortSetting\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12H\n\nattributes\x18\x02 \x01(\x0b\x32\x34.core.protos.home_datastore.ComfortSettingAttributes\x12N\n\rrelationships\x18\x03 \x01(\x0b\x32\x37.core.protos.home_datastore.ComfortSettingRelationships\"\xd0\x01\n\x10LedScheduleEvent\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07start_s\x18\x02 \x01(\x05\x12\x16\n\x0eled_color_code\x18\x03 \x01(\r\x12$\n\x1cled_color_brightness_percent\x18\x04 \x01(\x02\x12=\n\tanimation\x18\x05 \x01(\x0e\x32*.core.protos.home_datastore.LightAnimation\"\x99\x04\n\rScheduleEvent\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1a\n\x12\x63omfort_setting_id\x18\x02 \x01(\t\x12\x0f\n\x07start_s\x18\x03 \x01(\x05\x12\x14\n\x0cprecondition\x18\x04 \x01(\x08\x12I\n\x12schedule_execution\x18\x05 \x01(\x0e\x32-.core.protos.home_datastore.ScheduleExecution\x12@\n\x0e\x66\x61n_speed_mode\x18\x06 \x01(\x0e\x32(.core.protos.home_datastore.FanSpeedMode\x12\x19\n\x11\x66\x61n_speed_percent\x18\x07 \x01(\x02\x12&\n\x1eheating_temperature_setpoint_c\x18\x08 \x01(\x02\x12&\n\x1e\x63ooling_temperature_setpoint_c\x18\t \x01(\x02\x12\x37\n\thvac_mode\x18\n \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x45\n\x0blouver_mode\x18\x0b \x01(\x0e\x32\x30.core.protos.home_datastore.IndoorUnitLouverMode\x12\x1d\n\x15louver_fixed_position\x18\x0c \x01(\x02\"U\n\x15ScheduleDayAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0c\n\x04name\x18\x02 \x01(\t\"\\\n\x18ScheduleDayRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08space_id\x18\x02 \x01(\t\"\xda\x02\n\x0bScheduleDay\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x45\n\nattributes\x18\x02 \x01(\x0b\x32\x31.core.protos.home_datastore.ScheduleDayAttributes\x12\x39\n\x06\x65vents\x18\x03 \x03(\x0b\x32).core.protos.home_datastore.ScheduleEvent\x12K\n\rrelationships\x18\x04 \x01(\x0b\x32\x34.core.protos.home_datastore.ScheduleDayRelationships\x12@\n\nled_events\x18\x05 \x03(\x0b\x32,.core.protos.home_datastore.LedScheduleEvent\"\x87\x01\n\x0fScheduleWeekDay\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06\x64\x61y_id\x18\x02 \x01(\t\x12\x34\n\x07weekday\x18\x03 \x01(\x0e\x32#.core.protos.home_datastore.Weekday\"]\n\x19ScheduleWeekRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08space_id\x18\x02 \x01(\t\"\xd3\x01\n\x0cScheduleWeek\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x39\n\x04\x64\x61ys\x18\x02 \x03(\x0b\x32+.core.protos.home_datastore.ScheduleWeekDay\x12L\n\rrelationships\x18\x03 \x01(\x0b\x32\x35.core.protos.home_datastore.ScheduleWeekRelationships\"\x91\x01\n\x1dQuiltSmartModuleRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17software_update_info_id\x18\x02 \x01(\t\x12\x1f\n\x17\x66irmware_update_info_id\x18\x03 \x01(\t\"b\n\x18QuiltSmartModuleControls\x12\x16\n\x0eled_color_code\x18\x01 \x01(\x05\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x86\x02\n\x15QuiltSmartModuleState\x12\x1a\n\x12phase_detected_raw\x18\x01 \x01(\x02\x12\x1b\n\x13target_detected_raw\x18\x02 \x01(\x02\x12\x1b\n\x13\x61ls_illuminance_raw\x18\x03 \x01(\x05\x12\x12\n\nals_ir_raw\x18\x04 \x01(\x05\x12\x14\n\x0c\x61ls_both_raw\x18\x05 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65l_x_raw\x18\x06 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65l_y_raw\x18\x07 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65l_z_raw\x18\x08 \x01(\x05\x12.\n\nupdated_ts\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb3\x04\n\x10QuiltSmartModule\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x46\n\x08\x63ontrols\x18\x02 \x01(\x0b\x32\x34.core.protos.home_datastore.QuiltSmartModuleControls\x12@\n\x05state\x18\x03 \x01(\x0b\x32\x31.core.protos.home_datastore.QuiltSmartModuleState\x12@\n\x11hosted_wifi_state\x18\x04 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12<\n\rap_wifi_state\x18\x05 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12=\n\x0ep2p_wifi_state\x18\x06 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12P\n\rrelationships\x18\x07 \x01(\x0b\x32\x39.core.protos.home_datastore.QuiltSmartModuleRelationships\x12H\n\x12local_comms_status\x18\x08 \x01(\x0b\x32,.core.protos.home_datastore.LocalCommsStatus\"\xe7\x01\n\x1cSoftwareUpdateInfoAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05state\x18\x02 \x01(\x05\x12\x0e\n\x06status\x18\x03 \x01(\x05\x12\x17\n\x0f\x63urrent_version\x18\x04 \x01(\t\x12\x16\n\x0etarget_version\x18\x05 \x01(\t\x12\x18\n\x10\x63urrent_progress\x18\x06 \x01(\x02\x12\x16\n\x0etotal_progress\x18\x07 \x01(\x02\x12\x15\n\rprogress_unit\x18\x08 \x01(\x05\"\x9e\x01\n\x12SoftwareUpdateInfo\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12L\n\nattributes\x18\x02 \x01(\x0b\x32\x38.core.protos.home_datastore.SoftwareUpdateInfoAttributes\"\xa2\x08\n\x13HomeDatastoreSystem\x12\x31\n\x06spaces\x18\x03 \x03(\x0b\x32!.core.protos.home_datastore.Space\x12N\n\x15outdoor_unit_hardware\x18\x05 \x03(\x0b\x32/.core.protos.home_datastore.OutdoorUnitHardware\x12>\n\routdoor_units\x18\x06 \x03(\x0b\x32\'.core.protos.home_datastore.OutdoorUnit\x12I\n\x13quilt_smart_modules\x18\x07 \x03(\x0b\x32,.core.protos.home_datastore.QuiltSmartModule\x12L\n\x14indoor_unit_hardware\x18\x08 \x03(\x0b\x32..core.protos.home_datastore.IndoorUnitHardware\x12<\n\x0cindoor_units\x18\t \x03(\x0b\x32&.core.protos.home_datastore.IndoorUnit\x12K\n\x13\x63ontroller_hardware\x18\n \x03(\x0b\x32..core.protos.home_datastore.ControllerHardware\x12;\n\x0b\x63ontrollers\x18\x0b \x03(\x0b\x32&.core.protos.home_datastore.Controller\x12@\n\x0eremote_sensors\x18\x0c \x03(\x0b\x32(.core.protos.home_datastore.RemoteSensor\x12\x44\n\x10\x63omfort_settings\x18\r \x03(\x0b\x32*.core.protos.home_datastore.ComfortSetting\x12>\n\rschedule_days\x18\x0e \x03(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\x12@\n\x0eschedule_weeks\x18\x0f \x03(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\x12U\n\x19\x63ontroller_remote_sensors\x18\x10 \x03(\x0b\x32\x32.core.protos.home_datastore.ControllerRemoteSensor\x12\x37\n\tlocations\x18\x11 \x03(\x0b\x32$.core.protos.home_datastore.Location\x12M\n\x15software_update_infos\x18\x12 \x03(\x0b\x32..core.protos.home_datastore.SoftwareUpdateInfo\"b\n\x1dGetHomeDatastoreSystemRequest\x12\x11\n\tsystem_id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"\x1d\n\x0fGetSpaceRequest\x12\n\n\x02id\x18\x01 \x01(\t\"F\n\x12\x43reateSpaceRequest\x12\x30\n\x05space\x18\x01 \x01(\x0b\x32!.core.protos.home_datastore.Space\"E\n\x12UpdateSpaceRequest\x12/\n\x04\x64iff\x18\x01 \x01(\x0b\x32!.core.protos.home_datastore.Space\" \n\x12\x44\x65leteSpaceRequest\x12\n\n\x02id\x18\x01 \x01(\t\"O\n\x17UpdateIndoorUnitRequest\x12\x34\n\x04\x64iff\x18\x01 \x01(\x0b\x32&.core.protos.home_datastore.IndoorUnit\"%\n\x17\x44\x65leteIndoorUnitRequest\x12\n\n\x02id\x18\x01 \x01(\t\"Z\n\x1cGetIndoorUnitHardwareRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"U\n\x17UpdateControllerRequest\x12:\n\ncontroller\x18\x01 \x01(\x0b\x32&.core.protos.home_datastore.Controller\"%\n\x17\x44\x65leteControllerRequest\x12\n\n\x02id\x18\x01 \x01(\t\"Z\n\x1cGetControllerHardwareRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"&\n\x18\x44\x65leteOutdoorUnitRequest\x12\n\n\x02id\x18\x01 \x01(\t\"X\n\x1aGetQuiltSmartModuleRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"\\\n\x19\x43reateRemoteSensorRequest\x12?\n\rremote_sensor\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.RemoteSensor\"\\\n\x19UpdateRemoteSensorRequest\x12?\n\rremote_sensor\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.RemoteSensor\"5\n\x19\x44\x65leteRemoteSensorRequest\x12\x18\n\x10remote_sensor_id\x18\x01 \x01(\t\"p\n#CreateControllerRemoteSensorRequest\x12I\n\rremote_sensor\x18\x01 \x01(\x0b\x32\x32.core.protos.home_datastore.ControllerRemoteSensor\"p\n#UpdateControllerRemoteSensorRequest\x12I\n\rremote_sensor\x18\x01 \x01(\x0b\x32\x32.core.protos.home_datastore.ControllerRemoteSensor\"?\n#DeleteControllerRemoteSensorRequest\x12\x18\n\x10remote_sensor_id\x18\x01 \x01(\t\"Y\n\x18\x43reateScheduleDayRequest\x12=\n\x0cschedule_day\x18\x01 \x01(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\"0\n\x15GetScheduleDayRequest\x12\x17\n\x0fschedule_day_id\x18\x01 \x01(\t\"Y\n\x18UpdateScheduleDayRequest\x12=\n\x0cschedule_day\x18\x01 \x01(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\"3\n\x18\x44\x65leteScheduleDayRequest\x12\x17\n\x0fschedule_day_id\x18\x01 \x01(\t\")\n\x17ListScheduleDaysRequest\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\"Z\n\x18ListScheduleDaysResponse\x12>\n\rschedule_days\x18\x01 \x03(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\"\\\n\x19\x43reateScheduleWeekRequest\x12?\n\rschedule_week\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\"2\n\x16GetScheduleWeekRequest\x12\x18\n\x10schedule_week_id\x18\x01 \x01(\t\"\\\n\x19UpdateScheduleWeekRequest\x12?\n\rschedule_week\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\"5\n\x19\x44\x65leteScheduleWeekRequest\x12\x18\n\x10schedule_week_id\x18\x01 \x01(\t\"*\n\x18ListScheduleWeeksRequest\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\"]\n\x19ListScheduleWeeksResponse\x12@\n\x0eschedule_weeks\x18\x01 \x03(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\"b\n\x1b\x43reateComfortSettingRequest\x12\x43\n\x0f\x63omfort_setting\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.ComfortSetting\"6\n\x18GetComfortSettingRequest\x12\x1a\n\x12\x63omfort_setting_id\x18\x01 \x01(\t\"b\n\x1bUpdateComfortSettingRequest\x12\x43\n\x0f\x63omfort_setting\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.ComfortSetting\"9\n\x1b\x44\x65leteComfortSettingRequest\x12\x1a\n\x12\x63omfort_setting_id\x18\x01 \x01(\t\",\n\x1aListComfortSettingsRequest\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\"c\n\x1bListComfortSettingsResponse\x12\x44\n\x10\x63omfort_settings\x18\x01 \x03(\x0b\x32*.core.protos.home_datastore.ComfortSetting\"O\n\x15\x43reateLocationRequest\x12\x36\n\x08location\x18\x01 \x01(\x0b\x32$.core.protos.home_datastore.Location\")\n\x12GetLocationRequest\x12\x13\n\x0blocation_id\x18\x01 \x01(\t\"O\n\x15UpdateLocationRequest\x12\x36\n\x08location\x18\x01 \x01(\x0b\x32$.core.protos.home_datastore.Location\",\n\x15\x44\x65leteLocationRequest\x12\x13\n\x0blocation_id\x18\x01 \x01(\t\")\n\x14ListLocationsRequest\x12\x11\n\tsystem_id\x18\x01 \x01(\t\"P\n\x15ListLocationsResponse\x12\x37\n\tlocations\x18\x01 \x03(\x0b\x32$.core.protos.home_datastore.Location*\xd7\x01\n\x08HVACMode\x12\x19\n\x15HVAC_MODE_UNSPECIFIED\x10\x00\x12\x15\n\x11HVAC_MODE_STANDBY\x10\x01\x12\x12\n\x0eHVAC_MODE_COOL\x10\x02\x12\x12\n\x0eHVAC_MODE_HEAT\x10\x03\x12\x12\n\x0eHVAC_MODE_AUTO\x10\x04\x12\x11\n\rHVAC_MODE_FAN\x10\x05\x12\x1b\n\x17HVAC_MODE_FALLBACK_AUTO\x10\x06\x12\x1a\n\x16HVAC_MODE_FALLBACK_OFF\x10\x07\x12\x11\n\rHVAC_MODE_DRY\x10\x08*\xf9\x02\n\tHVACState\x12\x1a\n\x16HVAC_STATE_UNSPECIFIED\x10\x00\x12\x16\n\x12HVAC_STATE_STANDBY\x10\x01\x12\x13\n\x0fHVAC_STATE_COOL\x10\x02\x12\x13\n\x0fHVAC_STATE_HEAT\x10\x03\x12\x14\n\x10HVAC_STATE_DRIFT\x10\x04\x12\x12\n\x0eHVAC_STATE_FAN\x10\x05\x12\x1c\n\x18HVAC_STATE_COOL_DEFERRED\x10\x06\x12\x1c\n\x18HVAC_STATE_HEAT_DEFERRED\x10\x07\x12\x1b\n\x17HVAC_STATE_FAN_DEFERRED\x10\x08\x12\x1d\n\x19HVAC_STATE_COOL_PREPARING\x10\t\x12\x1d\n\x19HVAC_STATE_HEAT_PREPARING\x10\n\x12\x12\n\x0eHVAC_STATE_DRY\x10\x0b\x12\x1b\n\x17HVAC_STATE_DRY_DEFERRED\x10\x0c\x12\x1c\n\x18HVAC_STATE_DRY_PREPARING\x10\r*\xe4\x01\n\x16LocalCommsHealthStatus\x12)\n%LOCAL_COMMS_HEALTH_STATUS_UNSPECIFIED\x10\x00\x12%\n!LOCAL_COMMS_HEALTH_STATUS_HEALTHY\x10\x01\x12&\n\"LOCAL_COMMS_HEALTH_STATUS_DEGRADED\x10\x02\x12%\n!LOCAL_COMMS_HEALTH_STATUS_OFFLINE\x10\x03\x12)\n%LOCAL_COMMS_HEALTH_STATUS_STARTING_UP\x10\x04*\xda\x04\n\x16LocalCommsHealthReason\x12)\n%LOCAL_COMMS_HEALTH_REASON_UNSPECIFIED\x10\x00\x12%\n!LOCAL_COMMS_HEALTH_REASON_HEALTHY\x10\x01\x12,\n(LOCAL_COMMS_HEALTH_REASON_BOOT_OR_WARMUP\x10\x02\x12\x30\n,LOCAL_COMMS_HEALTH_REASON_ZENOH_SESSION_DOWN\x10\x03\x12.\n*LOCAL_COMMS_HEALTH_REASON_NO_PEERS_VISIBLE\x10\x04\x12\x30\n,LOCAL_COMMS_HEALTH_REASON_PARTIAL_VISIBILITY\x10\x05\x12\x32\n.LOCAL_COMMS_HEALTH_REASON_NO_MESSAGES_RECEIVED\x10\x06\x12\x31\n-LOCAL_COMMS_HEALTH_REASON_SESSION_INSTABILITY\x10\x07\x12,\n(LOCAL_COMMS_HEALTH_REASON_NO_ALIVE_PEERS\x10\x08\x12/\n+LOCAL_COMMS_HEALTH_REASON_NO_PEERS_EXPECTED\x10\t\x12\x33\n/LOCAL_COMMS_HEALTH_REASON_CREDENTIALS_NOT_READY\x10\n\x12\x31\n-LOCAL_COMMS_HEALTH_REASON_CREDENTIALS_EXPIRED\x10\x0b*h\n\rOccupancyMode\x12\x1e\n\x1aOCCUPANCY_MODE_UNSPECIFIED\x10\x00\x12\x1b\n\x17OCCUPANCY_MODE_DISABLED\x10\x01\x12\x1a\n\x16OCCUPANCY_MODE_ENABLED\x10\x02*{\n\x11SafetyHeatingMode\x12#\n\x1fSAFETY_HEATING_MODE_UNSPECIFIED\x10\x00\x12 \n\x1cSAFETY_HEATING_MODE_DISABLED\x10\x01\x12\x1f\n\x1bSAFETY_HEATING_MODE_ENABLED\x10\x02*\xd1\x01\n\x12HvacControllerType\x12$\n HVAC_CONTROLLER_TYPE_UNSPECIFIED\x10\x00\x12\x31\n-HVAC_CONTROLLER_TYPE_PASS_THROUGH_TEMPERATURE\x10\x01\x12\x30\n,HVAC_CONTROLLER_TYPE_INTEGRAL_TEMPERATURE_V1\x10\x02\x12\x30\n,HVAC_CONTROLLER_TYPE_INTEGRAL_TEMPERATURE_V2\x10\x03*\xb7\x02\n\x16\x43omfortSettingOverride\x12(\n$COMFORT_SETTING_OVERRIDE_UNSPECIFIED\x10\x00\x12!\n\x1d\x43OMFORT_SETTING_OVERRIDE_NONE\x10\x01\x12\x30\n,COMFORT_SETTING_OVERRIDE_UNTIL_NEXT_SCHEDULE\x10\x02\x12\'\n#COMFORT_SETTING_OVERRIDE_INDEFINITE\x10\x03\x12%\n!COMFORT_SETTING_OVERRIDE_SCHEDULE\x10\x04\x12\'\n#COMFORT_SETTING_OVERRIDE_UNOCCUPIED\x10\x05\x12%\n!COMFORT_SETTING_OVERRIDE_OCCUPIED\x10\x06*\xdd\x01\n\x12\x43omfortSettingType\x12$\n COMFORT_SETTING_TYPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1b\x43OMFORT_SETTING_TYPE_ACTIVE\x10\x01\x12\x1e\n\x1a\x43OMFORT_SETTING_TYPE_SLEEP\x10\x02\x12\x1d\n\x19\x43OMFORT_SETTING_TYPE_AWAY\x10\x03\x12 \n\x1c\x43OMFORT_SETTING_TYPE_STANDBY\x10\x04\x12\x1f\n\x1b\x43OMFORT_SETTING_TYPE_CUSTOM\x10\x05*\x8f\x01\n\x14IndoorUnitLouverMode\x12\x1b\n\x17LOUVER_MODE_UNSPECIFIED\x10\x00\x12\x16\n\x12LOUVER_MODE_CLOSED\x10\x01\x12\x15\n\x11LOUVER_MODE_SWEEP\x10\x02\x12\x15\n\x11LOUVER_MODE_FIXED\x10\x03\x12\x14\n\x10LOUVER_MODE_AUTO\x10\x04*d\n\x0c\x46\x61nSpeedMode\x12\x1e\n\x1a\x46\x41N_SPEED_MODE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x46\x41N_SPEED_MODE_AUTO\x10\x01\x12\x1b\n\x17\x46\x41N_SPEED_MODE_SETPOINT\x10\x02*N\n\tBoostMode\x12\x1a\n\x16\x42OOST_MODE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x42OOST_MODE_OFF\x10\x01\x12\x11\n\rBOOST_MODE_ON\x10\x02*\xc5\x01\n\x0eLightAnimation\x12\x1f\n\x1bLIGHT_ANIMATION_UNSPECIFIED\x10\x00\x12\x18\n\x14LIGHT_ANIMATION_NONE\x10\x01\x12 \n\x1cLIGHT_ANIMATION_SPARKLE_FADE\x10\x02\x12 \n\x1cLIGHT_ANIMATION_TWINKLE_FADE\x10\x03\x12\x19\n\x15LIGHT_ANIMATION_DANCE\x10\x04\x12\x19\n\x15LIGHT_ANIMATION_CHASE\x10\x05*R\n\nLightState\x12\x1b\n\x17LIGHT_STATE_UNSPECIFIED\x10\x00\x12\x12\n\x0eLIGHT_STATE_ON\x10\x01\x12\x13\n\x0fLIGHT_STATE_OFF\x10\x02*k\n\x0e\x43onditionState\x12\x1f\n\x1b\x43ONDITION_STATE_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x43ONDITION_STATE_INACTIVE\x10\x01\x12\x1a\n\x16\x43ONDITION_STATE_ACTIVE\x10\x02*T\n\x08Presence\x12\x18\n\x14PRESENCE_UNSPECIFIED\x10\x00\x12\x17\n\x13PRESENCE_UNDETECTED\x10\x01\x12\x15\n\x11PRESENCE_DETECTED\x10\x02*\x96\x01\n\x17RemoteSensorControlMode\x12*\n&REMOTE_SENSOR_CONTROL_MODE_UNSPECIFIED\x10\x00\x12\'\n#REMOTE_SENSOR_CONTROL_MODE_DISABLED\x10\x01\x12&\n\"REMOTE_SENSOR_CONTROL_MODE_ENABLED\x10\x02*\xb6\x01\n\x07Weekday\x12\x17\n\x13WEEKDAY_UNSPECIFIED\x10\x00\x12\x12\n\x0eWEEKDAY_MONDAY\x10\x01\x12\x13\n\x0fWEEKDAY_TUESDAY\x10\x02\x12\x15\n\x11WEEKDAY_WEDNESDAY\x10\x03\x12\x14\n\x10WEEKDAY_THURSDAY\x10\x04\x12\x12\n\x0eWEEKDAY_FRIDAY\x10\x05\x12\x14\n\x10WEEKDAY_SATURDAY\x10\x06\x12\x12\n\x0eWEEKDAY_SUNDAY\x10\x07*v\n\x11ScheduleExecution\x12\"\n\x1eSCHEDULE_EXECUTION_UNSPECIFIED\x10\x00\x12\x1e\n\x1aSCHEDULE_EXECUTION_RUNNING\x10\x01\x12\x1d\n\x19SCHEDULE_EXECUTION_PAUSED\x10\x02*\x8c\x01\n\x16\x46\x61llbackControlCommand\x12(\n$FALLBACK_CONTROL_COMMAND_UNSPECIFIED\x10\x00\x12%\n!FALLBACK_CONTROL_COMMAND_COMPLETE\x10\x01\x12!\n\x1d\x46\x41LLBACK_CONTROL_COMMAND_EXIT\x10\x02*o\n\x0eOccupancyState\x12\x1f\n\x1bOCCUPANCY_STATE_UNSPECIFIED\x10\x00\x12\x1e\n\x1aOCCUPANCY_STATE_UNDETECTED\x10\x01\x12\x1c\n\x18OCCUPANCY_STATE_DETECTED\x10\x02*\xd6\x02\n\x13WifiConnectionState\x12\x1a\n\x16WIFI_STATE_UNSPECIFIED\x10\x00\x12\x1b\n\x17WIFI_STATE_DISCONNECTED\x10\x01\x12!\n\x1dWIFI_STATE_INTERFACE_DISABLED\x10\x02\x12\x17\n\x13WIFI_STATE_INACTIVE\x10\x03\x12\x17\n\x13WIFI_STATE_SCANNING\x10\x04\x12\x1d\n\x19WIFI_STATE_AUTHENTICATING\x10\x05\x12\x1a\n\x16WIFI_STATE_ASSOCIATING\x10\x06\x12\x19\n\x15WIFI_STATE_ASSOCIATED\x10\x07\x12\x1d\n\x19WIFI_STATE_4WAY_HANDSHAKE\x10\x08\x12\x1e\n\x1aWIFI_STATE_GROUP_HANDSHAKE\x10\t\x12\x1c\n\x18WIFI_STATE_WPA_COMPLETED\x10\n2\x88#\n\x14HomeDatastoreService\x12\x84\x01\n\x16GetHomeDatastoreSystem\x12\x39.core.protos.home_datastore.GetHomeDatastoreSystemRequest\x1a/.core.protos.home_datastore.HomeDatastoreSystem\x12Z\n\x08GetSpace\x12+.core.protos.home_datastore.GetSpaceRequest\x1a!.core.protos.home_datastore.Space\x12`\n\x0b\x43reateSpace\x12..core.protos.home_datastore.CreateSpaceRequest\x1a!.core.protos.home_datastore.Space\x12`\n\x0bUpdateSpace\x12..core.protos.home_datastore.UpdateSpaceRequest\x1a!.core.protos.home_datastore.Space\x12U\n\x0b\x44\x65leteSpace\x12..core.protos.home_datastore.DeleteSpaceRequest\x1a\x16.google.protobuf.Empty\x12o\n\x10UpdateIndoorUnit\x12\x33.core.protos.home_datastore.UpdateIndoorUnitRequest\x1a&.core.protos.home_datastore.IndoorUnit\x12_\n\x10\x44\x65leteIndoorUnit\x12\x33.core.protos.home_datastore.DeleteIndoorUnitRequest\x1a\x16.google.protobuf.Empty\x12\x81\x01\n\x15GetIndoorUnitHardware\x12\x38.core.protos.home_datastore.GetIndoorUnitHardwareRequest\x1a..core.protos.home_datastore.IndoorUnitHardware\x12{\n\x13GetQuiltSmartModule\x12\x36.core.protos.home_datastore.GetQuiltSmartModuleRequest\x1a,.core.protos.home_datastore.QuiltSmartModule\x12o\n\x10UpdateController\x12\x33.core.protos.home_datastore.UpdateControllerRequest\x1a&.core.protos.home_datastore.Controller\x12_\n\x10\x44\x65leteController\x12\x33.core.protos.home_datastore.DeleteControllerRequest\x1a\x16.google.protobuf.Empty\x12\x81\x01\n\x15GetControllerHardware\x12\x38.core.protos.home_datastore.GetControllerHardwareRequest\x1a..core.protos.home_datastore.ControllerHardware\x12\x61\n\x11\x44\x65leteOutdoorUnit\x12\x34.core.protos.home_datastore.DeleteOutdoorUnitRequest\x1a\x16.google.protobuf.Empty\x12u\n\x12\x43reateRemoteSensor\x12\x35.core.protos.home_datastore.CreateRemoteSensorRequest\x1a(.core.protos.home_datastore.RemoteSensor\x12u\n\x12UpdateRemoteSensor\x12\x35.core.protos.home_datastore.UpdateRemoteSensorRequest\x1a(.core.protos.home_datastore.RemoteSensor\x12\x63\n\x12\x44\x65leteRemoteSensor\x12\x35.core.protos.home_datastore.DeleteRemoteSensorRequest\x1a\x16.google.protobuf.Empty\x12\x93\x01\n\x1c\x43reateControllerRemoteSensor\x12?.core.protos.home_datastore.CreateControllerRemoteSensorRequest\x1a\x32.core.protos.home_datastore.ControllerRemoteSensor\x12\x93\x01\n\x1cUpdateControllerRemoteSensor\x12?.core.protos.home_datastore.UpdateControllerRemoteSensorRequest\x1a\x32.core.protos.home_datastore.ControllerRemoteSensor\x12w\n\x1c\x44\x65leteControllerRemoteSensor\x12?.core.protos.home_datastore.DeleteControllerRemoteSensorRequest\x1a\x16.google.protobuf.Empty\x12r\n\x11\x43reateScheduleDay\x12\x34.core.protos.home_datastore.CreateScheduleDayRequest\x1a\'.core.protos.home_datastore.ScheduleDay\x12l\n\x0eGetScheduleDay\x12\x31.core.protos.home_datastore.GetScheduleDayRequest\x1a\'.core.protos.home_datastore.ScheduleDay\x12r\n\x11UpdateScheduleDay\x12\x34.core.protos.home_datastore.UpdateScheduleDayRequest\x1a\'.core.protos.home_datastore.ScheduleDay\x12\x61\n\x11\x44\x65leteScheduleDay\x12\x34.core.protos.home_datastore.DeleteScheduleDayRequest\x1a\x16.google.protobuf.Empty\x12}\n\x10ListScheduleDays\x12\x33.core.protos.home_datastore.ListScheduleDaysRequest\x1a\x34.core.protos.home_datastore.ListScheduleDaysResponse\x12u\n\x12\x43reateScheduleWeek\x12\x35.core.protos.home_datastore.CreateScheduleWeekRequest\x1a(.core.protos.home_datastore.ScheduleWeek\x12o\n\x0fGetScheduleWeek\x12\x32.core.protos.home_datastore.GetScheduleWeekRequest\x1a(.core.protos.home_datastore.ScheduleWeek\x12u\n\x12UpdateScheduleWeek\x12\x35.core.protos.home_datastore.UpdateScheduleWeekRequest\x1a(.core.protos.home_datastore.ScheduleWeek\x12\x63\n\x12\x44\x65leteScheduleWeek\x12\x35.core.protos.home_datastore.DeleteScheduleWeekRequest\x1a\x16.google.protobuf.Empty\x12\x80\x01\n\x11ListScheduleWeeks\x12\x34.core.protos.home_datastore.ListScheduleWeeksRequest\x1a\x35.core.protos.home_datastore.ListScheduleWeeksResponse\x12{\n\x14\x43reateComfortSetting\x12\x37.core.protos.home_datastore.CreateComfortSettingRequest\x1a*.core.protos.home_datastore.ComfortSetting\x12u\n\x11GetComfortSetting\x12\x34.core.protos.home_datastore.GetComfortSettingRequest\x1a*.core.protos.home_datastore.ComfortSetting\x12{\n\x14UpdateComfortSetting\x12\x37.core.protos.home_datastore.UpdateComfortSettingRequest\x1a*.core.protos.home_datastore.ComfortSetting\x12g\n\x14\x44\x65leteComfortSetting\x12\x37.core.protos.home_datastore.DeleteComfortSettingRequest\x1a\x16.google.protobuf.Empty\x12\x86\x01\n\x13ListComfortSettings\x12\x36.core.protos.home_datastore.ListComfortSettingsRequest\x1a\x37.core.protos.home_datastore.ListComfortSettingsResponse\x12i\n\x0e\x43reateLocation\x12\x31.core.protos.home_datastore.CreateLocationRequest\x1a$.core.protos.home_datastore.Location\x12\x63\n\x0bGetLocation\x12..core.protos.home_datastore.GetLocationRequest\x1a$.core.protos.home_datastore.Location\x12i\n\x0eUpdateLocation\x12\x31.core.protos.home_datastore.UpdateLocationRequest\x1a$.core.protos.home_datastore.Location\x12[\n\x0e\x44\x65leteLocation\x12\x31.core.protos.home_datastore.DeleteLocationRequest\x1a\x16.google.protobuf.Empty\x12t\n\rListLocations\x12\x30.core.protos.home_datastore.ListLocationsRequest\x1a\x31.core.protos.home_datastore.ListLocationsResponseB$\n\x13\x63om.quilt.proto.hdsB\rQuiltHdsProtob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fquilt_hds.proto\x12\x1a\x63ore.protos.home_datastore\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\"\xc5\x02\n\x10LocalCommsStatus\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x42\n\x06status\x18\x02 \x01(\x0e\x32\x32.core.protos.home_datastore.LocalCommsHealthStatus\x12\x1d\n\x15visible_devices_count\x18\x03 \x01(\x05\x12\x1e\n\x16\x65xpected_devices_count\x18\x04 \x01(\x05\x12:\n\x16last_session_change_ts\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x42\n\x06reason\x18\x06 \x01(\x0e\x32\x32.core.protos.home_datastore.LocalCommsHealthReason\"\x96\x01\n\x0e\x45ntityMetadata\x12\x11\n\tobject_id\x18\x01 \x01(\t\x12.\n\ncreated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x11\n\tsystem_id\x18\x04 \x01(\t\"i\n\x12LocationAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x15\n\rtz_identifier\x18\x03 \x01(\t\"\x8d\x01\n\x10LocationControls\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12I\n\x12schedule_execution\x18\x02 \x01(\x0e\x32-.core.protos.home_datastore.ScheduleExecution\"\xca\x01\n\x08Location\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x42\n\nattributes\x18\x02 \x01(\x0b\x32..core.protos.home_datastore.LocationAttributes\x12>\n\x08\x63ontrols\x18\x03 \x01(\x0b\x32,.core.protos.home_datastore.LocationControls\"z\n\x12SpaceRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x17\n\x0fparent_space_id\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x81\x03\n\rSpaceSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08timezone\x18\x04 \x01(\t\x12<\n\toccupancy\x18\x05 \x01(\x0e\x32).core.protos.home_datastore.OccupancyMode\x12L\n\x14hvac_controller_type\x18\x06 \x01(\x0e\x32..core.protos.home_datastore.HvacControllerType\x12\x1a\n\x12occupied_timeout_s\x18\x07 \x01(\x02\x12\x1c\n\x14unoccupied_timeout_s\x18\x08 \x01(\x02\x12\x45\n\x0esafety_heating\x18\t \x01(\x0e\x32-.core.protos.home_datastore.SafetyHeatingMode\"\xb8\x03\n\rSpaceControls\x12\x37\n\thvac_mode\x18\x01 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x1e\n\x16temperature_setpoint_c\x18\x02 \x01(\x02\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x1e\x63ooling_temperature_setpoint_c\x18\x04 \x01(\x02\x12&\n\x1eheating_temperature_setpoint_c\x18\x05 \x01(\x02\x12\x1a\n\x12\x63omfort_setting_id\x18\x06 \x01(\t\x12\x39\n\nboost_mode\x18\x07 \x01(\x0e\x32%.core.protos.home_datastore.BoostMode\x12T\n\x18\x63omfort_setting_override\x18\x08 \x01(\x0e\x32\x32.core.protos.home_datastore.ComfortSettingOverride\x12!\n\x19\x63omfort_setting_id_string\x18\t \x01(\t\"\xd2\x01\n\nSpaceState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16setpoint_temperature_c\x18\x02 \x01(\x02\x12\x1d\n\x15\x61mbient_temperature_c\x18\x03 \x01(\x02\x12\x39\n\nhvac_state\x18\x04 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\x12\x1a\n\x12\x63omfort_setting_id\x18\x05 \x01(\t\"\xbb\x02\n\x05Space\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x45\n\rrelationships\x18\x02 \x01(\x0b\x32..core.protos.home_datastore.SpaceRelationships\x12;\n\x08settings\x18\x03 \x01(\x0b\x32).core.protos.home_datastore.SpaceSettings\x12;\n\x08\x63ontrols\x18\x04 \x01(\x0b\x32).core.protos.home_datastore.SpaceControls\x12\x35\n\x05state\x18\x05 \x01(\x0b\x32&.core.protos.home_datastore.SpaceState\"\xe6\x01\n\x17IndoorUnitRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x10\n\x08space_id\x18\x02 \x01(\t\x12\x17\n\x0foutdoor_unit_id\x18\x03 \x01(\t\x12\x13\n\x0bhardware_id\x18\x04 \x01(\t\x12\x1d\n\x15quilt_smart_module_id\x18\x05 \x01(\t\x12.\n\nupdated_ts\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17\x66irmware_update_info_id\x18\x07 \x01(\t\"\x9e\x02\n\x12IndoorUnitSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12(\n light_brightness_default_percent\x18\x03 \x01(\x02\x12.\n\nupdated_ts\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15presence_fence_left_m\x18\x05 \x01(\x02\x12\x1e\n\x16presence_fence_right_m\x18\x06 \x01(\x02\x12 \n\x18presence_fence_forward_m\x18\x07 \x01(\x02\x12*\n\"radar_sensor_distance_from_floor_m\x18\x08 \x01(\x02\"\xc3\x03\n\x12IndoorUnitControls\x12\x16\n\x0eled_color_code\x18\x03 \x01(\r\x12$\n\x1cled_color_brightness_percent\x18\x04 \x01(\x02\x12@\n\x0e\x66\x61n_speed_mode\x18\x05 \x01(\x0e\x32(.core.protos.home_datastore.FanSpeedMode\x12\x19\n\x11\x66\x61n_speed_percent\x18\x06 \x01(\x02\x12.\n\nupdated_ts\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x45\n\x0blouver_mode\x18\n \x01(\x0e\x32\x30.core.protos.home_datastore.IndoorUnitLouverMode\x12\x1d\n\x15louver_fixed_position\x18\x0b \x01(\x02\x12\x41\n\rled_animation\x18\x0c \x01(\x0e\x32*.core.protos.home_datastore.LightAnimation\x12\x39\n\tled_state\x18\r \x01(\x0e\x32&.core.protos.home_datastore.LightState\"\x9c\x04\n\x0fIndoorUnitState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16temperature_setpoint_c\x18\x02 \x01(\x02\x12\x1d\n\x15\x61mbient_temperature_c\x18\x03 \x01(\x02\x12\x39\n\nhvac_state\x18\x04 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\x12\x1e\n\x16\x66\x61n_speed_setpoint_rpm\x18\x05 \x01(\x02\x12\x15\n\rfan_speed_rpm\x18\x06 \x01(\x02\x12 \n\x18light_brightness_percent\x18\x07 \x01(\x02\x12 \n\x18presence_detection_level\x18\x08 \x01(\x02\x12\x1b\n\x13inlet_temperature_c\x18\t \x01(\x02\x12\x1c\n\x14outlet_temperature_c\x18\n \x01(\x02\x12 \n\x18\x61mbient_humidity_percent\x18\x0b \x01(\x02\x12\x37\n\thvac_mode\x18\x0c \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12(\n calculated_ambient_temperature_c\x18\r \x01(\x02\x12$\n\x1clouver_angle_up_down_degrees\x18\x0e \x01(\x02\"\xbf\x07\n\x14IndoorUnitConditions\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x41\n\rmode_conflict\x18\x02 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12\x42\n\x0e\x61nti_cold_wind\x18\x03 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12T\n abnormal_outdoor_air_temperature\x18\x04 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12M\n\x19hvac_mode_switching_delay\x18\x05 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12\x41\n\rdefrost_cycle\x18\x06 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12\x42\n\x0esafety_heating\x18\x07 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12>\n\noil_return\x18\x08 \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12N\n\x1amodbus_communication_error\x18\t \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12@\n\x0c\x63oil_preheat\x18\n \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12K\n\x17mode_conflict_avoidance\x18\x0b \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12T\n outdoor_unit_communication_error\x18\x0c \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\x12O\n\x1b\x63ompressor_minimum_run_time\x18\r \x01(\x0e\x32*.core.protos.home_datastore.ConditionState\"\xc9\x01\n\x17IndoorUnitPresenceState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12>\n\x10sensor0_presence\x18\x02 \x01(\x0e\x32$.core.protos.home_datastore.Presence\x12>\n\x10sensor1_presence\x18\x03 \x01(\x0e\x32$.core.protos.home_datastore.Presence\"\x9a\x01\n\x12IndoorUnitCommands\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12T\n\x18\x66\x61llback_control_command\x18\x02 \x01(\x0e\x32\x32.core.protos.home_datastore.FallbackControlCommand\"\xad\x02\n\x1cIndoorUnitHardwareAttributes\x12\x11\n\tmodel_sku\x18\x01 \x01(\t\x12\x15\n\rserial_number\x18\x02 \x01(\t\x12\x31\n\rproduction_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x66irmware_version\x18\x04 \x01(\t\x12\x19\n\x11\x66\x61n_speed_max_rpm\x18\x05 \x01(\x02\x12.\n\nupdated_ts\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12!\n\x19indoor_unit_serial_number\x18\x07 \x01(\t\x12(\n quilt_smart_module_serial_number\x18\x08 \x01(\t\"\x9e\x01\n\x12IndoorUnitHardware\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12L\n\nattributes\x18\x02 \x01(\x0b\x32\x38.core.protos.home_datastore.IndoorUnitHardwareAttributes\"\xf4\x02\n\x14IndoorUnitHvacInputs\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12&\n\x1e\x65xternal_ambient_temperature_c\x18\x02 \x01(\x02\x12\"\n\x1a\x61mbient_temperature_source\x18\x03 \x01(\x05\x12\x1e\n\x16temperature_setpoint_c\x18\x04 \x01(\x02\x12\x37\n\thvac_mode\x18\x05 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12L\n\x14hvac_controller_type\x18\x06 \x01(\x0e\x32..core.protos.home_datastore.HvacControllerType\x12\x39\n\nhvac_state\x18\x07 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\"\xd1\x03\n\x19IndoorUnitPerformanceData\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16measurement_interval_s\x18\x02 \x01(\x02\x12\x1c\n\x14\x65nergy_measurement_j\x18\x03 \x01(\x02\x12\x37\n\thvac_mode\x18\x04 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x1c\n\x14\x61\x63tual_fan_speed_rpm\x18\x05 \x01(\x02\x12\x1c\n\x14outlet_temperature_c\x18\x06 \x01(\x02\x12\x1a\n\x12inlet_humidity_pct\x18\x07 \x01(\x02\x12\x1b\n\x13inlet_temperature_c\x18\x08 \x01(\x02\x12\x1a\n\x12\x63oil_temperature_c\x18\t \x01(\x02\x12\x1e\n\x16gas_pipe_temperature_c\x18\n \x01(\x02\x12!\n\x19liquid_pipe_temperature_c\x18\x0b \x01(\x02\x12\x39\n\nhvac_state\x18\x0c \x01(\x0e\x32%.core.protos.home_datastore.HVACState\"\x8a\x03\n\x1cIndoorUnitPerformanceMetrics\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16measurement_duration_s\x18\x0b \x01(\x02\x12\x16\n\x0e\x65nergy_total_j\x18\x04 \x01(\x02\x12\x12\n\ncapacity_w\x18\x05 \x01(\x02\x12\"\n\x1a\x63oefficient_of_performance\x18\x06 \x01(\x02\x12\x37\n\thvac_mode\x18\x07 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x14\n\x0chvac_power_w\x18\x08 \x01(\x02\x12\x13\n\x0bled_power_w\x18\t \x01(\x02\x12\x39\n\nhvac_state\x18\n \x01(\x0e\x32%.core.protos.home_datastore.HVACState\x12\x15\n\rhvac_energy_j\x18\x0c \x01(\x02\x12\x14\n\x0cled_energy_j\x18\r \x01(\x02\"\x8a\x01\n\x13IndoorUnitOccupancy\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x43\n\x0foccupancy_state\x18\x02 \x01(\x0e\x32*.core.protos.home_datastore.OccupancyState\"\xd6\x06\n\nIndoorUnit\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12J\n\rrelationships\x18\x02 \x01(\x0b\x32\x33.core.protos.home_datastore.IndoorUnitRelationships\x12@\n\x08settings\x18\x03 \x01(\x0b\x32..core.protos.home_datastore.IndoorUnitSettings\x12@\n\x08\x63ontrols\x18\x04 \x01(\x0b\x32..core.protos.home_datastore.IndoorUnitControls\x12:\n\x05state\x18\x05 \x01(\x0b\x32+.core.protos.home_datastore.IndoorUnitState\x12\x45\n\x0bhvac_inputs\x18\x06 \x01(\x0b\x32\x30.core.protos.home_datastore.IndoorUnitHvacInputs\x12\x45\n\x08presence\x18\x07 \x01(\x0b\x32\x33.core.protos.home_datastore.IndoorUnitPresenceState\x12\x44\n\nconditions\x18\x08 \x01(\x0b\x32\x30.core.protos.home_datastore.IndoorUnitConditions\x12@\n\x08\x63ommands\x18\t \x01(\x0b\x32..core.protos.home_datastore.IndoorUnitCommands\x12O\n\x10performance_data\x18\n \x01(\x0b\x32\x35.core.protos.home_datastore.IndoorUnitPerformanceData\x12U\n\x13performance_metrics\x18\x0b \x01(\x0b\x32\x38.core.protos.home_datastore.IndoorUnitPerformanceMetrics\x12\x42\n\toccupancy\x18\x0c \x01(\x0b\x32/.core.protos.home_datastore.IndoorUnitOccupancy\"\xaf\x01\n\x18OutdoorUnitRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x10\n\x08space_id\x18\x02 \x01(\t\x12\x13\n\x0bhardware_id\x18\x03 \x01(\t\x12.\n\nupdated_ts\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17\x66irmware_update_info_id\x18\x05 \x01(\t\"h\n\x13OutdoorUnitSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"}\n\x10OutdoorUnitState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x39\n\nhvac_state\x18\x02 \x01(\x0e\x32%.core.protos.home_datastore.HVACState\"\xba\x02\n\x1aOutdoorUnitPerformanceData\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1e\n\x16measurement_interval_s\x18\x02 \x01(\x02\x12\x1c\n\x14\x65nergy_measurement_j\x18\x03 \x01(\x02\x12\x1f\n\x17\x63ompressor_frequency_hz\x18\x04 \x01(\x02\x12\x1d\n\x15\x61mbient_temperature_c\x18\x05 \x01(\x02\x12\x1a\n\x12\x63oil_temperature_c\x18\x06 \x01(\x02\x12\x1d\n\x15\x65xhaust_temperature_c\x18\x07 \x01(\x02\x12\x19\n\x11high_pressure_kpa\x18\x08 \x01(\x02\x12\x18\n\x10low_pressure_kpa\x18\t \x01(\x02\"\xc6\x01\n\x1dOutdoorUnitHardwareAttributes\x12\x11\n\tmodel_sku\x18\x01 \x01(\t\x12\x15\n\rserial_number\x18\x02 \x01(\t\x12\x31\n\rproduction_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x66irmware_version\x18\x04 \x01(\t\x12.\n\nupdated_ts\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa0\x01\n\x13OutdoorUnitHardware\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12M\n\nattributes\x18\x02 \x01(\x0b\x32\x39.core.protos.home_datastore.OutdoorUnitHardwareAttributes\"\xe8\x02\n\x0bOutdoorUnit\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12K\n\rrelationships\x18\x02 \x01(\x0b\x32\x34.core.protos.home_datastore.OutdoorUnitRelationships\x12\x41\n\x08settings\x18\x03 \x01(\x0b\x32/.core.protos.home_datastore.OutdoorUnitSettings\x12;\n\x05state\x18\x04 \x01(\x0b\x32,.core.protos.home_datastore.OutdoorUnitState\x12P\n\x10performance_data\x18\x05 \x01(\x0b\x32\x36.core.protos.home_datastore.OutdoorUnitPerformanceData\"\xcf\x01\n\x17\x43ontrollerRelationships\x12\x1b\n\x13\x63ustomer_account_id\x18\x01 \x01(\t\x12\x10\n\x08space_id\x18\x02 \x01(\t\x12\x13\n\x0bhardware_id\x18\x03 \x01(\t\x12.\n\nupdated_ts\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17software_update_info_id\x18\x05 \x01(\t\x12\x1f\n\x17\x66irmware_update_info_id\x18\x06 \x01(\t\"g\n\x12\x43ontrollerSettings\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x02 \x01(\t\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa8\x01\n\x0f\x43ontrollerState\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15\x61mbient_temperature_c\x18\x02 \x01(\x02\x12\x16\n\x0etemperature_f3\x18\x03 \x01(\x02\x12\x16\n\x0etemperature_f4\x18\x04 \x01(\x02\x12\x16\n\x0etemperature_f5\x18\x05 \x01(\x02\"\x9d\x01\n\x12\x43ontrollerControls\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12W\n\x1aremote_sensor_control_mode\x18\x02 \x01(\x0e\x32\x33.core.protos.home_datastore.RemoteSensorControlMode\"\xde\x02\n\tWifiState\x12\x43\n\nwifi_state\x18\x01 \x01(\x0e\x32/.core.protos.home_datastore.WifiConnectionState\x12\x0c\n\x04ssid\x18\x02 \x01(\t\x12\r\n\x05\x62ssid\x18\x03 \x01(\t\x12\x15\n\rfrequency_mhz\x18\x04 \x01(\x05\x12\x12\n\npassphrase\x18\x05 \x01(\t\x12\x18\n\x10signal_level_dbm\x18\x06 \x01(\x05\x12\x17\n\x0fnoise_level_dbm\x18\x07 \x01(\x05\x12\x17\n\x0frx_invalid_frag\x18\x08 \x01(\x05\x12\x1c\n\x14tx_excessive_retries\x18\t \x01(\x05\x12\x14\n\x0cipv4_address\x18\n \x01(\t\x12\x14\n\x0cipv6_address\x18\x0b \x01(\t\x12.\n\nupdated_ts\x18\x0c \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xc5\x01\n\x1c\x43ontrollerHardwareAttributes\x12\x11\n\tmodel_sku\x18\x01 \x01(\t\x12\x15\n\rserial_number\x18\x02 \x01(\t\x12\x31\n\rproduction_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x18\n\x10\x66irmware_version\x18\x04 \x01(\t\x12.\n\nupdated_ts\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x9e\x01\n\x12\x43ontrollerHardware\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12L\n\nattributes\x18\x02 \x01(\x0b\x32\x38.core.protos.home_datastore.ControllerHardwareAttributes\"\xdd\x04\n\nController\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12J\n\rrelationships\x18\x02 \x01(\x0b\x32\x33.core.protos.home_datastore.ControllerRelationships\x12@\n\x08settings\x18\x03 \x01(\x0b\x32..core.protos.home_datastore.ControllerSettings\x12:\n\x05state\x18\x04 \x01(\x0b\x32+.core.protos.home_datastore.ControllerState\x12@\n\x11hosted_wifi_state\x18\x05 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12<\n\rap_wifi_state\x18\x06 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12=\n\x0ep2p_wifi_state\x18\x07 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12@\n\x08\x63ontrols\x18\x08 \x01(\x0b\x32..core.protos.home_datastore.ControllerControls\x12H\n\x12local_comms_status\x18\t \x01(\x0b\x32,.core.protos.home_datastore.LocalCommsStatus\"U\n\x16RemoteSensorAttributes\x12\x0b\n\x03mac\x18\x01 \x01(\t\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb5\x01\n\x11RemoteSensorState\x12\x1d\n\x15\x61mbient_temperature_c\x18\x01 \x01(\x02\x12\x18\n\x10humidity_percent\x18\x02 \x01(\x02\x12.\n\nupdated_ts\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1d\n\x15\x62\x61ttery_level_percent\x18\x04 \x01(\x02\x12\x18\n\x10signal_level_dbm\x18\x05 \x01(\x05\"c\n\x19RemoteSensorRelationships\x12\x16\n\x0eindoor_unit_id\x18\x01 \x01(\t\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"l\n#ControllerRemoteSensorRelationships\x12\x15\n\rcontroller_id\x18\x01 \x01(\t\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x91\x01\n\x14RemoteSensorControls\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12I\n\x0c\x63ontrol_mode\x18\x02 \x01(\x0e\x32\x33.core.protos.home_datastore.RemoteSensorControlMode\"\xe2\x02\n\x0cRemoteSensor\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x46\n\nattributes\x18\x02 \x01(\x0b\x32\x32.core.protos.home_datastore.RemoteSensorAttributes\x12<\n\x05state\x18\x03 \x01(\x0b\x32-.core.protos.home_datastore.RemoteSensorState\x12L\n\rrelationships\x18\x04 \x01(\x0b\x32\x35.core.protos.home_datastore.RemoteSensorRelationships\x12\x42\n\x08\x63ontrols\x18\x05 \x01(\x0b\x32\x30.core.protos.home_datastore.RemoteSensorControls\"\xf6\x02\n\x16\x43ontrollerRemoteSensor\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x46\n\nattributes\x18\x02 \x01(\x0b\x32\x32.core.protos.home_datastore.RemoteSensorAttributes\x12<\n\x05state\x18\x03 \x01(\x0b\x32-.core.protos.home_datastore.RemoteSensorState\x12V\n\rrelationships\x18\x04 \x01(\x0b\x32?.core.protos.home_datastore.ControllerRemoteSensorRelationships\x12\x42\n\x08\x63ontrols\x18\x05 \x01(\x0b\x32\x30.core.protos.home_datastore.RemoteSensorControls\"\xe2\x03\n\x18\x43omfortSettingAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0c\n\x04name\x18\x02 \x01(\t\x12@\n\x0e\x66\x61n_speed_mode\x18\x03 \x01(\x0e\x32(.core.protos.home_datastore.FanSpeedMode\x12\x19\n\x11\x66\x61n_speed_percent\x18\x04 \x01(\x02\x12&\n\x1eheating_temperature_setpoint_c\x18\x05 \x01(\x02\x12&\n\x1e\x63ooling_temperature_setpoint_c\x18\x06 \x01(\x02\x12<\n\x04type\x18\x07 \x01(\x0e\x32..core.protos.home_datastore.ComfortSettingType\x12\x37\n\thvac_mode\x18\x08 \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x45\n\x0blouver_mode\x18\t \x01(\x0e\x32\x30.core.protos.home_datastore.IndoorUnitLouverMode\x12\x1d\n\x15louver_fixed_position\x18\n \x01(\x02\"_\n\x1b\x43omfortSettingRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08space_id\x18\x02 \x01(\t\"\xe6\x01\n\x0e\x43omfortSetting\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12H\n\nattributes\x18\x02 \x01(\x0b\x32\x34.core.protos.home_datastore.ComfortSettingAttributes\x12N\n\rrelationships\x18\x03 \x01(\x0b\x32\x37.core.protos.home_datastore.ComfortSettingRelationships\"\xd0\x01\n\x10LedScheduleEvent\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07start_s\x18\x02 \x01(\x05\x12\x16\n\x0eled_color_code\x18\x03 \x01(\r\x12$\n\x1cled_color_brightness_percent\x18\x04 \x01(\x02\x12=\n\tanimation\x18\x05 \x01(\x0e\x32*.core.protos.home_datastore.LightAnimation\"\x99\x04\n\rScheduleEvent\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1a\n\x12\x63omfort_setting_id\x18\x02 \x01(\t\x12\x0f\n\x07start_s\x18\x03 \x01(\x05\x12\x14\n\x0cprecondition\x18\x04 \x01(\x08\x12I\n\x12schedule_execution\x18\x05 \x01(\x0e\x32-.core.protos.home_datastore.ScheduleExecution\x12@\n\x0e\x66\x61n_speed_mode\x18\x06 \x01(\x0e\x32(.core.protos.home_datastore.FanSpeedMode\x12\x19\n\x11\x66\x61n_speed_percent\x18\x07 \x01(\x02\x12&\n\x1eheating_temperature_setpoint_c\x18\x08 \x01(\x02\x12&\n\x1e\x63ooling_temperature_setpoint_c\x18\t \x01(\x02\x12\x37\n\thvac_mode\x18\n \x01(\x0e\x32$.core.protos.home_datastore.HVACMode\x12\x45\n\x0blouver_mode\x18\x0b \x01(\x0e\x32\x30.core.protos.home_datastore.IndoorUnitLouverMode\x12\x1d\n\x15louver_fixed_position\x18\x0c \x01(\x02\"U\n\x15ScheduleDayAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0c\n\x04name\x18\x02 \x01(\t\"\\\n\x18ScheduleDayRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08space_id\x18\x02 \x01(\t\"\xda\x02\n\x0bScheduleDay\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x45\n\nattributes\x18\x02 \x01(\x0b\x32\x31.core.protos.home_datastore.ScheduleDayAttributes\x12\x39\n\x06\x65vents\x18\x03 \x03(\x0b\x32).core.protos.home_datastore.ScheduleEvent\x12K\n\rrelationships\x18\x04 \x01(\x0b\x32\x34.core.protos.home_datastore.ScheduleDayRelationships\x12@\n\nled_events\x18\x05 \x03(\x0b\x32,.core.protos.home_datastore.LedScheduleEvent\"\x87\x01\n\x0fScheduleWeekDay\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06\x64\x61y_id\x18\x02 \x01(\t\x12\x34\n\x07weekday\x18\x03 \x01(\x0e\x32#.core.protos.home_datastore.Weekday\"]\n\x19ScheduleWeekRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x10\n\x08space_id\x18\x02 \x01(\t\"\xd3\x01\n\x0cScheduleWeek\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x39\n\x04\x64\x61ys\x18\x02 \x03(\x0b\x32+.core.protos.home_datastore.ScheduleWeekDay\x12L\n\rrelationships\x18\x03 \x01(\x0b\x32\x35.core.protos.home_datastore.ScheduleWeekRelationships\"\x91\x01\n\x1dQuiltSmartModuleRelationships\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1f\n\x17software_update_info_id\x18\x02 \x01(\t\x12\x1f\n\x17\x66irmware_update_info_id\x18\x03 \x01(\t\"b\n\x18QuiltSmartModuleControls\x12\x16\n\x0eled_color_code\x18\x01 \x01(\x05\x12.\n\nupdated_ts\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x86\x02\n\x15QuiltSmartModuleState\x12\x1a\n\x12phase_detected_raw\x18\x01 \x01(\x02\x12\x1b\n\x13target_detected_raw\x18\x02 \x01(\x02\x12\x1b\n\x13\x61ls_illuminance_raw\x18\x03 \x01(\x05\x12\x12\n\nals_ir_raw\x18\x04 \x01(\x05\x12\x14\n\x0c\x61ls_both_raw\x18\x05 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65l_x_raw\x18\x06 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65l_y_raw\x18\x07 \x01(\x05\x12\x13\n\x0b\x61\x63\x63\x65l_z_raw\x18\x08 \x01(\x05\x12.\n\nupdated_ts\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xb3\x04\n\x10QuiltSmartModule\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12\x46\n\x08\x63ontrols\x18\x02 \x01(\x0b\x32\x34.core.protos.home_datastore.QuiltSmartModuleControls\x12@\n\x05state\x18\x03 \x01(\x0b\x32\x31.core.protos.home_datastore.QuiltSmartModuleState\x12@\n\x11hosted_wifi_state\x18\x04 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12<\n\rap_wifi_state\x18\x05 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12=\n\x0ep2p_wifi_state\x18\x06 \x01(\x0b\x32%.core.protos.home_datastore.WifiState\x12P\n\rrelationships\x18\x07 \x01(\x0b\x32\x39.core.protos.home_datastore.QuiltSmartModuleRelationships\x12H\n\x12local_comms_status\x18\x08 \x01(\x0b\x32,.core.protos.home_datastore.LocalCommsStatus\"\xe7\x01\n\x1cSoftwareUpdateInfoAttributes\x12.\n\nupdated_ts\x18\x01 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05state\x18\x02 \x01(\x05\x12\x0e\n\x06status\x18\x03 \x01(\x05\x12\x17\n\x0f\x63urrent_version\x18\x04 \x01(\t\x12\x16\n\x0etarget_version\x18\x05 \x01(\t\x12\x18\n\x10\x63urrent_progress\x18\x06 \x01(\x02\x12\x16\n\x0etotal_progress\x18\x07 \x01(\x02\x12\x15\n\rprogress_unit\x18\x08 \x01(\x05\"\x9e\x01\n\x12SoftwareUpdateInfo\x12:\n\x06header\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.EntityMetadata\x12L\n\nattributes\x18\x02 \x01(\x0b\x32\x38.core.protos.home_datastore.SoftwareUpdateInfoAttributes\"\xa2\x08\n\x13HomeDatastoreSystem\x12\x31\n\x06spaces\x18\x03 \x03(\x0b\x32!.core.protos.home_datastore.Space\x12N\n\x15outdoor_unit_hardware\x18\x05 \x03(\x0b\x32/.core.protos.home_datastore.OutdoorUnitHardware\x12>\n\routdoor_units\x18\x06 \x03(\x0b\x32\'.core.protos.home_datastore.OutdoorUnit\x12I\n\x13quilt_smart_modules\x18\x07 \x03(\x0b\x32,.core.protos.home_datastore.QuiltSmartModule\x12L\n\x14indoor_unit_hardware\x18\x08 \x03(\x0b\x32..core.protos.home_datastore.IndoorUnitHardware\x12<\n\x0cindoor_units\x18\t \x03(\x0b\x32&.core.protos.home_datastore.IndoorUnit\x12K\n\x13\x63ontroller_hardware\x18\n \x03(\x0b\x32..core.protos.home_datastore.ControllerHardware\x12;\n\x0b\x63ontrollers\x18\x0b \x03(\x0b\x32&.core.protos.home_datastore.Controller\x12@\n\x0eremote_sensors\x18\x0c \x03(\x0b\x32(.core.protos.home_datastore.RemoteSensor\x12\x44\n\x10\x63omfort_settings\x18\r \x03(\x0b\x32*.core.protos.home_datastore.ComfortSetting\x12>\n\rschedule_days\x18\x0e \x03(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\x12@\n\x0eschedule_weeks\x18\x0f \x03(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\x12U\n\x19\x63ontroller_remote_sensors\x18\x10 \x03(\x0b\x32\x32.core.protos.home_datastore.ControllerRemoteSensor\x12\x37\n\tlocations\x18\x11 \x03(\x0b\x32$.core.protos.home_datastore.Location\x12M\n\x15software_update_infos\x18\x12 \x03(\x0b\x32..core.protos.home_datastore.SoftwareUpdateInfo\"b\n\x1dGetHomeDatastoreSystemRequest\x12\x11\n\tsystem_id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"\x1d\n\x0fGetSpaceRequest\x12\n\n\x02id\x18\x01 \x01(\t\"F\n\x12\x43reateSpaceRequest\x12\x30\n\x05space\x18\x01 \x01(\x0b\x32!.core.protos.home_datastore.Space\"E\n\x12UpdateSpaceRequest\x12/\n\x04\x64iff\x18\x01 \x01(\x0b\x32!.core.protos.home_datastore.Space\" \n\x12\x44\x65leteSpaceRequest\x12\n\n\x02id\x18\x01 \x01(\t\"O\n\x17UpdateIndoorUnitRequest\x12\x34\n\x04\x64iff\x18\x01 \x01(\x0b\x32&.core.protos.home_datastore.IndoorUnit\"%\n\x17\x44\x65leteIndoorUnitRequest\x12\n\n\x02id\x18\x01 \x01(\t\"Z\n\x1cGetIndoorUnitHardwareRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"U\n\x17UpdateControllerRequest\x12:\n\ncontroller\x18\x01 \x01(\x0b\x32&.core.protos.home_datastore.Controller\"%\n\x17\x44\x65leteControllerRequest\x12\n\n\x02id\x18\x01 \x01(\t\"Z\n\x1cGetControllerHardwareRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"&\n\x18\x44\x65leteOutdoorUnitRequest\x12\n\n\x02id\x18\x01 \x01(\t\"X\n\x1aGetQuiltSmartModuleRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12.\n\nfield_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMask\"\\\n\x19\x43reateRemoteSensorRequest\x12?\n\rremote_sensor\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.RemoteSensor\"\\\n\x19UpdateRemoteSensorRequest\x12?\n\rremote_sensor\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.RemoteSensor\"5\n\x19\x44\x65leteRemoteSensorRequest\x12\x18\n\x10remote_sensor_id\x18\x01 \x01(\t\"p\n#CreateControllerRemoteSensorRequest\x12I\n\rremote_sensor\x18\x01 \x01(\x0b\x32\x32.core.protos.home_datastore.ControllerRemoteSensor\"p\n#UpdateControllerRemoteSensorRequest\x12I\n\rremote_sensor\x18\x01 \x01(\x0b\x32\x32.core.protos.home_datastore.ControllerRemoteSensor\"?\n#DeleteControllerRemoteSensorRequest\x12\x18\n\x10remote_sensor_id\x18\x01 \x01(\t\"Y\n\x18\x43reateScheduleDayRequest\x12=\n\x0cschedule_day\x18\x01 \x01(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\"0\n\x15GetScheduleDayRequest\x12\x17\n\x0fschedule_day_id\x18\x01 \x01(\t\"Y\n\x18UpdateScheduleDayRequest\x12=\n\x0cschedule_day\x18\x01 \x01(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\"3\n\x18\x44\x65leteScheduleDayRequest\x12\x17\n\x0fschedule_day_id\x18\x01 \x01(\t\")\n\x17ListScheduleDaysRequest\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\"Z\n\x18ListScheduleDaysResponse\x12>\n\rschedule_days\x18\x01 \x03(\x0b\x32\'.core.protos.home_datastore.ScheduleDay\"\\\n\x19\x43reateScheduleWeekRequest\x12?\n\rschedule_week\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\"2\n\x16GetScheduleWeekRequest\x12\x18\n\x10schedule_week_id\x18\x01 \x01(\t\"\\\n\x19UpdateScheduleWeekRequest\x12?\n\rschedule_week\x18\x01 \x01(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\"5\n\x19\x44\x65leteScheduleWeekRequest\x12\x18\n\x10schedule_week_id\x18\x01 \x01(\t\"*\n\x18ListScheduleWeeksRequest\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\"]\n\x19ListScheduleWeeksResponse\x12@\n\x0eschedule_weeks\x18\x01 \x03(\x0b\x32(.core.protos.home_datastore.ScheduleWeek\"b\n\x1b\x43reateComfortSettingRequest\x12\x43\n\x0f\x63omfort_setting\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.ComfortSetting\"6\n\x18GetComfortSettingRequest\x12\x1a\n\x12\x63omfort_setting_id\x18\x01 \x01(\t\"b\n\x1bUpdateComfortSettingRequest\x12\x43\n\x0f\x63omfort_setting\x18\x01 \x01(\x0b\x32*.core.protos.home_datastore.ComfortSetting\"9\n\x1b\x44\x65leteComfortSettingRequest\x12\x1a\n\x12\x63omfort_setting_id\x18\x01 \x01(\t\",\n\x1aListComfortSettingsRequest\x12\x0e\n\x06\x66ilter\x18\x01 \x01(\t\"c\n\x1bListComfortSettingsResponse\x12\x44\n\x10\x63omfort_settings\x18\x01 \x03(\x0b\x32*.core.protos.home_datastore.ComfortSetting\"O\n\x15\x43reateLocationRequest\x12\x36\n\x08location\x18\x01 \x01(\x0b\x32$.core.protos.home_datastore.Location\")\n\x12GetLocationRequest\x12\x13\n\x0blocation_id\x18\x01 \x01(\t\"O\n\x15UpdateLocationRequest\x12\x36\n\x08location\x18\x01 \x01(\x0b\x32$.core.protos.home_datastore.Location\",\n\x15\x44\x65leteLocationRequest\x12\x13\n\x0blocation_id\x18\x01 \x01(\t\")\n\x14ListLocationsRequest\x12\x11\n\tsystem_id\x18\x01 \x01(\t\"P\n\x15ListLocationsResponse\x12\x37\n\tlocations\x18\x01 \x03(\x0b\x32$.core.protos.home_datastore.Location\"l\n\x19RequestFastUpdatesRequest\x12\x11\n\tsystem_id\x18\x01 \x01(\t\x12<\n\x06reason\x18\x02 \x01(\x0e\x32,.core.protos.home_datastore.FastUpdateReason*\xd7\x01\n\x08HVACMode\x12\x19\n\x15HVAC_MODE_UNSPECIFIED\x10\x00\x12\x15\n\x11HVAC_MODE_STANDBY\x10\x01\x12\x12\n\x0eHVAC_MODE_COOL\x10\x02\x12\x12\n\x0eHVAC_MODE_HEAT\x10\x03\x12\x12\n\x0eHVAC_MODE_AUTO\x10\x04\x12\x11\n\rHVAC_MODE_FAN\x10\x05\x12\x1b\n\x17HVAC_MODE_FALLBACK_AUTO\x10\x06\x12\x1a\n\x16HVAC_MODE_FALLBACK_OFF\x10\x07\x12\x11\n\rHVAC_MODE_DRY\x10\x08*\xf9\x02\n\tHVACState\x12\x1a\n\x16HVAC_STATE_UNSPECIFIED\x10\x00\x12\x16\n\x12HVAC_STATE_STANDBY\x10\x01\x12\x13\n\x0fHVAC_STATE_COOL\x10\x02\x12\x13\n\x0fHVAC_STATE_HEAT\x10\x03\x12\x14\n\x10HVAC_STATE_DRIFT\x10\x04\x12\x12\n\x0eHVAC_STATE_FAN\x10\x05\x12\x1c\n\x18HVAC_STATE_COOL_DEFERRED\x10\x06\x12\x1c\n\x18HVAC_STATE_HEAT_DEFERRED\x10\x07\x12\x1b\n\x17HVAC_STATE_FAN_DEFERRED\x10\x08\x12\x1d\n\x19HVAC_STATE_COOL_PREPARING\x10\t\x12\x1d\n\x19HVAC_STATE_HEAT_PREPARING\x10\n\x12\x12\n\x0eHVAC_STATE_DRY\x10\x0b\x12\x1b\n\x17HVAC_STATE_DRY_DEFERRED\x10\x0c\x12\x1c\n\x18HVAC_STATE_DRY_PREPARING\x10\r*\xe4\x01\n\x16LocalCommsHealthStatus\x12)\n%LOCAL_COMMS_HEALTH_STATUS_UNSPECIFIED\x10\x00\x12%\n!LOCAL_COMMS_HEALTH_STATUS_HEALTHY\x10\x01\x12&\n\"LOCAL_COMMS_HEALTH_STATUS_DEGRADED\x10\x02\x12%\n!LOCAL_COMMS_HEALTH_STATUS_OFFLINE\x10\x03\x12)\n%LOCAL_COMMS_HEALTH_STATUS_STARTING_UP\x10\x04*\xda\x04\n\x16LocalCommsHealthReason\x12)\n%LOCAL_COMMS_HEALTH_REASON_UNSPECIFIED\x10\x00\x12%\n!LOCAL_COMMS_HEALTH_REASON_HEALTHY\x10\x01\x12,\n(LOCAL_COMMS_HEALTH_REASON_BOOT_OR_WARMUP\x10\x02\x12\x30\n,LOCAL_COMMS_HEALTH_REASON_ZENOH_SESSION_DOWN\x10\x03\x12.\n*LOCAL_COMMS_HEALTH_REASON_NO_PEERS_VISIBLE\x10\x04\x12\x30\n,LOCAL_COMMS_HEALTH_REASON_PARTIAL_VISIBILITY\x10\x05\x12\x32\n.LOCAL_COMMS_HEALTH_REASON_NO_MESSAGES_RECEIVED\x10\x06\x12\x31\n-LOCAL_COMMS_HEALTH_REASON_SESSION_INSTABILITY\x10\x07\x12,\n(LOCAL_COMMS_HEALTH_REASON_NO_ALIVE_PEERS\x10\x08\x12/\n+LOCAL_COMMS_HEALTH_REASON_NO_PEERS_EXPECTED\x10\t\x12\x33\n/LOCAL_COMMS_HEALTH_REASON_CREDENTIALS_NOT_READY\x10\n\x12\x31\n-LOCAL_COMMS_HEALTH_REASON_CREDENTIALS_EXPIRED\x10\x0b*h\n\rOccupancyMode\x12\x1e\n\x1aOCCUPANCY_MODE_UNSPECIFIED\x10\x00\x12\x1b\n\x17OCCUPANCY_MODE_DISABLED\x10\x01\x12\x1a\n\x16OCCUPANCY_MODE_ENABLED\x10\x02*{\n\x11SafetyHeatingMode\x12#\n\x1fSAFETY_HEATING_MODE_UNSPECIFIED\x10\x00\x12 \n\x1cSAFETY_HEATING_MODE_DISABLED\x10\x01\x12\x1f\n\x1bSAFETY_HEATING_MODE_ENABLED\x10\x02*\xd1\x01\n\x12HvacControllerType\x12$\n HVAC_CONTROLLER_TYPE_UNSPECIFIED\x10\x00\x12\x31\n-HVAC_CONTROLLER_TYPE_PASS_THROUGH_TEMPERATURE\x10\x01\x12\x30\n,HVAC_CONTROLLER_TYPE_INTEGRAL_TEMPERATURE_V1\x10\x02\x12\x30\n,HVAC_CONTROLLER_TYPE_INTEGRAL_TEMPERATURE_V2\x10\x03*\xb7\x02\n\x16\x43omfortSettingOverride\x12(\n$COMFORT_SETTING_OVERRIDE_UNSPECIFIED\x10\x00\x12!\n\x1d\x43OMFORT_SETTING_OVERRIDE_NONE\x10\x01\x12\x30\n,COMFORT_SETTING_OVERRIDE_UNTIL_NEXT_SCHEDULE\x10\x02\x12\'\n#COMFORT_SETTING_OVERRIDE_INDEFINITE\x10\x03\x12%\n!COMFORT_SETTING_OVERRIDE_SCHEDULE\x10\x04\x12\'\n#COMFORT_SETTING_OVERRIDE_UNOCCUPIED\x10\x05\x12%\n!COMFORT_SETTING_OVERRIDE_OCCUPIED\x10\x06*\xdd\x01\n\x12\x43omfortSettingType\x12$\n COMFORT_SETTING_TYPE_UNSPECIFIED\x10\x00\x12\x1f\n\x1b\x43OMFORT_SETTING_TYPE_ACTIVE\x10\x01\x12\x1e\n\x1a\x43OMFORT_SETTING_TYPE_SLEEP\x10\x02\x12\x1d\n\x19\x43OMFORT_SETTING_TYPE_AWAY\x10\x03\x12 \n\x1c\x43OMFORT_SETTING_TYPE_STANDBY\x10\x04\x12\x1f\n\x1b\x43OMFORT_SETTING_TYPE_CUSTOM\x10\x05*\x8f\x01\n\x14IndoorUnitLouverMode\x12\x1b\n\x17LOUVER_MODE_UNSPECIFIED\x10\x00\x12\x16\n\x12LOUVER_MODE_CLOSED\x10\x01\x12\x15\n\x11LOUVER_MODE_SWEEP\x10\x02\x12\x15\n\x11LOUVER_MODE_FIXED\x10\x03\x12\x14\n\x10LOUVER_MODE_AUTO\x10\x04*d\n\x0c\x46\x61nSpeedMode\x12\x1e\n\x1a\x46\x41N_SPEED_MODE_UNSPECIFIED\x10\x00\x12\x17\n\x13\x46\x41N_SPEED_MODE_AUTO\x10\x01\x12\x1b\n\x17\x46\x41N_SPEED_MODE_SETPOINT\x10\x02*N\n\tBoostMode\x12\x1a\n\x16\x42OOST_MODE_UNSPECIFIED\x10\x00\x12\x12\n\x0e\x42OOST_MODE_OFF\x10\x01\x12\x11\n\rBOOST_MODE_ON\x10\x02*\xc5\x01\n\x0eLightAnimation\x12\x1f\n\x1bLIGHT_ANIMATION_UNSPECIFIED\x10\x00\x12\x18\n\x14LIGHT_ANIMATION_NONE\x10\x01\x12 \n\x1cLIGHT_ANIMATION_SPARKLE_FADE\x10\x02\x12 \n\x1cLIGHT_ANIMATION_TWINKLE_FADE\x10\x03\x12\x19\n\x15LIGHT_ANIMATION_DANCE\x10\x04\x12\x19\n\x15LIGHT_ANIMATION_CHASE\x10\x05*R\n\nLightState\x12\x1b\n\x17LIGHT_STATE_UNSPECIFIED\x10\x00\x12\x12\n\x0eLIGHT_STATE_ON\x10\x01\x12\x13\n\x0fLIGHT_STATE_OFF\x10\x02*k\n\x0e\x43onditionState\x12\x1f\n\x1b\x43ONDITION_STATE_UNSPECIFIED\x10\x00\x12\x1c\n\x18\x43ONDITION_STATE_INACTIVE\x10\x01\x12\x1a\n\x16\x43ONDITION_STATE_ACTIVE\x10\x02*T\n\x08Presence\x12\x18\n\x14PRESENCE_UNSPECIFIED\x10\x00\x12\x17\n\x13PRESENCE_UNDETECTED\x10\x01\x12\x15\n\x11PRESENCE_DETECTED\x10\x02*\x96\x01\n\x17RemoteSensorControlMode\x12*\n&REMOTE_SENSOR_CONTROL_MODE_UNSPECIFIED\x10\x00\x12\'\n#REMOTE_SENSOR_CONTROL_MODE_DISABLED\x10\x01\x12&\n\"REMOTE_SENSOR_CONTROL_MODE_ENABLED\x10\x02*\xb6\x01\n\x07Weekday\x12\x17\n\x13WEEKDAY_UNSPECIFIED\x10\x00\x12\x12\n\x0eWEEKDAY_MONDAY\x10\x01\x12\x13\n\x0fWEEKDAY_TUESDAY\x10\x02\x12\x15\n\x11WEEKDAY_WEDNESDAY\x10\x03\x12\x14\n\x10WEEKDAY_THURSDAY\x10\x04\x12\x12\n\x0eWEEKDAY_FRIDAY\x10\x05\x12\x14\n\x10WEEKDAY_SATURDAY\x10\x06\x12\x12\n\x0eWEEKDAY_SUNDAY\x10\x07*v\n\x11ScheduleExecution\x12\"\n\x1eSCHEDULE_EXECUTION_UNSPECIFIED\x10\x00\x12\x1e\n\x1aSCHEDULE_EXECUTION_RUNNING\x10\x01\x12\x1d\n\x19SCHEDULE_EXECUTION_PAUSED\x10\x02*\x8c\x01\n\x16\x46\x61llbackControlCommand\x12(\n$FALLBACK_CONTROL_COMMAND_UNSPECIFIED\x10\x00\x12%\n!FALLBACK_CONTROL_COMMAND_COMPLETE\x10\x01\x12!\n\x1d\x46\x41LLBACK_CONTROL_COMMAND_EXIT\x10\x02*o\n\x0eOccupancyState\x12\x1f\n\x1bOCCUPANCY_STATE_UNSPECIFIED\x10\x00\x12\x1e\n\x1aOCCUPANCY_STATE_UNDETECTED\x10\x01\x12\x1c\n\x18OCCUPANCY_STATE_DETECTED\x10\x02*\xd6\x02\n\x13WifiConnectionState\x12\x1a\n\x16WIFI_STATE_UNSPECIFIED\x10\x00\x12\x1b\n\x17WIFI_STATE_DISCONNECTED\x10\x01\x12!\n\x1dWIFI_STATE_INTERFACE_DISABLED\x10\x02\x12\x17\n\x13WIFI_STATE_INACTIVE\x10\x03\x12\x17\n\x13WIFI_STATE_SCANNING\x10\x04\x12\x1d\n\x19WIFI_STATE_AUTHENTICATING\x10\x05\x12\x1a\n\x16WIFI_STATE_ASSOCIATING\x10\x06\x12\x19\n\x15WIFI_STATE_ASSOCIATED\x10\x07\x12\x1d\n\x19WIFI_STATE_4WAY_HANDSHAKE\x10\x08\x12\x1e\n\x1aWIFI_STATE_GROUP_HANDSHAKE\x10\t\x12\x1c\n\x18WIFI_STATE_WPA_COMPLETED\x10\n*\x8a\x01\n\x10\x46\x61stUpdateReason\x12\"\n\x1e\x46\x41ST_UPDATE_REASON_UNSPECIFIED\x10\x00\x12,\n(FAST_UPDATE_REASON_LOCAL_COMMS_UNHEALTHY\x10\x01\x12$\n FAST_UPDATE_REASON_USER_ACTIVITY\x10\x02\x32\x88#\n\x14HomeDatastoreService\x12\x84\x01\n\x16GetHomeDatastoreSystem\x12\x39.core.protos.home_datastore.GetHomeDatastoreSystemRequest\x1a/.core.protos.home_datastore.HomeDatastoreSystem\x12Z\n\x08GetSpace\x12+.core.protos.home_datastore.GetSpaceRequest\x1a!.core.protos.home_datastore.Space\x12`\n\x0b\x43reateSpace\x12..core.protos.home_datastore.CreateSpaceRequest\x1a!.core.protos.home_datastore.Space\x12`\n\x0bUpdateSpace\x12..core.protos.home_datastore.UpdateSpaceRequest\x1a!.core.protos.home_datastore.Space\x12U\n\x0b\x44\x65leteSpace\x12..core.protos.home_datastore.DeleteSpaceRequest\x1a\x16.google.protobuf.Empty\x12o\n\x10UpdateIndoorUnit\x12\x33.core.protos.home_datastore.UpdateIndoorUnitRequest\x1a&.core.protos.home_datastore.IndoorUnit\x12_\n\x10\x44\x65leteIndoorUnit\x12\x33.core.protos.home_datastore.DeleteIndoorUnitRequest\x1a\x16.google.protobuf.Empty\x12\x81\x01\n\x15GetIndoorUnitHardware\x12\x38.core.protos.home_datastore.GetIndoorUnitHardwareRequest\x1a..core.protos.home_datastore.IndoorUnitHardware\x12{\n\x13GetQuiltSmartModule\x12\x36.core.protos.home_datastore.GetQuiltSmartModuleRequest\x1a,.core.protos.home_datastore.QuiltSmartModule\x12o\n\x10UpdateController\x12\x33.core.protos.home_datastore.UpdateControllerRequest\x1a&.core.protos.home_datastore.Controller\x12_\n\x10\x44\x65leteController\x12\x33.core.protos.home_datastore.DeleteControllerRequest\x1a\x16.google.protobuf.Empty\x12\x81\x01\n\x15GetControllerHardware\x12\x38.core.protos.home_datastore.GetControllerHardwareRequest\x1a..core.protos.home_datastore.ControllerHardware\x12\x61\n\x11\x44\x65leteOutdoorUnit\x12\x34.core.protos.home_datastore.DeleteOutdoorUnitRequest\x1a\x16.google.protobuf.Empty\x12u\n\x12\x43reateRemoteSensor\x12\x35.core.protos.home_datastore.CreateRemoteSensorRequest\x1a(.core.protos.home_datastore.RemoteSensor\x12u\n\x12UpdateRemoteSensor\x12\x35.core.protos.home_datastore.UpdateRemoteSensorRequest\x1a(.core.protos.home_datastore.RemoteSensor\x12\x63\n\x12\x44\x65leteRemoteSensor\x12\x35.core.protos.home_datastore.DeleteRemoteSensorRequest\x1a\x16.google.protobuf.Empty\x12\x93\x01\n\x1c\x43reateControllerRemoteSensor\x12?.core.protos.home_datastore.CreateControllerRemoteSensorRequest\x1a\x32.core.protos.home_datastore.ControllerRemoteSensor\x12\x93\x01\n\x1cUpdateControllerRemoteSensor\x12?.core.protos.home_datastore.UpdateControllerRemoteSensorRequest\x1a\x32.core.protos.home_datastore.ControllerRemoteSensor\x12w\n\x1c\x44\x65leteControllerRemoteSensor\x12?.core.protos.home_datastore.DeleteControllerRemoteSensorRequest\x1a\x16.google.protobuf.Empty\x12r\n\x11\x43reateScheduleDay\x12\x34.core.protos.home_datastore.CreateScheduleDayRequest\x1a\'.core.protos.home_datastore.ScheduleDay\x12l\n\x0eGetScheduleDay\x12\x31.core.protos.home_datastore.GetScheduleDayRequest\x1a\'.core.protos.home_datastore.ScheduleDay\x12r\n\x11UpdateScheduleDay\x12\x34.core.protos.home_datastore.UpdateScheduleDayRequest\x1a\'.core.protos.home_datastore.ScheduleDay\x12\x61\n\x11\x44\x65leteScheduleDay\x12\x34.core.protos.home_datastore.DeleteScheduleDayRequest\x1a\x16.google.protobuf.Empty\x12}\n\x10ListScheduleDays\x12\x33.core.protos.home_datastore.ListScheduleDaysRequest\x1a\x34.core.protos.home_datastore.ListScheduleDaysResponse\x12u\n\x12\x43reateScheduleWeek\x12\x35.core.protos.home_datastore.CreateScheduleWeekRequest\x1a(.core.protos.home_datastore.ScheduleWeek\x12o\n\x0fGetScheduleWeek\x12\x32.core.protos.home_datastore.GetScheduleWeekRequest\x1a(.core.protos.home_datastore.ScheduleWeek\x12u\n\x12UpdateScheduleWeek\x12\x35.core.protos.home_datastore.UpdateScheduleWeekRequest\x1a(.core.protos.home_datastore.ScheduleWeek\x12\x63\n\x12\x44\x65leteScheduleWeek\x12\x35.core.protos.home_datastore.DeleteScheduleWeekRequest\x1a\x16.google.protobuf.Empty\x12\x80\x01\n\x11ListScheduleWeeks\x12\x34.core.protos.home_datastore.ListScheduleWeeksRequest\x1a\x35.core.protos.home_datastore.ListScheduleWeeksResponse\x12{\n\x14\x43reateComfortSetting\x12\x37.core.protos.home_datastore.CreateComfortSettingRequest\x1a*.core.protos.home_datastore.ComfortSetting\x12u\n\x11GetComfortSetting\x12\x34.core.protos.home_datastore.GetComfortSettingRequest\x1a*.core.protos.home_datastore.ComfortSetting\x12{\n\x14UpdateComfortSetting\x12\x37.core.protos.home_datastore.UpdateComfortSettingRequest\x1a*.core.protos.home_datastore.ComfortSetting\x12g\n\x14\x44\x65leteComfortSetting\x12\x37.core.protos.home_datastore.DeleteComfortSettingRequest\x1a\x16.google.protobuf.Empty\x12\x86\x01\n\x13ListComfortSettings\x12\x36.core.protos.home_datastore.ListComfortSettingsRequest\x1a\x37.core.protos.home_datastore.ListComfortSettingsResponse\x12i\n\x0e\x43reateLocation\x12\x31.core.protos.home_datastore.CreateLocationRequest\x1a$.core.protos.home_datastore.Location\x12\x63\n\x0bGetLocation\x12..core.protos.home_datastore.GetLocationRequest\x1a$.core.protos.home_datastore.Location\x12i\n\x0eUpdateLocation\x12\x31.core.protos.home_datastore.UpdateLocationRequest\x1a$.core.protos.home_datastore.Location\x12[\n\x0e\x44\x65leteLocation\x12\x31.core.protos.home_datastore.DeleteLocationRequest\x1a\x16.google.protobuf.Empty\x12t\n\rListLocations\x12\x30.core.protos.home_datastore.ListLocationsRequest\x1a\x31.core.protos.home_datastore.ListLocationsResponse2u\n\x0e\x43ommandService\x12\x63\n\x12RequestFastUpdates\x12\x35.core.protos.home_datastore.RequestFastUpdatesRequest\x1a\x16.google.protobuf.EmptyB$\n\x13\x63om.quilt.proto.hdsB\rQuiltHdsProtob\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -35,50 +35,52 @@ if not _descriptor._USE_C_DESCRIPTORS: _globals['DESCRIPTOR']._loaded_options = None _globals['DESCRIPTOR']._serialized_options = b'\n\023com.quilt.proto.hdsB\rQuiltHdsProto' - _globals['_HVACMODE']._serialized_start=21073 - _globals['_HVACMODE']._serialized_end=21288 - _globals['_HVACSTATE']._serialized_start=21291 - _globals['_HVACSTATE']._serialized_end=21668 - _globals['_LOCALCOMMSHEALTHSTATUS']._serialized_start=21671 - _globals['_LOCALCOMMSHEALTHSTATUS']._serialized_end=21899 - _globals['_LOCALCOMMSHEALTHREASON']._serialized_start=21902 - _globals['_LOCALCOMMSHEALTHREASON']._serialized_end=22504 - _globals['_OCCUPANCYMODE']._serialized_start=22506 - _globals['_OCCUPANCYMODE']._serialized_end=22610 - _globals['_SAFETYHEATINGMODE']._serialized_start=22612 - _globals['_SAFETYHEATINGMODE']._serialized_end=22735 - _globals['_HVACCONTROLLERTYPE']._serialized_start=22738 - _globals['_HVACCONTROLLERTYPE']._serialized_end=22947 - _globals['_COMFORTSETTINGOVERRIDE']._serialized_start=22950 - _globals['_COMFORTSETTINGOVERRIDE']._serialized_end=23261 - _globals['_COMFORTSETTINGTYPE']._serialized_start=23264 - _globals['_COMFORTSETTINGTYPE']._serialized_end=23485 - _globals['_INDOORUNITLOUVERMODE']._serialized_start=23488 - _globals['_INDOORUNITLOUVERMODE']._serialized_end=23631 - _globals['_FANSPEEDMODE']._serialized_start=23633 - _globals['_FANSPEEDMODE']._serialized_end=23733 - _globals['_BOOSTMODE']._serialized_start=23735 - _globals['_BOOSTMODE']._serialized_end=23813 - _globals['_LIGHTANIMATION']._serialized_start=23816 - _globals['_LIGHTANIMATION']._serialized_end=24013 - _globals['_LIGHTSTATE']._serialized_start=24015 - _globals['_LIGHTSTATE']._serialized_end=24097 - _globals['_CONDITIONSTATE']._serialized_start=24099 - _globals['_CONDITIONSTATE']._serialized_end=24206 - _globals['_PRESENCE']._serialized_start=24208 - _globals['_PRESENCE']._serialized_end=24292 - _globals['_REMOTESENSORCONTROLMODE']._serialized_start=24295 - _globals['_REMOTESENSORCONTROLMODE']._serialized_end=24445 - _globals['_WEEKDAY']._serialized_start=24448 - _globals['_WEEKDAY']._serialized_end=24630 - _globals['_SCHEDULEEXECUTION']._serialized_start=24632 - _globals['_SCHEDULEEXECUTION']._serialized_end=24750 - _globals['_FALLBACKCONTROLCOMMAND']._serialized_start=24753 - _globals['_FALLBACKCONTROLCOMMAND']._serialized_end=24893 - _globals['_OCCUPANCYSTATE']._serialized_start=24895 - _globals['_OCCUPANCYSTATE']._serialized_end=25006 - _globals['_WIFICONNECTIONSTATE']._serialized_start=25009 - _globals['_WIFICONNECTIONSTATE']._serialized_end=25351 + _globals['_HVACMODE']._serialized_start=21183 + _globals['_HVACMODE']._serialized_end=21398 + _globals['_HVACSTATE']._serialized_start=21401 + _globals['_HVACSTATE']._serialized_end=21778 + _globals['_LOCALCOMMSHEALTHSTATUS']._serialized_start=21781 + _globals['_LOCALCOMMSHEALTHSTATUS']._serialized_end=22009 + _globals['_LOCALCOMMSHEALTHREASON']._serialized_start=22012 + _globals['_LOCALCOMMSHEALTHREASON']._serialized_end=22614 + _globals['_OCCUPANCYMODE']._serialized_start=22616 + _globals['_OCCUPANCYMODE']._serialized_end=22720 + _globals['_SAFETYHEATINGMODE']._serialized_start=22722 + _globals['_SAFETYHEATINGMODE']._serialized_end=22845 + _globals['_HVACCONTROLLERTYPE']._serialized_start=22848 + _globals['_HVACCONTROLLERTYPE']._serialized_end=23057 + _globals['_COMFORTSETTINGOVERRIDE']._serialized_start=23060 + _globals['_COMFORTSETTINGOVERRIDE']._serialized_end=23371 + _globals['_COMFORTSETTINGTYPE']._serialized_start=23374 + _globals['_COMFORTSETTINGTYPE']._serialized_end=23595 + _globals['_INDOORUNITLOUVERMODE']._serialized_start=23598 + _globals['_INDOORUNITLOUVERMODE']._serialized_end=23741 + _globals['_FANSPEEDMODE']._serialized_start=23743 + _globals['_FANSPEEDMODE']._serialized_end=23843 + _globals['_BOOSTMODE']._serialized_start=23845 + _globals['_BOOSTMODE']._serialized_end=23923 + _globals['_LIGHTANIMATION']._serialized_start=23926 + _globals['_LIGHTANIMATION']._serialized_end=24123 + _globals['_LIGHTSTATE']._serialized_start=24125 + _globals['_LIGHTSTATE']._serialized_end=24207 + _globals['_CONDITIONSTATE']._serialized_start=24209 + _globals['_CONDITIONSTATE']._serialized_end=24316 + _globals['_PRESENCE']._serialized_start=24318 + _globals['_PRESENCE']._serialized_end=24402 + _globals['_REMOTESENSORCONTROLMODE']._serialized_start=24405 + _globals['_REMOTESENSORCONTROLMODE']._serialized_end=24555 + _globals['_WEEKDAY']._serialized_start=24558 + _globals['_WEEKDAY']._serialized_end=24740 + _globals['_SCHEDULEEXECUTION']._serialized_start=24742 + _globals['_SCHEDULEEXECUTION']._serialized_end=24860 + _globals['_FALLBACKCONTROLCOMMAND']._serialized_start=24863 + _globals['_FALLBACKCONTROLCOMMAND']._serialized_end=25003 + _globals['_OCCUPANCYSTATE']._serialized_start=25005 + _globals['_OCCUPANCYSTATE']._serialized_end=25116 + _globals['_WIFICONNECTIONSTATE']._serialized_start=25119 + _globals['_WIFICONNECTIONSTATE']._serialized_end=25461 + _globals['_FASTUPDATEREASON']._serialized_start=25464 + _globals['_FASTUPDATEREASON']._serialized_end=25602 _globals['_LOCALCOMMSSTATUS']._serialized_start=144 _globals['_LOCALCOMMSSTATUS']._serialized_end=469 _globals['_ENTITYMETADATA']._serialized_start=472 @@ -293,6 +295,10 @@ _globals['_LISTLOCATIONSREQUEST']._serialized_end=20988 _globals['_LISTLOCATIONSRESPONSE']._serialized_start=20990 _globals['_LISTLOCATIONSRESPONSE']._serialized_end=21070 - _globals['_HOMEDATASTORESERVICE']._serialized_start=25354 - _globals['_HOMEDATASTORESERVICE']._serialized_end=29842 + _globals['_REQUESTFASTUPDATESREQUEST']._serialized_start=21072 + _globals['_REQUESTFASTUPDATESREQUEST']._serialized_end=21180 + _globals['_HOMEDATASTORESERVICE']._serialized_start=25605 + _globals['_HOMEDATASTORESERVICE']._serialized_end=30093 + _globals['_COMMANDSERVICE']._serialized_start=30095 + _globals['_COMMANDSERVICE']._serialized_end=30212 # @@protoc_insertion_point(module_scope) diff --git a/src/quilt_hp/_proto/quilt_hds_pb2.pyi b/src/quilt_hp/_proto/quilt_hds_pb2.pyi index 8317011..9213fec 100644 --- a/src/quilt_hp/_proto/quilt_hds_pb2.pyi +++ b/src/quilt_hp/_proto/quilt_hds_pb2.pyi @@ -544,6 +544,42 @@ WIFI_STATE_GROUP_HANDSHAKE: WifiConnectionState.ValueType # 9 WIFI_STATE_WPA_COMPLETED: WifiConnectionState.ValueType # 10 Global___WifiConnectionState: _TypeAlias = WifiConnectionState # noqa: Y015 +class _FastUpdateReason: + ValueType = _typing.NewType("ValueType", _builtins.int) + V: _TypeAlias = ValueType # noqa: Y015 + +class _FastUpdateReasonEnumTypeWrapper(_enum_type_wrapper._EnumTypeWrapper[_FastUpdateReason.ValueType], _builtins.type): + DESCRIPTOR: _descriptor.EnumDescriptor + FAST_UPDATE_REASON_UNSPECIFIED: _FastUpdateReason.ValueType # 0 + FAST_UPDATE_REASON_LOCAL_COMMS_UNHEALTHY: _FastUpdateReason.ValueType # 1 + """Local Zenoh mesh is degraded/offline, so the app leans on the cloud to + poll the devices faster instead (there is no app-side local fast path). + """ + FAST_UPDATE_REASON_USER_ACTIVITY: _FastUpdateReason.ValueType # 2 + """The user is actively interacting in the app.""" + +class FastUpdateReason(_FastUpdateReason, metaclass=_FastUpdateReasonEnumTypeWrapper): + """--------------------------------------------------------------------------- + CommandService — imperative device commands (new in com.quilt.android 255) + --------------------------------------------------------------------------- + + A separate service in the same home_datastore package. Registered in the + cloud gRPC stub only (api.prod.quilt.cloud) — there is no local endpoint. + Field numbers, method name, and enum values APK-confirmed from + com.quilt.android versionCode 255 (request class sh1, reason enum qh1). + + Why the app is asking the cloud to raise the per-system telemetry cadence. + """ + +FAST_UPDATE_REASON_UNSPECIFIED: FastUpdateReason.ValueType # 0 +FAST_UPDATE_REASON_LOCAL_COMMS_UNHEALTHY: FastUpdateReason.ValueType # 1 +"""Local Zenoh mesh is degraded/offline, so the app leans on the cloud to +poll the devices faster instead (there is no app-side local fast path). +""" +FAST_UPDATE_REASON_USER_ACTIVITY: FastUpdateReason.ValueType # 2 +"""The user is actively interacting in the app.""" +Global___FastUpdateReason: _TypeAlias = FastUpdateReason # noqa: Y015 + @_typing.final class LocalCommsStatus(_message.Message): """Local communications status for a QSM or Controller mesh node. @@ -3995,3 +4031,25 @@ class ListLocationsResponse(_message.Message): def WhichOneof(self, oneof_group: _Never) -> None: ... Global___ListLocationsResponse: _TypeAlias = ListLocationsResponse # noqa: Y015 + +@_typing.final +class RequestFastUpdatesRequest(_message.Message): + DESCRIPTOR: _descriptor.Descriptor + + SYSTEM_ID_FIELD_NUMBER: _builtins.int + REASON_FIELD_NUMBER: _builtins.int + system_id: _builtins.str + reason: Global___FastUpdateReason.ValueType + def __init__( + self, + *, + system_id: _builtins.str = ..., + reason: Global___FastUpdateReason.ValueType = ..., + ) -> None: ... + _HasFieldArgType: _TypeAlias = _Never # noqa: Y015 + def HasField(self, field_name: _HasFieldArgType) -> _builtins.bool: ... + _ClearFieldArgType: _TypeAlias = _typing.Literal["reason", b"reason", "system_id", b"system_id"] # noqa: Y015 + def ClearField(self, field_name: _ClearFieldArgType) -> None: ... + def WhichOneof(self, oneof_group: _Never) -> None: ... + +Global___RequestFastUpdatesRequest: _TypeAlias = RequestFastUpdatesRequest # noqa: Y015 diff --git a/src/quilt_hp/_proto/quilt_hds_pb2_grpc.py b/src/quilt_hp/_proto/quilt_hds_pb2_grpc.py index 3b03ad3..36d5f5a 100644 --- a/src/quilt_hp/_proto/quilt_hds_pb2_grpc.py +++ b/src/quilt_hp/_proto/quilt_hds_pb2_grpc.py @@ -1730,3 +1730,75 @@ def ListLocations(request, timeout, metadata, _registered_method=True) + + +class CommandServiceStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.RequestFastUpdates = channel.unary_unary( + '/core.protos.home_datastore.CommandService/RequestFastUpdates', + request_serializer=quilt__hds__pb2.RequestFastUpdatesRequest.SerializeToString, + response_deserializer=google_dot_protobuf_dot_empty__pb2.Empty.FromString, + _registered_method=True) + + +class CommandServiceServicer(object): + """Missing associated documentation comment in .proto file.""" + + def RequestFastUpdates(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CommandServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'RequestFastUpdates': grpc.unary_unary_rpc_method_handler( + servicer.RequestFastUpdates, + request_deserializer=quilt__hds__pb2.RequestFastUpdatesRequest.FromString, + response_serializer=google_dot_protobuf_dot_empty__pb2.Empty.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'core.protos.home_datastore.CommandService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('core.protos.home_datastore.CommandService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class CommandService(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def RequestFastUpdates(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/core.protos.home_datastore.CommandService/RequestFastUpdates', + quilt__hds__pb2.RequestFastUpdatesRequest.SerializeToString, + google_dot_protobuf_dot_empty__pb2.Empty.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/src/quilt_hp/cli/main.py b/src/quilt_hp/cli/main.py index 2417408..31107b1 100644 --- a/src/quilt_hp/cli/main.py +++ b/src/quilt_hp/cli/main.py @@ -7,6 +7,7 @@ import sys from collections.abc import AsyncIterator, Callable, Coroutine, Sequence from contextlib import asynccontextmanager +from dataclasses import asdict from enum import StrEnum from functools import wraps from typing import Any, Protocol, cast @@ -158,6 +159,18 @@ def _space_name_by_id(snap: SystemSnapshot) -> dict[str, str]: return {space.id: space.name for space in snap.spaces} +def _fmt_c(value: float | None) -> str: + return "n/a" if value is None else f"{value:.1f}°C" + + +def _fmt_pct(value: float | None) -> str: + return "n/a" if value is None else f"{value:.0f}%" + + +def _fmt_w(value: float | None) -> str: + return "n/a" if value is None else f"{value:.2f}W" + + def _snapshot_payload(snap: SystemSnapshot) -> dict[str, Any]: space_names = _space_name_by_id(snap) update_refs: dict[str, list[str]] = {} @@ -651,6 +664,84 @@ async def _values() -> None: _run(_values()) +@app.command() +def diagnostics( + email: str | None = typer.Option(None, envvar="QUILT_EMAIL", help="Quilt account email"), + home: str | None = typer.Option(None, help="Specific home name to connect to"), + faults_only: bool = typer.Option( + False, "--faults-only", help="Only show indoor units with an active fault condition." + ), + output: OutputMode = typer.Option( # noqa: B008 + OutputMode.SUMMARY, + "--output", + "-o", + help="Output mode: summary or json", + ), +) -> None: + """Show system diagnostics: fault conditions, refrigerant temps, and power. + + Assembles the installer-style diagnostic view from data the cloud API + returns on the indoor units. The outdoor unit's own raw sensors (compressor + Hz, pressures, discharge temp) are withheld from the cloud plane and are not + shown. + """ + email, home = _resolve(email, home) + + async def _diagnostics() -> None: + async with _client_snapshot(email, home) as (_, snapshot): + diag = snapshot.diagnostics() + + if output == OutputMode.JSON: + console.print(json.dumps(asdict(diag), indent=2, sort_keys=True, default=str)) + return + + idus = ( + [d for d in diag.indoor_units if d.active_faults] + if faults_only + else (diag.indoor_units) + ) + fault_total = len(diag.active_faults) + header = ( + f"[red]{fault_total} active fault(s)[/red]" + if fault_total + else "[green]No active faults[/green]" + ) + console.print(f"[bold]Diagnostics[/bold] — {header}\n") + + if not idus: + console.print(" (no indoor units to show)") + + for d in idus: + status = "online" if d.online else "[dim]offline[/dim]" + faults = ( + "[red]" + ", ".join(d.active_faults) + "[/red]" if d.active_faults else "none" + ) + name = d.name or d.space_name or d.indoor_unit_id + console.print( + f"[bold]{name}[/bold] ({d.space_name}) — {status}, state={d.hvac_state}" + ) + console.print(f" faults: {faults}") + console.print( + " refrigerant: " + f"coil={_fmt_c(d.coil_temperature_c)} " + f"gas={_fmt_c(d.gas_pipe_temperature_c)} " + f"liquid={_fmt_c(d.liquid_pipe_temperature_c)} " + f"inlet={_fmt_c(d.inlet_temperature_c)} " + f"outlet={_fmt_c(d.outlet_temperature_c)} " + f"humidity={_fmt_pct(d.inlet_humidity_pct)}" + ) + console.print(f" power: {_fmt_w(d.hvac_power_w)}\n") + + console.print("[bold]Outdoor Units[/bold]") + for o in diag.outdoor_units: + console.print( + f" {o.outdoor_unit_id} state={o.hvac_state} " + f"raw_sensors={'yes' if o.raw_sensors_available else 'no (cloud-withheld)'}" + ) + + _run(_diagnostics()) + + @app.command() def presets( email: str | None = typer.Option(None, envvar="QUILT_EMAIL", help="Quilt account email"), diff --git a/src/quilt_hp/client.py b/src/quilt_hp/client.py index 1004d19..3e8b238 100644 --- a/src/quilt_hp/client.py +++ b/src/quilt_hp/client.py @@ -23,6 +23,8 @@ from quilt_hp.auth import OtpCallback, authenticate from quilt_hp.const import Environment from quilt_hp.exceptions import QuiltAuthError, QuiltError, QuiltNotFoundError +from quilt_hp.models.enums import FastUpdateReason +from quilt_hp.services.command import CommandService from quilt_hp.services.hds import HomeDatastoreService from quilt_hp.services.streaming import NotifierStream from quilt_hp.services.system import SystemInformationService @@ -44,6 +46,7 @@ import grpc.aio from quilt_hp.models.comfort import ComfortSetting + from quilt_hp.models.diagnostics import SystemDiagnostics from quilt_hp.models.energy import SpaceEnergyMetrics from quilt_hp.models.enums import FanSpeed, HVACMode, LouverMode from quilt_hp.models.indoor_unit import IndoorUnit @@ -102,6 +105,7 @@ def __init__( self._hds: HomeDatastoreService | None = None self._sysinfo: SystemInformationService | None = None self._user_svc: UserService | None = None + self._command: CommandService | None = None # Snapshot cache self._snapshot_cache: SystemSnapshot | None = None @@ -135,6 +139,7 @@ def _ensure_channel(self) -> grpc.aio.Channel: self._hds = HomeDatastoreService(self._channel) self._sysinfo = SystemInformationService(self._channel) self._user_svc = UserService(self._channel) + self._command = CommandService(self._channel) return self._channel def _require_channel(self) -> grpc.aio.Channel: @@ -157,6 +162,11 @@ def _require_user_service(self) -> UserService: raise QuiltError("Client not connected. Call login() first.") return self._user_svc + def _require_command(self) -> CommandService: + if self._command is None: + raise QuiltError("Client not connected. Call login() first.") + return self._command + async def _resolve_system_id(self, system_id: str | None = None) -> str: return system_id or await self.get_system_id() @@ -313,6 +323,19 @@ def invalidate_snapshot(self) -> None: self._snapshot_cache = None self._snapshot_cached_at = 0.0 + async def get_diagnostics(self, system_id: str | None = None) -> SystemDiagnostics: + """Fetch the installer-style diagnostic view for a system. + + Convenience wrapper over ``get_snapshot().diagnostics()``. Returns the + per-indoor-unit fault/condition matrix (including outdoor-unit and + refrigerant conditions surfaced through each IDU), refrigerant-circuit + temperatures, and per-unit power — the diagnostic data the cloud plane + exposes. The outdoor unit's own raw sensors are not included (see + :class:`~quilt_hp.models.diagnostics.OutdoorUnitDiagnostics`). + """ + snapshot = await self.get_snapshot(system_id) + return snapshot.diagnostics() + # --- Space control --- async def list_spaces(self) -> list[Space]: @@ -586,6 +609,30 @@ async def get_energy( sid = await self._resolve_system_id(system_id) return await self._require_sysinfo().get_energy_metrics(sid, start, end) + # --- Telemetry cadence --- + + async def request_fast_updates( + self, + *, + reason: FastUpdateReason = FastUpdateReason.USER_ACTIVITY, + system_id: str | None = None, + ) -> None: + """Ask the cloud to raise the telemetry cadence for a system. + + Calls ``CommandService/RequestFastUpdates`` — the same lever the mobile + app pulls when the user is active or a device's local mesh is degraded. + The effect is a faster stream of state updates over the + NotifierService stream; there is no return value. + + Args: + reason: Why fast updates are being requested (default: + ``USER_ACTIVITY``). + system_id: Target system; defaults to the client's resolved system. + """ + command = self._require_command() + sid = await self._resolve_system_id(system_id) + await command.request_fast_updates(sid, reason) + # --- Streaming --- def stream( @@ -695,6 +742,7 @@ async def close(self) -> None: self._hds = None self._sysinfo = None self._user_svc = None + self._command = None async def __aenter__(self) -> Self: return self diff --git a/src/quilt_hp/models/__init__.py b/src/quilt_hp/models/__init__.py index 03af586..aef5e12 100644 --- a/src/quilt_hp/models/__init__.py +++ b/src/quilt_hp/models/__init__.py @@ -2,6 +2,11 @@ from quilt_hp.models.comfort import ComfortSetting from quilt_hp.models.controller import Controller +from quilt_hp.models.diagnostics import ( + IndoorUnitDiagnostics, + OutdoorUnitDiagnostics, + SystemDiagnostics, +) from quilt_hp.models.energy import EnergyBucket, SpaceEnergyMetrics from quilt_hp.models.enums import ( BoostMode, @@ -10,6 +15,7 @@ ConditionState, FallbackControlCommand, FanSpeed, + FastUpdateReason, HvacControllerType, HVACMode, HVACState, @@ -28,6 +34,7 @@ from quilt_hp.models.indoor_unit import ( IndoorUnit, IndoorUnitCommands, + IndoorUnitConditions, IndoorUnitControls, IndoorUnitSettings, IndoorUnitState, @@ -55,12 +62,15 @@ "EnergyBucket", "FallbackControlCommand", "FanSpeed", + "FastUpdateReason", "HVACMode", "HVACState", "HvacControllerType", "IndoorUnit", "IndoorUnitCommands", + "IndoorUnitConditions", "IndoorUnitControls", + "IndoorUnitDiagnostics", "IndoorUnitSettings", "IndoorUnitState", "LedAnimation", @@ -74,6 +84,7 @@ "OccupancyMode", "OccupancyState", "OutdoorUnit", + "OutdoorUnitDiagnostics", "RemoteSensor", "RemoteSensorControlMode", "SafetyHeatingMode", @@ -86,6 +97,7 @@ "SpaceEnergyMetrics", "SpaceSettings", "SpaceState", + "SystemDiagnostics", "SystemInfo", "SystemSnapshot", ] diff --git a/src/quilt_hp/models/diagnostics.py b/src/quilt_hp/models/diagnostics.py new file mode 100644 index 0000000..70c65ab --- /dev/null +++ b/src/quilt_hp/models/diagnostics.py @@ -0,0 +1,123 @@ +"""Derived diagnostics view over a system snapshot. + +Assembles the "installer-style" diagnostic picture from data the normal cloud +API already returns. Most of what a Quilt installer inspects lives on the +**indoor unit** objects — the fault/condition matrix (including outdoor-unit and +refrigerant conditions surfaced through the IDU), the refrigerant-circuit +temperatures, and per-unit power. + +The outdoor unit's *own* raw sensors (compressor frequency, suction/discharge +pressures, discharge temperature) are withheld from the cloud plane, so +:class:`OutdoorUnitDiagnostics` reports only whether they are present (they are +not, over the cloud) alongside the ODU's coarse ``hvac_state``. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from quilt_hp.models.enums import ConditionState, HVACState + from quilt_hp.models.indoor_unit import IndoorUnit + from quilt_hp.models.outdoor_unit import OutdoorUnit + + +@dataclass(slots=True) +class IndoorUnitDiagnostics: + """Per-indoor-unit diagnostic summary derived from a snapshot.""" + + indoor_unit_id: str + name: str + space_id: str + space_name: str + online: bool + hvac_state: HVACState + #: Names of conditions currently ``ACTIVE`` (i.e. faults). Empty when healthy. + active_faults: list[str] + #: Every condition mapped to its state; empty when the IDU sent no conditions. + conditions: dict[str, ConditionState] + # Refrigerant-circuit telemetry (from IndoorUnit.performance_data); None if absent. + coil_temperature_c: float | None + gas_pipe_temperature_c: float | None + liquid_pipe_temperature_c: float | None + inlet_temperature_c: float | None + outlet_temperature_c: float | None + inlet_humidity_pct: float | None + # Power (from IndoorUnit.performance_metrics); None if absent. + hvac_power_w: float | None + + @classmethod + def from_indoor_unit(cls, idu: IndoorUnit, space_name: str = "") -> IndoorUnitDiagnostics: + """Build a diagnostics summary for a single indoor unit.""" + conditions = idu.conditions + pd = idu.performance_data + pm = idu.performance_metrics + return cls( + indoor_unit_id=idu.id, + name=idu.settings.name or space_name, + space_id=idu.space_id, + space_name=space_name, + online=idu.is_online, + hvac_state=idu.state.hvac_state, + active_faults=conditions.active if conditions is not None else [], + conditions=conditions.states() if conditions is not None else {}, + coil_temperature_c=pd.coil_temperature_c if pd is not None else None, + gas_pipe_temperature_c=pd.gas_pipe_temperature_c if pd is not None else None, + liquid_pipe_temperature_c=pd.liquid_pipe_temperature_c if pd is not None else None, + inlet_temperature_c=pd.inlet_temperature_c if pd is not None else None, + outlet_temperature_c=pd.outlet_temperature_c if pd is not None else None, + inlet_humidity_pct=pd.inlet_humidity_pct if pd is not None else None, + hvac_power_w=pm.hvac_power_w if pm is not None else None, + ) + + +@dataclass(slots=True) +class OutdoorUnitDiagnostics: + """Per-outdoor-unit diagnostic summary. + + ``raw_sensors_available`` reflects whether the ODU's ``performance_data`` + (compressor Hz, pressures, coil/exhaust temps) is present. Over the cloud + plane it never is — that telemetry is only reachable on the local/hardware + track. The refrigerant conditions and pipe temperatures for this ODU's + circuit are instead surfaced through its indoor units (see + :class:`IndoorUnitDiagnostics`). + """ + + outdoor_unit_id: str + hvac_state: HVACState + raw_sensors_available: bool + + @classmethod + def from_outdoor_unit(cls, odu: OutdoorUnit) -> OutdoorUnitDiagnostics: + return cls( + outdoor_unit_id=odu.id, + hvac_state=odu.hvac_state, + raw_sensors_available=odu.performance_data is not None, + ) + + +@dataclass(slots=True) +class SystemDiagnostics: + """Whole-system diagnostic view — the installer-style summary. + + Obtain one from :meth:`SystemSnapshot.diagnostics` or + :meth:`QuiltClient.get_diagnostics`. + """ + + indoor_units: list[IndoorUnitDiagnostics] + outdoor_units: list[OutdoorUnitDiagnostics] + + @property + def active_faults(self) -> list[tuple[str, str]]: + """``(indoor_unit_id, condition_name)`` for every ACTIVE fault, system-wide.""" + return [ + (d.indoor_unit_id, condition) + for d in self.indoor_units + for condition in d.active_faults + ] + + @property + def has_faults(self) -> bool: + """True if any indoor unit reports an ACTIVE fault condition.""" + return any(d.active_faults for d in self.indoor_units) diff --git a/src/quilt_hp/models/enums.py b/src/quilt_hp/models/enums.py index 1910e3f..09a20ea 100644 --- a/src/quilt_hp/models/enums.py +++ b/src/quilt_hp/models/enums.py @@ -389,3 +389,23 @@ class LocalCommsHealthReason(IntEnum): def __str__(self) -> str: return self.name + + +class FastUpdateReason(IntEnum): + """Reason passed to ``CommandService/RequestFastUpdates``. + + The app calls ``RequestFastUpdates`` to ask the cloud to raise the + telemetry cadence for a system. APK-confirmed from com.quilt.android + versionCode 255 (reason enum ``qh1``). + + ``LOCAL_COMMS_UNHEALTHY`` is sent when the device's local Zenoh mesh is + degraded and the app is falling back to cloud polling; ``USER_ACTIVITY`` + is sent while the user is actively interacting in the app. + """ + + UNSPECIFIED = 0 + LOCAL_COMMS_UNHEALTHY = 1 + USER_ACTIVITY = 2 + + def __str__(self) -> str: + return self.name diff --git a/src/quilt_hp/models/indoor_unit.py b/src/quilt_hp/models/indoor_unit.py index 0609673..9eb9261 100644 --- a/src/quilt_hp/models/indoor_unit.py +++ b/src/quilt_hp/models/indoor_unit.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from datetime import UTC, datetime -from typing import Any, cast +from typing import Any, ClassVar, cast from quilt_hp.const import ( ABSENT_FAN_SPEED_MODE_SENTINEL, @@ -12,6 +12,7 @@ ) from quilt_hp.models._helpers import lookup_hardware, present_submsg, timestamp_or_none from quilt_hp.models.enums import ( + ConditionState, FallbackControlCommand, FanSpeed, HvacControllerType, @@ -169,7 +170,29 @@ class IndoorUnitCommands: @dataclass(slots=True) class IndoorUnitConditions: - """IDU diagnostic conditions (ODU-linked conditions included).""" + """IDU diagnostic conditions (ODU-linked conditions included). + + Each field is a raw ``ConditionState`` wire value (``UNSPECIFIED=0``, + ``INACTIVE=1``, ``ACTIVE=2``). Several conditions describe the outdoor + unit / refrigerant system as reported through the indoor unit — e.g. + ``outdoor_unit_communication_error``, ``defrost_cycle``, ``oil_return``. + """ + + #: All condition field names, in wire order. Shared by the helpers below. + FIELD_NAMES: ClassVar[tuple[str, ...]] = ( + "mode_conflict", + "anti_cold_wind", + "abnormal_outdoor_air_temperature", + "hvac_mode_switching_delay", + "defrost_cycle", + "safety_heating", + "oil_return", + "modbus_communication_error", + "coil_preheat", + "mode_conflict_avoidance", + "outdoor_unit_communication_error", + "compressor_minimum_run_time", + ) mode_conflict: int anti_cold_wind: int @@ -184,26 +207,19 @@ class IndoorUnitConditions: outdoor_unit_communication_error: int compressor_minimum_run_time: int = 0 + def states(self) -> dict[str, ConditionState]: + """Every condition mapped to its :class:`ConditionState`.""" + return {name: ConditionState(getattr(self, name)) for name in self.FIELD_NAMES} + + @property + def active(self) -> list[str]: + """Names of the conditions currently ``ACTIVE`` (i.e. faults).""" + return [name for name in self.FIELD_NAMES if getattr(self, name) == ConditionState.ACTIVE] + @property def any_active(self) -> bool: """True if any condition is ACTIVE (value 2).""" - return any( - getattr(self, f) == 2 - for f in ( - "mode_conflict", - "anti_cold_wind", - "abnormal_outdoor_air_temperature", - "hvac_mode_switching_delay", - "defrost_cycle", - "safety_heating", - "oil_return", - "modbus_communication_error", - "coil_preheat", - "mode_conflict_avoidance", - "outdoor_unit_communication_error", - "compressor_minimum_run_time", - ) - ) + return bool(self.active) @dataclass(slots=True) diff --git a/src/quilt_hp/models/system.py b/src/quilt_hp/models/system.py index 10684f5..a1551c2 100644 --- a/src/quilt_hp/models/system.py +++ b/src/quilt_hp/models/system.py @@ -8,6 +8,11 @@ from quilt_hp.models._helpers import _id_variants from quilt_hp.models.comfort import ComfortSetting from quilt_hp.models.controller import Controller +from quilt_hp.models.diagnostics import ( + IndoorUnitDiagnostics, + OutdoorUnitDiagnostics, + SystemDiagnostics, +) from quilt_hp.models.enums import ( ComfortSettingType, HVACMode, @@ -576,6 +581,26 @@ def stream_topics(self) -> list[str]: topics.append(f"hds/software_update_info/{sui.id}") return topics + def diagnostics(self) -> SystemDiagnostics: + """Assemble the installer-style diagnostic view from this snapshot. + + Returns per-indoor-unit fault conditions (including the outdoor-unit / + refrigerant conditions surfaced through each IDU), refrigerant-circuit + temperatures, and power, plus a coarse per-outdoor-unit entry. The ODU's + own raw sensors are not on the cloud plane; see + :class:`~quilt_hp.models.diagnostics.OutdoorUnitDiagnostics`. + """ + space_names = {space.id: space.name for space in self.spaces} + return SystemDiagnostics( + indoor_units=[ + IndoorUnitDiagnostics.from_indoor_unit(idu, space_names.get(idu.space_id, "")) + for idu in self.indoor_units + ], + outdoor_units=[ + OutdoorUnitDiagnostics.from_outdoor_unit(odu) for odu in self.outdoor_units + ], + ) + @classmethod def from_proto(cls, proto: object) -> SystemSnapshot: """Construct from a protobuf HomeDatastoreSystem message.""" diff --git a/src/quilt_hp/services/command.py b/src/quilt_hp/services/command.py new file mode 100644 index 0000000..5127eac --- /dev/null +++ b/src/quilt_hp/services/command.py @@ -0,0 +1,62 @@ +"""CommandService. + +Imperative device commands. New in com.quilt.android versionCode 255. + +Currently a single method, ``RequestFastUpdates``, which asks the cloud to +raise the telemetry cadence for a system. Registered in the cloud gRPC stub +only — there is no local endpoint. +""" + +from __future__ import annotations + +import logging +from collections.abc import Callable +from typing import TYPE_CHECKING, Protocol, cast + +if TYPE_CHECKING: + import grpc.aio + +from quilt_hp._proto import quilt_hds_pb2 as hds +from quilt_hp._proto import quilt_hds_pb2_grpc as hds_grpc +from quilt_hp.models.enums import FastUpdateReason +from quilt_hp.services import grpc_call + +logger = logging.getLogger(__name__) + + +class _CommandServiceStub(Protocol): + async def RequestFastUpdates(self, request: hds.RequestFastUpdatesRequest) -> object: ... + + +class CommandService: + """Async wrapper for CommandService gRPC methods.""" + + def __init__(self, channel: grpc.aio.Channel) -> None: + factory = cast( + "Callable[[grpc.aio.Channel], _CommandServiceStub]", + hds_grpc.CommandServiceStub, + ) + self._stub: _CommandServiceStub = factory(channel) + + async def request_fast_updates( + self, + system_id: str, + reason: FastUpdateReason = FastUpdateReason.USER_ACTIVITY, + ) -> None: + """Ask the cloud to raise the telemetry cadence for a system. + + Mirrors what the mobile app does when the user is active or the + device's local mesh is degraded. The response is empty; the effect + (a faster stream of state updates) is observed on the NotifierService + stream, not in the return value. + """ + logger.debug( + "RPC RequestFastUpdates system_id=%s reason=%s", system_id, FastUpdateReason(reason) + ) + async with grpc_call("RequestFastUpdates"): + await self._stub.RequestFastUpdates( + hds.RequestFastUpdatesRequest( + system_id=system_id, + reason=cast("hds.FastUpdateReason.ValueType", reason.value), + ) + ) diff --git a/tests/test_command.py b/tests/test_command.py new file mode 100644 index 0000000..cf2b9e4 --- /dev/null +++ b/tests/test_command.py @@ -0,0 +1,86 @@ +"""Tests for CommandService/RequestFastUpdates and the client wrapper.""" + +from __future__ import annotations + +from unittest.mock import AsyncMock, MagicMock + +import grpc +import pytest + +from quilt_hp._proto import quilt_hds_pb2 as hds +from quilt_hp.client import QuiltClient +from quilt_hp.exceptions import QuiltError +from quilt_hp.models.enums import FastUpdateReason +from quilt_hp.services import command as command_service + + +class _FakeRpcError(grpc.aio.AioRpcError): + def __init__(self, code: grpc.StatusCode, details: str = "") -> None: + self._code = code + self._details = details + + def code(self) -> grpc.StatusCode: # type: ignore[override] + return self._code + + def details(self) -> str: # type: ignore[override] + return self._details + + +@pytest.mark.asyncio +async def test_request_fast_updates_builds_request(monkeypatch: pytest.MonkeyPatch) -> None: + stub = MagicMock(RequestFastUpdates=AsyncMock(return_value=None)) + monkeypatch.setattr(command_service.hds_grpc, "CommandServiceStub", lambda _ch: stub) + + svc = command_service.CommandService(MagicMock()) + await svc.request_fast_updates("sys-1", FastUpdateReason.LOCAL_COMMS_UNHEALTHY) + + stub.RequestFastUpdates.assert_awaited_once() + sent = stub.RequestFastUpdates.await_args.args[0] + assert isinstance(sent, hds.RequestFastUpdatesRequest) + assert sent.system_id == "sys-1" + assert sent.reason == hds.FAST_UPDATE_REASON_LOCAL_COMMS_UNHEALTHY + + +@pytest.mark.asyncio +async def test_request_fast_updates_default_reason(monkeypatch: pytest.MonkeyPatch) -> None: + stub = MagicMock(RequestFastUpdates=AsyncMock(return_value=None)) + monkeypatch.setattr(command_service.hds_grpc, "CommandServiceStub", lambda _ch: stub) + + svc = command_service.CommandService(MagicMock()) + await svc.request_fast_updates("sys-1") + + sent = stub.RequestFastUpdates.await_args.args[0] + assert sent.reason == hds.FAST_UPDATE_REASON_USER_ACTIVITY + + +@pytest.mark.asyncio +async def test_request_fast_updates_translates_grpc_error(monkeypatch: pytest.MonkeyPatch) -> None: + stub = MagicMock( + RequestFastUpdates=AsyncMock(side_effect=_FakeRpcError(grpc.StatusCode.INTERNAL, "boom")) + ) + monkeypatch.setattr(command_service.hds_grpc, "CommandServiceStub", lambda _ch: stub) + + svc = command_service.CommandService(MagicMock()) + with pytest.raises(QuiltError, match="RequestFastUpdates failed"): + await svc.request_fast_updates("sys-1") + + +@pytest.mark.asyncio +async def test_client_request_fast_updates_resolves_system( + monkeypatch: pytest.MonkeyPatch, +) -> None: + client = QuiltClient("user@example.com") + command = MagicMock(request_fast_updates=AsyncMock(return_value=None)) + client._command = command + monkeypatch.setattr(client, "_resolve_system_id", AsyncMock(return_value="sys-42")) + + await client.request_fast_updates(reason=FastUpdateReason.USER_ACTIVITY) + + command.request_fast_updates.assert_awaited_once_with("sys-42", FastUpdateReason.USER_ACTIVITY) + + +@pytest.mark.asyncio +async def test_client_request_fast_updates_requires_connection() -> None: + client = QuiltClient("user@example.com") + with pytest.raises(QuiltError, match="not connected"): + await client.request_fast_updates() diff --git a/tests/test_diagnostics.py b/tests/test_diagnostics.py new file mode 100644 index 0000000..9ac8d73 --- /dev/null +++ b/tests/test_diagnostics.py @@ -0,0 +1,346 @@ +"""Tests for the diagnostics feature (conditions helpers, SystemDiagnostics, CLI).""" + +from __future__ import annotations + +from datetime import UTC, datetime +from unittest.mock import AsyncMock, patch + +from typer.testing import CliRunner + +from quilt_hp.cli import main as cli_main +from quilt_hp.client import QuiltClient +from quilt_hp.models.diagnostics import ( + IndoorUnitDiagnostics, + OutdoorUnitDiagnostics, + SystemDiagnostics, +) +from quilt_hp.models.enums import ( + ComfortSettingOverride, + ConditionState, + FanSpeed, + HVACMode, + HVACState, + LouverMode, + OccupancyMode, + SafetyHeatingMode, +) +from quilt_hp.models.indoor_unit import ( + IndoorUnit, + IndoorUnitConditions, + IndoorUnitControls, + IndoorUnitPerformanceData, + IndoorUnitPerformanceMetrics, + IndoorUnitSettings, + IndoorUnitState, +) +from quilt_hp.models.outdoor_unit import OutdoorUnit +from quilt_hp.models.space import Space, SpaceControls, SpaceSettings, SpaceState +from quilt_hp.models.system import SystemSnapshot + +runner = CliRunner() + + +def _conditions(**active: bool) -> IndoorUnitConditions: + """Build conditions with the named fields ACTIVE(2), the rest INACTIVE(1).""" + values = dict.fromkeys(IndoorUnitConditions.FIELD_NAMES, int(ConditionState.INACTIVE)) + for name, is_active in active.items(): + values[name] = int(ConditionState.ACTIVE if is_active else ConditionState.INACTIVE) + return IndoorUnitConditions(**values) + + +def _make_idu( + idu_id: str = "idu-1", + space_id: str = "space-1", + *, + online: bool = True, + conditions: IndoorUnitConditions | None = None, + with_perf: bool = True, +) -> IndoorUnit: + perf = ( + IndoorUnitPerformanceData( + measurement_interval_s=5.0, + energy_measurement_j=11.0, + hvac_mode=HVACMode.FAN, + hvac_state=HVACState.STANDBY, + actual_fan_speed_rpm=0.0, + outlet_temperature_c=21.0, + inlet_temperature_c=21.4, + inlet_humidity_pct=65.0, + coil_temperature_c=24.0, + gas_pipe_temperature_c=16.0, + liquid_pipe_temperature_c=16.0, + ) + if with_perf + else None + ) + metrics = ( + IndoorUnitPerformanceMetrics( + capacity_w=0.0, + coefficient_of_performance=0.0, + hvac_power_w=2.26, + led_power_w=0.0, + hvac_mode=HVACMode.FAN, + hvac_state=HVACState.STANDBY, + ) + if with_perf + else None + ) + return IndoorUnit( + id=idu_id, + system_id="sys-1", + space_id=space_id, + outdoor_unit_id=None, + hardware_id="hw-1", + qsm_id=None, + settings=IndoorUnitSettings( + name="IDU One", + description="", + light_brightness_default_percent=0.0, + presence_fence_left_m=0.0, + presence_fence_right_m=0.0, + presence_fence_forward_m=0.0, + radar_sensor_distance_from_floor_m=0.0, + ), + controls=IndoorUnitControls( + fan_speed=FanSpeed.AUTO, + louver_mode=LouverMode.SWEEP, + louver_fixed_position=0.0, + led_color_code=0, + led_brightness=0.0, + led_animation=1, + ), + state=IndoorUnitState( + hvac_mode=HVACMode.FAN, + hvac_state=HVACState.STANDBY, + ambient_temperature_c=21.0, + ambient_humidity_percent=65.0, + fan_speed_rpm=0.0, + fan_speed_setpoint_rpm=0.0, + presence_detection_level=0.0, + updated_at=datetime.now(tz=UTC) if online else None, + ), + hvac_inputs=None, + conditions=conditions, + performance_data=perf, + performance_metrics=metrics, + presence=None, + occupancy=None, + commands=None, + ) + + +def _make_odu(odu_id: str = "odu-1") -> OutdoorUnit: + return OutdoorUnit( + id=odu_id, + system_id="sys-1", + space_id="space-1", + hvac_state=HVACState.UNSPECIFIED, + model_sku="N/A", + serial_number="QU1-TEST", + firmware_version="46", + firmware_update_info_id=None, + performance_data=None, + ) + + +def _make_space() -> Space: + return Space( + id="space-1", + system_id="sys-1", + name="Living Room", + parent_space_id="root", + settings=SpaceSettings( + name="Living Room", + timezone="UTC", + occupancy_mode=OccupancyMode.ENABLED, + occupied_timeout_s=180.0, + unoccupied_timeout_s=1200.0, + safety_heating=SafetyHeatingMode.ENABLED, + ), + controls=SpaceControls( + hvac_mode=HVACMode.FAN, + temperature_setpoint_c=21.0, + cooling_setpoint_c=24.0, + heating_setpoint_c=20.0, + comfort_setting_id="", + comfort_setting_override=ComfortSettingOverride.NONE, + ), + state=SpaceState( + ambient_temperature_c=21.0, + hvac_state=HVACState.STANDBY, + setpoint_c=21.0, + comfort_setting_id="", + ), + ) + + +def _snapshot(idus: list[IndoorUnit], odus: list[OutdoorUnit]) -> SystemSnapshot: + return SystemSnapshot( + spaces=[_make_space()], + indoor_units=idus, + outdoor_units=odus, + controllers=[], + quilt_smart_modules=[], + comfort_settings=[], + schedule_weeks=[], + schedule_days=[], + remote_sensors=[], + controller_remote_sensors=[], + software_update_infos=[], + locations=[], + timezone="UTC", + ) + + +# --- condition helpers ------------------------------------------------------- + + +def test_conditions_active_and_states() -> None: + conds = _conditions(defrost_cycle=True, outdoor_unit_communication_error=True) + assert conds.any_active is True + assert set(conds.active) == {"defrost_cycle", "outdoor_unit_communication_error"} + states = conds.states() + assert len(states) == len(IndoorUnitConditions.FIELD_NAMES) + assert states["defrost_cycle"] is ConditionState.ACTIVE + assert states["mode_conflict"] is ConditionState.INACTIVE + + +def test_conditions_healthy() -> None: + conds = _conditions() + assert conds.any_active is False + assert conds.active == [] + + +# --- IndoorUnitDiagnostics --------------------------------------------------- + + +def test_idu_diagnostics_from_populated_unit() -> None: + idu = _make_idu(conditions=_conditions(oil_return=True)) + diag = IndoorUnitDiagnostics.from_indoor_unit(idu, space_name="Living Room") + assert diag.indoor_unit_id == "idu-1" + assert diag.name == "IDU One" + assert diag.space_name == "Living Room" + assert diag.online is True + assert diag.active_faults == ["oil_return"] + assert diag.coil_temperature_c == 24.0 + assert diag.gas_pipe_temperature_c == 16.0 + assert diag.hvac_power_w == 2.26 + + +def test_idu_diagnostics_handles_absent_submessages() -> None: + idu = _make_idu(conditions=None, with_perf=False, online=False) + diag = IndoorUnitDiagnostics.from_indoor_unit(idu) + assert diag.online is False + assert diag.active_faults == [] + assert diag.conditions == {} + assert diag.coil_temperature_c is None + assert diag.hvac_power_w is None + # name falls back to space_name when the IDU has one; here settings.name is set + assert diag.name == "IDU One" + + +def test_idu_diagnostics_name_falls_back_to_space() -> None: + idu = _make_idu() + idu.settings.name = "" + diag = IndoorUnitDiagnostics.from_indoor_unit(idu, space_name="Kitchen") + assert diag.name == "Kitchen" + + +# --- SystemDiagnostics aggregate -------------------------------------------- + + +def test_system_diagnostics_aggregate() -> None: + healthy = IndoorUnitDiagnostics.from_indoor_unit(_make_idu("a", conditions=_conditions())) + faulted = IndoorUnitDiagnostics.from_indoor_unit( + _make_idu("b", conditions=_conditions(modbus_communication_error=True)) + ) + diag = SystemDiagnostics( + indoor_units=[healthy, faulted], + outdoor_units=[OutdoorUnitDiagnostics.from_outdoor_unit(_make_odu())], + ) + assert diag.has_faults is True + assert diag.active_faults == [("b", "modbus_communication_error")] + assert diag.outdoor_units[0].raw_sensors_available is False + + +def test_system_diagnostics_no_faults() -> None: + diag = SystemDiagnostics( + indoor_units=[IndoorUnitDiagnostics.from_indoor_unit(_make_idu(conditions=_conditions()))], + outdoor_units=[], + ) + assert diag.has_faults is False + assert diag.active_faults == [] + + +# --- SystemSnapshot.diagnostics() ------------------------------------------- + + +def test_snapshot_diagnostics_resolves_space_name() -> None: + snap = _snapshot( + idus=[_make_idu(conditions=_conditions(defrost_cycle=True))], + odus=[_make_odu()], + ) + diag = snap.diagnostics() + assert len(diag.indoor_units) == 1 + assert diag.indoor_units[0].space_name == "Living Room" + assert diag.active_faults == [("idu-1", "defrost_cycle")] + assert diag.outdoor_units[0].outdoor_unit_id == "odu-1" + + +# --- QuiltClient.get_diagnostics -------------------------------------------- + + +async def test_client_get_diagnostics_delegates() -> None: + client = QuiltClient("user@example.com") + snap = _snapshot(idus=[_make_idu()], odus=[_make_odu()]) + with patch.object(client, "get_snapshot", AsyncMock(return_value=snap)): + diag = await client.get_diagnostics() + assert isinstance(diag, SystemDiagnostics) + assert len(diag.indoor_units) == 1 + + +# --- CLI --------------------------------------------------------------------- + + +class _FakeDiagClient: + def __init__(self, *_args: object, **_kwargs: object) -> None: + pass + + async def __aenter__(self) -> _FakeDiagClient: + return self + + async def __aexit__(self, *_args: object) -> None: + return None + + async def login(self) -> None: + return None + + async def get_snapshot(self) -> SystemSnapshot: + return _snapshot( + idus=[ + _make_idu("a", conditions=_conditions()), + _make_idu("b", conditions=_conditions(outdoor_unit_communication_error=True)), + ], + odus=[_make_odu()], + ) + + +def test_cli_diagnostics_summary_and_faults_only_and_json() -> None: + with ( + patch.object(cli_main, "_resolve", return_value=("user@example.com", "Home")), + patch.object(cli_main, "QuiltClient", _FakeDiagClient), + ): + summary = runner.invoke(cli_main.app, ["diagnostics"]) + faults_only = runner.invoke(cli_main.app, ["diagnostics", "--faults-only"]) + as_json = runner.invoke(cli_main.app, ["diagnostics", "--output", "json"]) + + assert summary.exit_code == 0 + assert "1 active fault(s)" in summary.stdout + assert "outdoor_unit_communication_error" in summary.stdout + assert "cloud-withheld" in summary.stdout + + assert faults_only.exit_code == 0 + assert "outdoor_unit_communication_error" in faults_only.stdout + + assert as_json.exit_code == 0 + assert '"outdoor_unit_communication_error"' in as_json.stdout From 8da68af3522707b046678827dbbcbe08ef79a21c Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Mon, 3 Aug 2026 10:44:30 -0700 Subject: [PATCH 2/3] chore: gitignore uv.lock Not consumed by CI (which resolves from pyproject.toml) and this is a published library, so consumers never use the lockfile. Keeps a dev-only, already-drifting lock out of version control. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 061094d..25705a5 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,8 @@ tokens.json .env .env.* + +# uv lockfile — not tracked: this is a library (consumers resolve from +# pyproject.toml) and CI does not use the lock. Commit it only if dev/CI +# standardize on `uv sync --locked`. +uv.lock From 42a19ecacd29172faadb944a48287f8d9b2c141e Mon Sep 17 00:00:00 2001 From: Emmanuel Levijarvi Date: Mon, 3 Aug 2026 11:08:31 -0700 Subject: [PATCH 3/3] fix: tolerate unknown ConditionState wire values in states() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit proto3 preserves unknown enum integers, so ConditionState(value) could raise ValueError and break diagnostics()/the CLI if the backend adds a new condition state. states() now falls back to UNSPECIFIED for unknown values, matching the defensive pattern used for OutdoorUnit.hvac_state. (.active was already safe — it compares raw ints.) Addresses Copilot review feedback. --- src/quilt_hp/models/indoor_unit.py | 21 +++++++++++++++++++-- tests/test_diagnostics.py | 11 +++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/quilt_hp/models/indoor_unit.py b/src/quilt_hp/models/indoor_unit.py index 9eb9261..e59fd55 100644 --- a/src/quilt_hp/models/indoor_unit.py +++ b/src/quilt_hp/models/indoor_unit.py @@ -168,6 +168,19 @@ class IndoorUnitCommands: fallback_control_command: FallbackControlCommand +def _safe_condition_state(value: int) -> ConditionState: + """``ConditionState`` for a raw wire value, tolerating unknown integers. + + proto3 preserves unknown enum integers, so a firmware/backend that adds a + new condition state would otherwise make ``ConditionState(value)`` raise. + Mirrors the defensive fallback used elsewhere (e.g. ``OutdoorUnit.hvac_state``). + """ + try: + return ConditionState(value) + except ValueError: + return ConditionState.UNSPECIFIED + + @dataclass(slots=True) class IndoorUnitConditions: """IDU diagnostic conditions (ODU-linked conditions included). @@ -208,8 +221,12 @@ class IndoorUnitConditions: compressor_minimum_run_time: int = 0 def states(self) -> dict[str, ConditionState]: - """Every condition mapped to its :class:`ConditionState`.""" - return {name: ConditionState(getattr(self, name)) for name in self.FIELD_NAMES} + """Every condition mapped to its :class:`ConditionState`. + + Unknown wire values (proto3 preserves them) fall back to + ``UNSPECIFIED`` rather than raising. + """ + return {name: _safe_condition_state(getattr(self, name)) for name in self.FIELD_NAMES} @property def active(self) -> list[str]: diff --git a/tests/test_diagnostics.py b/tests/test_diagnostics.py index 9ac8d73..8629827 100644 --- a/tests/test_diagnostics.py +++ b/tests/test_diagnostics.py @@ -211,6 +211,17 @@ def test_conditions_healthy() -> None: assert conds.active == [] +def test_conditions_tolerates_unknown_wire_value() -> None: + # proto3 preserves unknown enum integers; states() must not raise. + conds = _conditions() + conds.defrost_cycle = 99 # value outside ConditionState + states = conds.states() + assert states["defrost_cycle"] is ConditionState.UNSPECIFIED + # the unknown value is not ACTIVE, so it does not register as a fault + assert conds.active == [] + assert conds.any_active is False + + # --- IndoorUnitDiagnostics ---------------------------------------------------