diff --git a/package-lock.json b/package-lock.json index ebd2c9f..5a16639 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "devDependencies": { "@seamapi/fake-seam-connect": "1.72.0", "@seamapi/nextlove-sdk-generator": "1.14.2", - "@seamapi/types": "1.238.0", + "@seamapi/types": "1.242.0", "del": "^7.1.0", "prettier": "^3.2.5" } @@ -473,9 +473,9 @@ } }, "node_modules/@seamapi/types": { - "version": "1.238.0", - "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.238.0.tgz", - "integrity": "sha512-irH4R4KFN87fmfFeEHT3dTfs+M0KSUvW0xGZrcgVOTh7hmUNcv8kYJyvINYqquKmJYMJYkX3mBC7bU3JBUWrVg==", + "version": "1.242.0", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.242.0.tgz", + "integrity": "sha512-s3r5T9ttfEgdhKgY4RgsA2FOc25q4hxYet82WqKfB7TsGDwrl1V5LUKf9xQOHbWQuTfigitZOOzqUbg1PaiNbA==", "dev": true, "engines": { "node": ">=18.12.0", diff --git a/package.json b/package.json index 0d9bf52..1d93a3b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "devDependencies": { "@seamapi/fake-seam-connect": "1.72.0", "@seamapi/nextlove-sdk-generator": "1.14.2", - "@seamapi/types": "1.238.0", + "@seamapi/types": "1.242.0", "del": "^7.1.0", "prettier": "^3.2.5" } diff --git a/seam/routes/models.py b/seam/routes/models.py index 665935a..36c9bcc 100644 --- a/seam/routes/models.py +++ b/seam/routes/models.py @@ -279,7 +279,7 @@ class AcsUser: display_name: str email: str email_address: str - errors: Any + errors: List[Dict[str, Any]] external_type: str external_type_display_name: str full_name: str @@ -375,43 +375,6 @@ def from_dict(d: Dict[str, Any]): ) -@dataclass -class ClimateSettingSchedule: - climate_setting_schedule_id: str - cooling_set_point_celsius: float - cooling_set_point_fahrenheit: float - created_at: str - device_id: str - errors: Any - heating_set_point_celsius: float - heating_set_point_fahrenheit: float - hvac_mode_setting: str - manual_override_allowed: bool - name: str - schedule_ends_at: str - schedule_starts_at: str - schedule_type: str - - @staticmethod - def from_dict(d: Dict[str, Any]): - return ClimateSettingSchedule( - climate_setting_schedule_id=d.get("climate_setting_schedule_id", None), - cooling_set_point_celsius=d.get("cooling_set_point_celsius", None), - cooling_set_point_fahrenheit=d.get("cooling_set_point_fahrenheit", None), - created_at=d.get("created_at", None), - device_id=d.get("device_id", None), - errors=d.get("errors", None), - heating_set_point_celsius=d.get("heating_set_point_celsius", None), - heating_set_point_fahrenheit=d.get("heating_set_point_fahrenheit", None), - hvac_mode_setting=d.get("hvac_mode_setting", None), - manual_override_allowed=d.get("manual_override_allowed", None), - name=d.get("name", None), - schedule_ends_at=d.get("schedule_ends_at", None), - schedule_starts_at=d.get("schedule_starts_at", None), - schedule_type=d.get("schedule_type", None), - ) - - @dataclass class ConnectWebview: accepted_devices: List[str] @@ -768,6 +731,33 @@ def from_dict(d: Dict[str, Any]): ) +@dataclass +class ThermostatSchedule: + climate_preset_key: str + created_at: str + device_id: str + ends_at: str + errors: Any + max_override_period_minutes: int + name: str + starts_at: str + thermostat_schedule_id: str + + @staticmethod + def from_dict(d: Dict[str, Any]): + return ThermostatSchedule( + climate_preset_key=d.get("climate_preset_key", None), + created_at=d.get("created_at", None), + device_id=d.get("device_id", None), + ends_at=d.get("ends_at", None), + errors=d.get("errors", None), + max_override_period_minutes=d.get("max_override_period_minutes", None), + name=d.get("name", None), + starts_at=d.get("starts_at", None), + thermostat_schedule_id=d.get("thermostat_schedule_id", None), + ) + + @dataclass class UnmanagedAccessCode: access_code_id: str @@ -1625,60 +1615,45 @@ def create_sandbox_phone( raise NotImplementedError() -class AbstractThermostatsClimateSettingSchedules(abc.ABC): +class AbstractThermostatsSchedules(abc.ABC): @abc.abstractmethod def create( self, *, + climate_preset_key: str, device_id: str, - schedule_ends_at: str, - schedule_starts_at: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, - name: Optional[str] = None, - schedule_type: Optional[str] = None - ) -> ClimateSettingSchedule: + ends_at: str, + starts_at: str, + max_override_period_minutes: Optional[int] = None, + name: Optional[str] = None + ) -> ThermostatSchedule: raise NotImplementedError() @abc.abstractmethod - def delete(self, *, climate_setting_schedule_id: str) -> None: + def delete(self, *, thermostat_schedule_id: str) -> None: raise NotImplementedError() @abc.abstractmethod - def get( - self, - *, - climate_setting_schedule_id: Optional[str] = None, - device_id: Optional[str] = None - ) -> ClimateSettingSchedule: + def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: raise NotImplementedError() @abc.abstractmethod def list( self, *, device_id: str, user_identifier_key: Optional[str] = None - ) -> List[ClimateSettingSchedule]: + ) -> List[ThermostatSchedule]: raise NotImplementedError() @abc.abstractmethod def update( self, *, - climate_setting_schedule_id: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, + thermostat_schedule_id: str, + climate_preset_key: Optional[str] = None, + ends_at: Optional[str] = None, + max_override_period_minutes: Optional[int] = None, name: Optional[str] = None, - schedule_ends_at: Optional[str] = None, - schedule_starts_at: Optional[str] = None, - schedule_type: Optional[str] = None + starts_at: Optional[str] = None ) -> None: raise NotImplementedError() @@ -1794,7 +1769,17 @@ class AbstractThermostats(abc.ABC): @property @abc.abstractmethod - def climate_setting_schedules(self) -> AbstractThermostatsClimateSettingSchedules: + def schedules(self) -> AbstractThermostatsSchedules: + raise NotImplementedError() + + @abc.abstractmethod + def activate_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: raise NotImplementedError() @abc.abstractmethod @@ -1809,6 +1794,27 @@ def cool( ) -> ActionAttempt: raise NotImplementedError() + @abc.abstractmethod + def create_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + manual_override_allowed: bool, + name: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None + ) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: + raise NotImplementedError() + @abc.abstractmethod def get( self, *, device_id: Optional[str] = None, name: Optional[str] = None @@ -1871,6 +1877,12 @@ def off( ) -> ActionAttempt: raise NotImplementedError() + @abc.abstractmethod + def set_fallback_climate_preset( + self, *, climate_preset_key: str, device_id: str + ) -> None: + raise NotImplementedError() + @abc.abstractmethod def set_fan_mode( self, @@ -1884,8 +1896,19 @@ def set_fan_mode( raise NotImplementedError() @abc.abstractmethod - def update( - self, *, default_climate_setting: Dict[str, Any], device_id: str + def update_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + manual_override_allowed: bool, + name: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None ) -> None: raise NotImplementedError() diff --git a/seam/routes/thermostats.py b/seam/routes/thermostats.py index ca1afc7..9e57ef8 100644 --- a/seam/routes/thermostats.py +++ b/seam/routes/thermostats.py @@ -1,7 +1,7 @@ from typing import Optional, Any, List, Dict, Union from ..client import SeamHttpClient from .models import AbstractThermostats, ActionAttempt, Device -from .thermostats_climate_setting_schedules import ThermostatsClimateSettingSchedules +from .thermostats_schedules import ThermostatsSchedules from ..modules.action_attempts import resolve_action_attempt @@ -9,13 +9,41 @@ class Thermostats(AbstractThermostats): def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults - self._climate_setting_schedules = ThermostatsClimateSettingSchedules( - client=client, defaults=defaults - ) + self._schedules = ThermostatsSchedules(client=client, defaults=defaults) @property - def climate_setting_schedules(self) -> ThermostatsClimateSettingSchedules: - return self._climate_setting_schedules + def schedules(self) -> ThermostatsSchedules: + return self._schedules + + def activate_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: + json_payload = {} + + if climate_preset_key is not None: + json_payload["climate_preset_key"] = climate_preset_key + if device_id is not None: + json_payload["device_id"] = device_id + + res = self.client.post( + "/thermostats/activate_climate_preset", json=json_payload + ) + + wait_for_action_attempt = ( + self.defaults.get("wait_for_action_attempt") + if wait_for_action_attempt is None + else wait_for_action_attempt + ) + + return resolve_action_attempt( + client=self.client, + action_attempt=ActionAttempt.from_dict(res["action_attempt"]), + wait_for_action_attempt=wait_for_action_attempt, + ) def cool( self, @@ -51,6 +79,59 @@ def cool( wait_for_action_attempt=wait_for_action_attempt, ) + def create_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + manual_override_allowed: bool, + name: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None + ) -> None: + json_payload = {} + + if climate_preset_key is not None: + json_payload["climate_preset_key"] = climate_preset_key + if device_id is not None: + json_payload["device_id"] = device_id + if manual_override_allowed is not None: + json_payload["manual_override_allowed"] = manual_override_allowed + if name is not None: + json_payload["name"] = name + if cooling_set_point_celsius is not None: + json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius + if cooling_set_point_fahrenheit is not None: + json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit + if fan_mode_setting is not None: + json_payload["fan_mode_setting"] = fan_mode_setting + if heating_set_point_celsius is not None: + json_payload["heating_set_point_celsius"] = heating_set_point_celsius + if heating_set_point_fahrenheit is not None: + json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit + if hvac_mode_setting is not None: + json_payload["hvac_mode_setting"] = hvac_mode_setting + + self.client.post("/thermostats/create_climate_preset", json=json_payload) + + return None + + def delete_climate_preset(self, *, climate_preset_key: str, device_id: str) -> None: + json_payload = {} + + if climate_preset_key is not None: + json_payload["climate_preset_key"] = climate_preset_key + if device_id is not None: + json_payload["device_id"] = device_id + + self.client.post("/thermostats/delete_climate_preset", json=json_payload) + + return None + def get( self, *, device_id: Optional[str] = None, name: Optional[str] = None ) -> Device: @@ -217,6 +298,20 @@ def off( wait_for_action_attempt=wait_for_action_attempt, ) + def set_fallback_climate_preset( + self, *, climate_preset_key: str, device_id: str + ) -> None: + json_payload = {} + + if climate_preset_key is not None: + json_payload["climate_preset_key"] = climate_preset_key + if device_id is not None: + json_payload["device_id"] = device_id + + self.client.post("/thermostats/set_fallback_climate_preset", json=json_payload) + + return None + def set_fan_mode( self, *, @@ -251,16 +346,43 @@ def set_fan_mode( wait_for_action_attempt=wait_for_action_attempt, ) - def update( - self, *, default_climate_setting: Dict[str, Any], device_id: str + def update_climate_preset( + self, + *, + climate_preset_key: str, + device_id: str, + manual_override_allowed: bool, + name: str, + cooling_set_point_celsius: Optional[float] = None, + cooling_set_point_fahrenheit: Optional[float] = None, + fan_mode_setting: Optional[str] = None, + heating_set_point_celsius: Optional[float] = None, + heating_set_point_fahrenheit: Optional[float] = None, + hvac_mode_setting: Optional[str] = None ) -> None: json_payload = {} - if default_climate_setting is not None: - json_payload["default_climate_setting"] = default_climate_setting + if climate_preset_key is not None: + json_payload["climate_preset_key"] = climate_preset_key if device_id is not None: json_payload["device_id"] = device_id + if manual_override_allowed is not None: + json_payload["manual_override_allowed"] = manual_override_allowed + if name is not None: + json_payload["name"] = name + if cooling_set_point_celsius is not None: + json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius + if cooling_set_point_fahrenheit is not None: + json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit + if fan_mode_setting is not None: + json_payload["fan_mode_setting"] = fan_mode_setting + if heating_set_point_celsius is not None: + json_payload["heating_set_point_celsius"] = heating_set_point_celsius + if heating_set_point_fahrenheit is not None: + json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit + if hvac_mode_setting is not None: + json_payload["hvac_mode_setting"] = hvac_mode_setting - self.client.post("/thermostats/update", json=json_payload) + self.client.post("/thermostats/update_climate_preset", json=json_payload) return None diff --git a/seam/routes/thermostats_climate_setting_schedules.py b/seam/routes/thermostats_climate_setting_schedules.py deleted file mode 100644 index 4fc7b0b..0000000 --- a/seam/routes/thermostats_climate_setting_schedules.py +++ /dev/null @@ -1,151 +0,0 @@ -from typing import Optional, Any, List, Dict, Union -from ..client import SeamHttpClient -from .models import AbstractThermostatsClimateSettingSchedules, ClimateSettingSchedule - - -class ThermostatsClimateSettingSchedules(AbstractThermostatsClimateSettingSchedules): - def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): - self.client = client - self.defaults = defaults - - def create( - self, - *, - device_id: str, - schedule_ends_at: str, - schedule_starts_at: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, - name: Optional[str] = None, - schedule_type: Optional[str] = None - ) -> ClimateSettingSchedule: - json_payload = {} - - if device_id is not None: - json_payload["device_id"] = device_id - if schedule_ends_at is not None: - json_payload["schedule_ends_at"] = schedule_ends_at - if schedule_starts_at is not None: - json_payload["schedule_starts_at"] = schedule_starts_at - if cooling_set_point_celsius is not None: - json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius - if cooling_set_point_fahrenheit is not None: - json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit - if heating_set_point_celsius is not None: - json_payload["heating_set_point_celsius"] = heating_set_point_celsius - if heating_set_point_fahrenheit is not None: - json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit - if hvac_mode_setting is not None: - json_payload["hvac_mode_setting"] = hvac_mode_setting - if manual_override_allowed is not None: - json_payload["manual_override_allowed"] = manual_override_allowed - if name is not None: - json_payload["name"] = name - if schedule_type is not None: - json_payload["schedule_type"] = schedule_type - - res = self.client.post( - "/thermostats/climate_setting_schedules/create", json=json_payload - ) - - return ClimateSettingSchedule.from_dict(res["climate_setting_schedule"]) - - def delete(self, *, climate_setting_schedule_id: str) -> None: - json_payload = {} - - if climate_setting_schedule_id is not None: - json_payload["climate_setting_schedule_id"] = climate_setting_schedule_id - - self.client.post( - "/thermostats/climate_setting_schedules/delete", json=json_payload - ) - - return None - - def get( - self, - *, - climate_setting_schedule_id: Optional[str] = None, - device_id: Optional[str] = None - ) -> ClimateSettingSchedule: - json_payload = {} - - if climate_setting_schedule_id is not None: - json_payload["climate_setting_schedule_id"] = climate_setting_schedule_id - if device_id is not None: - json_payload["device_id"] = device_id - - res = self.client.post( - "/thermostats/climate_setting_schedules/get", json=json_payload - ) - - return ClimateSettingSchedule.from_dict(res["climate_setting_schedule"]) - - def list( - self, *, device_id: str, user_identifier_key: Optional[str] = None - ) -> List[ClimateSettingSchedule]: - json_payload = {} - - if device_id is not None: - json_payload["device_id"] = device_id - if user_identifier_key is not None: - json_payload["user_identifier_key"] = user_identifier_key - - res = self.client.post( - "/thermostats/climate_setting_schedules/list", json=json_payload - ) - - return [ - ClimateSettingSchedule.from_dict(item) - for item in res["climate_setting_schedules"] - ] - - def update( - self, - *, - climate_setting_schedule_id: str, - cooling_set_point_celsius: Optional[float] = None, - cooling_set_point_fahrenheit: Optional[float] = None, - heating_set_point_celsius: Optional[float] = None, - heating_set_point_fahrenheit: Optional[float] = None, - hvac_mode_setting: Optional[str] = None, - manual_override_allowed: Optional[bool] = None, - name: Optional[str] = None, - schedule_ends_at: Optional[str] = None, - schedule_starts_at: Optional[str] = None, - schedule_type: Optional[str] = None - ) -> None: - json_payload = {} - - if climate_setting_schedule_id is not None: - json_payload["climate_setting_schedule_id"] = climate_setting_schedule_id - if cooling_set_point_celsius is not None: - json_payload["cooling_set_point_celsius"] = cooling_set_point_celsius - if cooling_set_point_fahrenheit is not None: - json_payload["cooling_set_point_fahrenheit"] = cooling_set_point_fahrenheit - if heating_set_point_celsius is not None: - json_payload["heating_set_point_celsius"] = heating_set_point_celsius - if heating_set_point_fahrenheit is not None: - json_payload["heating_set_point_fahrenheit"] = heating_set_point_fahrenheit - if hvac_mode_setting is not None: - json_payload["hvac_mode_setting"] = hvac_mode_setting - if manual_override_allowed is not None: - json_payload["manual_override_allowed"] = manual_override_allowed - if name is not None: - json_payload["name"] = name - if schedule_ends_at is not None: - json_payload["schedule_ends_at"] = schedule_ends_at - if schedule_starts_at is not None: - json_payload["schedule_starts_at"] = schedule_starts_at - if schedule_type is not None: - json_payload["schedule_type"] = schedule_type - - self.client.post( - "/thermostats/climate_setting_schedules/update", json=json_payload - ) - - return None diff --git a/seam/routes/thermostats_schedules.py b/seam/routes/thermostats_schedules.py new file mode 100644 index 0000000..091e97a --- /dev/null +++ b/seam/routes/thermostats_schedules.py @@ -0,0 +1,103 @@ +from typing import Optional, Any, List, Dict, Union +from ..client import SeamHttpClient +from .models import AbstractThermostatsSchedules, ThermostatSchedule + + +class ThermostatsSchedules(AbstractThermostatsSchedules): + def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): + self.client = client + self.defaults = defaults + + def create( + self, + *, + climate_preset_key: str, + device_id: str, + ends_at: str, + starts_at: str, + max_override_period_minutes: Optional[int] = None, + name: Optional[str] = None + ) -> ThermostatSchedule: + json_payload = {} + + if climate_preset_key is not None: + json_payload["climate_preset_key"] = climate_preset_key + if device_id is not None: + json_payload["device_id"] = device_id + if ends_at is not None: + json_payload["ends_at"] = ends_at + if starts_at is not None: + json_payload["starts_at"] = starts_at + if max_override_period_minutes is not None: + json_payload["max_override_period_minutes"] = max_override_period_minutes + if name is not None: + json_payload["name"] = name + + res = self.client.post("/thermostats/schedules/create", json=json_payload) + + return ThermostatSchedule.from_dict(res["thermostat_schedule"]) + + def delete(self, *, thermostat_schedule_id: str) -> None: + json_payload = {} + + if thermostat_schedule_id is not None: + json_payload["thermostat_schedule_id"] = thermostat_schedule_id + + self.client.post("/thermostats/schedules/delete", json=json_payload) + + return None + + def get(self, *, thermostat_schedule_id: str) -> ThermostatSchedule: + json_payload = {} + + if thermostat_schedule_id is not None: + json_payload["thermostat_schedule_id"] = thermostat_schedule_id + + res = self.client.post("/thermostats/schedules/get", json=json_payload) + + return ThermostatSchedule.from_dict(res["thermostat_schedule"]) + + def list( + self, *, device_id: str, user_identifier_key: Optional[str] = None + ) -> List[ThermostatSchedule]: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if user_identifier_key is not None: + json_payload["user_identifier_key"] = user_identifier_key + + res = self.client.post("/thermostats/schedules/list", json=json_payload) + + return [ + ThermostatSchedule.from_dict(item) for item in res["thermostat_schedules"] + ] + + def update( + self, + *, + thermostat_schedule_id: str, + climate_preset_key: Optional[str] = None, + ends_at: Optional[str] = None, + max_override_period_minutes: Optional[int] = None, + name: Optional[str] = None, + starts_at: Optional[str] = None + ) -> None: + json_payload = {} + + if thermostat_schedule_id is not None: + json_payload["thermostat_schedule_id"] = thermostat_schedule_id + if climate_preset_key is not None: + json_payload["climate_preset_key"] = climate_preset_key + if ends_at is not None: + json_payload["ends_at"] = ends_at + if max_override_period_minutes is not None: + json_payload["max_override_period_minutes"] = max_override_period_minutes + if name is not None: + json_payload["name"] = name + if starts_at is not None: + json_payload["starts_at"] = starts_at + + self.client.post("/thermostats/schedules/update", json=json_payload) + + return None