diff --git a/package-lock.json b/package-lock.json index 9c4cf70e..ad983dde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "devDependencies": { "@seamapi/fake-seam-connect": "1.83.2", "@seamapi/nextlove-sdk-generator": "^1.19.0", - "@seamapi/types": "1.466.1", + "@seamapi/types": "1.471.0", "del": "^7.1.0", "prettier": "^3.2.5" } @@ -475,9 +475,9 @@ } }, "node_modules/@seamapi/types": { - "version": "1.466.1", - "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.466.1.tgz", - "integrity": "sha512-sRDleDijArxo3RMWdxyaYZub+bpCs2FlJBcWhcYNMS3hPdyvlRWkqSuB88uu9H0WqK6mnzzPLNv/AO2EV1RVCw==", + "version": "1.471.0", + "resolved": "https://registry.npmjs.org/@seamapi/types/-/types-1.471.0.tgz", + "integrity": "sha512-6zdP7J8I5e420Xm32+ccq+yCCsOUm6usWVCKL+R7zU8v7+MuXapJON43CIQD3wFLoHciTe/Z0VrUWT8TY0diOw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index d9d8748f..5b617f76 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "devDependencies": { "@seamapi/fake-seam-connect": "1.83.2", "@seamapi/nextlove-sdk-generator": "^1.19.0", - "@seamapi/types": "1.466.1", + "@seamapi/types": "1.471.0", "del": "^7.1.0", "prettier": "^3.2.5" } diff --git a/seam/routes/models.py b/seam/routes/models.py index 82dc9c70..f988fb04 100644 --- a/seam/routes/models.py +++ b/seam/routes/models.py @@ -673,6 +673,7 @@ class Device: can_program_online_access_codes: bool can_remotely_lock: bool can_remotely_unlock: bool + can_run_thermostat_programs: bool can_simulate_connection: bool can_simulate_disconnection: bool can_simulate_removal: bool @@ -707,6 +708,7 @@ def from_dict(d: Dict[str, Any]): ), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), + can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_removal=d.get("can_simulate_removal", None), @@ -738,6 +740,7 @@ class DeviceProvider: can_program_online_access_codes: bool can_remotely_lock: bool can_remotely_unlock: bool + can_run_thermostat_programs: bool can_simulate_connection: bool can_simulate_disconnection: bool can_simulate_removal: bool @@ -762,6 +765,7 @@ def from_dict(d: Dict[str, Any]): ), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), + can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_removal=d.get("can_simulate_removal", None), @@ -1368,6 +1372,7 @@ class UnmanagedDevice: can_program_online_access_codes: bool can_remotely_lock: bool can_remotely_unlock: bool + can_run_thermostat_programs: bool can_simulate_connection: bool can_simulate_disconnection: bool can_simulate_removal: bool @@ -1399,6 +1404,7 @@ def from_dict(d: Dict[str, Any]): ), can_remotely_lock=d.get("can_remotely_lock", None), can_remotely_unlock=d.get("can_remotely_unlock", None), + can_run_thermostat_programs=d.get("can_run_thermostat_programs", None), can_simulate_connection=d.get("can_simulate_connection", None), can_simulate_disconnection=d.get("can_simulate_disconnection", None), can_simulate_removal=d.get("can_simulate_removal", None), @@ -2532,6 +2538,30 @@ def update( raise NotImplementedError() +class AbstractThermostatsDailyPrograms(abc.ABC): + + @abc.abstractmethod + def create( + self, *, device_id: str, name: str, periods: List[Dict[str, Any]] + ) -> ThermostatDailyProgram: + raise NotImplementedError() + + @abc.abstractmethod + def delete(self, *, thermostat_daily_program_id: str) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def update( + self, + *, + name: str, + periods: List[Dict[str, Any]], + thermostat_daily_program_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: + raise NotImplementedError() + + class AbstractThermostatsSchedules(abc.ABC): @abc.abstractmethod @@ -3086,6 +3116,11 @@ def list( class AbstractThermostats(abc.ABC): + @property + @abc.abstractmethod + def daily_programs(self) -> AbstractThermostatsDailyPrograms: + raise NotImplementedError() + @property @abc.abstractmethod def schedules(self) -> AbstractThermostatsSchedules: @@ -3266,6 +3301,22 @@ def update_climate_preset( ) -> None: raise NotImplementedError() + @abc.abstractmethod + def update_weekly_program( + self, + *, + device_id: str, + friday_program_id: Optional[str] = None, + monday_program_id: Optional[str] = None, + saturday_program_id: Optional[str] = None, + sunday_program_id: Optional[str] = None, + thursday_program_id: Optional[str] = None, + tuesday_program_id: Optional[str] = None, + wednesday_program_id: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: + raise NotImplementedError() + class AbstractAcs(abc.ABC): diff --git a/seam/routes/thermostats.py b/seam/routes/thermostats.py index f6b4e0da..97e090f6 100644 --- a/seam/routes/thermostats.py +++ b/seam/routes/thermostats.py @@ -1,6 +1,7 @@ from typing import Optional, Any, List, Dict, Union from ..client import SeamHttpClient from .models import AbstractThermostats, ActionAttempt, Device +from .thermostats_daily_programs import ThermostatsDailyPrograms from .thermostats_schedules import ThermostatsSchedules from .thermostats_simulate import ThermostatsSimulate from ..modules.action_attempts import resolve_action_attempt @@ -10,9 +11,16 @@ class Thermostats(AbstractThermostats): def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): self.client = client self.defaults = defaults + self._daily_programs = ThermostatsDailyPrograms( + client=client, defaults=defaults + ) self._schedules = ThermostatsSchedules(client=client, defaults=defaults) self._simulate = ThermostatsSimulate(client=client, defaults=defaults) + @property + def daily_programs(self) -> ThermostatsDailyPrograms: + return self._daily_programs + @property def schedules(self) -> ThermostatsSchedules: return self._schedules @@ -474,3 +482,49 @@ def update_climate_preset( self.client.post("/thermostats/update_climate_preset", json=json_payload) return None + + def update_weekly_program( + self, + *, + device_id: str, + friday_program_id: Optional[str] = None, + monday_program_id: Optional[str] = None, + saturday_program_id: Optional[str] = None, + sunday_program_id: Optional[str] = None, + thursday_program_id: Optional[str] = None, + tuesday_program_id: Optional[str] = None, + wednesday_program_id: Optional[str] = None, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if friday_program_id is not None: + json_payload["friday_program_id"] = friday_program_id + if monday_program_id is not None: + json_payload["monday_program_id"] = monday_program_id + if saturday_program_id is not None: + json_payload["saturday_program_id"] = saturday_program_id + if sunday_program_id is not None: + json_payload["sunday_program_id"] = sunday_program_id + if thursday_program_id is not None: + json_payload["thursday_program_id"] = thursday_program_id + if tuesday_program_id is not None: + json_payload["tuesday_program_id"] = tuesday_program_id + if wednesday_program_id is not None: + json_payload["wednesday_program_id"] = wednesday_program_id + + res = self.client.post("/thermostats/update_weekly_program", 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, + ) diff --git a/seam/routes/thermostats_daily_programs.py b/seam/routes/thermostats_daily_programs.py new file mode 100644 index 00000000..a0985169 --- /dev/null +++ b/seam/routes/thermostats_daily_programs.py @@ -0,0 +1,72 @@ +from typing import Optional, Any, List, Dict, Union +from ..client import SeamHttpClient +from .models import ( + AbstractThermostatsDailyPrograms, + ThermostatDailyProgram, + ActionAttempt, +) + +from ..modules.action_attempts import resolve_action_attempt + + +class ThermostatsDailyPrograms(AbstractThermostatsDailyPrograms): + def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]): + self.client = client + self.defaults = defaults + + def create( + self, *, device_id: str, name: str, periods: List[Dict[str, Any]] + ) -> ThermostatDailyProgram: + json_payload = {} + + if device_id is not None: + json_payload["device_id"] = device_id + if name is not None: + json_payload["name"] = name + if periods is not None: + json_payload["periods"] = periods + + res = self.client.post("/thermostats/daily_programs/create", json=json_payload) + + return ThermostatDailyProgram.from_dict(res["thermostat_daily_program"]) + + def delete(self, *, thermostat_daily_program_id: str) -> None: + json_payload = {} + + if thermostat_daily_program_id is not None: + json_payload["thermostat_daily_program_id"] = thermostat_daily_program_id + + self.client.post("/thermostats/daily_programs/delete", json=json_payload) + + return None + + def update( + self, + *, + name: str, + periods: List[Dict[str, Any]], + thermostat_daily_program_id: str, + wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None + ) -> ActionAttempt: + json_payload = {} + + if name is not None: + json_payload["name"] = name + if periods is not None: + json_payload["periods"] = periods + if thermostat_daily_program_id is not None: + json_payload["thermostat_daily_program_id"] = thermostat_daily_program_id + + res = self.client.post("/thermostats/daily_programs/update", 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, + )