diff --git a/kasa/cli.py b/kasa/cli.py index d2ac9589d..9d878b89c 100755 --- a/kasa/cli.py +++ b/kasa/cli.py @@ -569,6 +569,26 @@ async def time(dev): return res +@cli.command() +@click.option("--list", is_flag=True) +@click.argument("index", type=int, required=False) +@pass_dev +async def set_timezone(dev, list, index): + """Set the device timezone. + + pass --list to see valid timezones + """ + time = dev.modules["time"] + if list: + for timezone in time.get_timezones(): + echo( + f"Index: {timezone['index']:3}\tName: {timezone['zone_str']:65}\tRule: {timezone['tz_str']}" + ) + return + if index: + return await time.set_timezone(index) + + @cli.command() @click.option("--index", type=int, required=False) @click.option("--name", type=str, required=False) @@ -630,18 +650,136 @@ async def schedule(dev): @schedule.command(name="list") @pass_dev +@click.option("--json", is_flag=True) @click.argument("type", default="schedule") -def _schedule_list(dev, type): +def _schedule_list(dev, json, type): """Return the list of schedule actions for the given type.""" sched = dev.modules[type] - for rule in sched.rules: - print(rule) - else: + if sched.rules == []: echo(f"No rules of type {type}") + for rule in sched.rules: + if json: + print(rule.json()) + else: + print(rule) return sched.rules +@schedule.command(name="enable") +@pass_dev +@click.argument("enable", type=click.BOOL) +async def _schedule_enable(dev, enable): + """Enable or disable schedule.""" + schedule = dev.modules["schedule"] + return await schedule.set_enabled(1 if state else 0) + + +@schedule.command(name="add") +@pass_dev +@click.option("--name", type=str, required=True) +@click.option("--enable", type=click.BOOL, default=True, show_default=True) +@click.option("--repeat", type=click.BOOL, default=True, show_default=True) +@click.option("--days", type=str, required=True) +@click.option("--start-action", type=click.IntRange(-1, 2), default=None, required=True) +@click.option("--start-sun", type=click.IntRange(-1, 2), default=None, required=True) +@click.option( + "--start-minutes", type=click.IntRange(0, 1440), default=None, required=True +) +@click.option("--end-action", type=click.IntRange(-1, 2), default=-1) +@click.option("--end-sun", type=click.IntRange(-1, 2), default=-1) +@click.option("--end-minutes", type=click.IntRange(0, 1440), default=None) +async def add_rule( + dev, + name, + enable, + repeat, + days, + start_action, + start_sun, + start_minutes, + end_action, + end_sun, + end_minutes, +): + """Add rule to device.""" + schedule = dev.modules["schedule"] + rule_to_add = schedule.Rule( + name=name, + enable=enable, + repeat=repeat, + days=list(map(int, days.split(","))), + start_action=start_action, + start_sun=start_sun, + start_minutes=start_minutes, + end_action=end_action, + end_sun=end_sun, + end_minutes=end_minutes, + ) + if rule_to_add: + echo("Adding rule") + return await schedule.add_rule(rule_to_add) + else: + echo("Invalid rule") + + +@schedule.command(name="edit") +@pass_dev +@click.option("--id", type=str, required=True) +@click.option("--name", type=str) +@click.option("--enable", type=click.BOOL) +@click.option("--repeat", type=click.BOOL) +@click.option("--days", type=str) +@click.option("--start-action", type=click.IntRange(-1, 2)) +@click.option("--start-sun", type=click.IntRange(-1, 2)) +@click.option("--start-minutes", type=click.IntRange(0, 1440)) +@click.option("--end-action", type=click.IntRange(-1, 2)) +@click.option("--end-sun", type=click.IntRange(-1, 2)) +@click.option("--end-minutes", type=click.IntRange(0, 1440)) +async def edit_rule( + dev, + id, + name, + enable, + repeat, + days, + start_action, + start_sun, + start_minutes, + end_action, + end_sun, + end_minutes, +): + """Edit rule from device.""" + schedule = dev.modules["schedule"] + rule_to_edit = next(filter(lambda rule: (rule.id == id), schedule.rules), None) + if rule_to_edit: + echo(f"Editing rule id {id}") + if name is not None: + rule_to_edit.name = name + if enable is not None: + rule_to_edit.enable = 1 if enable else 0 + if repeat is not None: + rule_to_edit.repeat = 1 if repeat else 0 + if days is not None: + rule_to_edit.wday = list(map(int, days.split(","))) + if start_action is not None: + rule_to_edit.sact = start_action + if start_sun is not None: + rule_to_edit.stime_opt = start_sun + if start_minutes is not None: + rule_to_edit.smin = start_minutes + if end_action is not None: + rule_to_edit.eact = end_action + if end_sun is not None: + rule_to_edit.etime_opt = end_sun + if end_minutes is not None: + rule_to_edit.emin = end_minutes + return await schedule.edit_rule(rule_to_edit) + else: + echo(f"No rule with id {id} was found") + + @schedule.command(name="delete") @pass_dev @click.option("--id", type=str, required=True) @@ -656,6 +794,17 @@ async def delete_rule(dev, id): echo(f"No rule with id {id} was found") +@schedule.command() +@pass_dev +@click.option("--prompt", type=click.BOOL, prompt=True, help="Are you sure?") +async def delete_all(dev, prompt): + """Delete all rules from device.""" + schedule = dev.modules["schedule"] + if prompt: + echo("Deleting all rules") + return await schedule.delete_all_rules() + + @cli.group(invoke_without_command=True) @click.pass_context async def presets(ctx): diff --git a/kasa/modules/__init__.py b/kasa/modules/__init__.py index 8ad5088d5..eedcc0dfa 100644 --- a/kasa/modules/__init__.py +++ b/kasa/modules/__init__.py @@ -6,8 +6,14 @@ from .emeter import Emeter from .module import Module from .motion import Motion -from .rulemodule import Rule, RuleModule -from .schedule import Schedule +from .rulemodule import ( + AntitheftRule, + BulbScheduleRule, + CountdownRule, + RuleModule, + ScheduleRule, +) +from .schedule import BulbSchedule, Schedule from .time import Time from .usage import Usage @@ -19,9 +25,13 @@ "Emeter", "Module", "Motion", - "Rule", + "AntitheftRule", + "CountdownRule", + "ScheduleRule", + "BulbScheduleRule", "RuleModule", "Schedule", + "BulbSchedule", "Time", "Usage", ] diff --git a/kasa/modules/antitheft.py b/kasa/modules/antitheft.py index c885a70c2..0dae2e19b 100644 --- a/kasa/modules/antitheft.py +++ b/kasa/modules/antitheft.py @@ -1,5 +1,5 @@ """Implementation of the antitheft module.""" -from .rulemodule import RuleModule +from .rulemodule import AntitheftRule, RuleModule class Antitheft(RuleModule): @@ -7,3 +7,5 @@ class Antitheft(RuleModule): This shares the functionality among other rule-based modules. """ + + Rule = AntitheftRule diff --git a/kasa/modules/countdown.py b/kasa/modules/countdown.py index 9f3e59c16..eacc0702b 100644 --- a/kasa/modules/countdown.py +++ b/kasa/modules/countdown.py @@ -1,6 +1,8 @@ """Implementation for the countdown timer.""" -from .rulemodule import RuleModule +from .rulemodule import CountdownRule, RuleModule class Countdown(RuleModule): """Implementation of countdown module.""" + + Rule = CountdownRule diff --git a/kasa/modules/rulemodule.py b/kasa/modules/rulemodule.py index e73b2d03e..232f5bc74 100644 --- a/kasa/modules/rulemodule.py +++ b/kasa/modules/rulemodule.py @@ -1,13 +1,21 @@ """Base implementation for all rule-based modules.""" +import json import logging from enum import Enum -from typing import Dict, List, Optional +from typing import List, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Field from .module import Module, merge +class EnabledOption(Enum): + """Integer enabled option.""" + + TurnOff = 0 + Enabled = 1 + + class Action(Enum): """Action to perform.""" @@ -26,33 +34,83 @@ class TimeOption(Enum): AtSunset = 2 -class Rule(BaseModel): +class BulbModeOption(Enum): + """Bulb mode.""" + + Customize = "customize_preset" + Last = "last_status" + + +class BaseRule(BaseModel): """Representation of a rule.""" - id: str + # not used when adding a rule + id: Optional[str] name: str - enable: bool - wday: List[int] - repeat: bool + enable: EnabledOption + + class Config: + """Rule Config.""" + + validate_assignment = True + allow_population_by_field_name = True + + +class CountdownRule(BaseRule): + """Representation of a countdown rule.""" + + delay: int + act: EnabledOption = Field(alias="action") + + +class ScheduleRule(BaseRule): + """Representation of a schedule rule.""" + + wday: List[int] = Field(alias="days") + repeat: EnabledOption # start action - sact: Optional[Action] - stime_opt: TimeOption - smin: int + sact: Optional[Action] = Field(alias="start_action") + stime_opt: TimeOption = Field(alias="start_sun") + smin: int = Field(alias="start_minutes", ge=0, le=1440) + + # end action + eact: Optional[Action] = Field(alias="end_action") + # Required to submit, but the device will not return it if set to -1 + etime_opt: TimeOption = Field(default=TimeOption.Disabled, alias="end_sun") + emin: Optional[int] = Field(alias="end_minutes", ge=0, le=1440) + + +class BulbRule(BaseRule): + """Representation of a bulb schedule rule.""" + + saturation: int = Field(ge=0, le=100) + hue: int = Field(ge=0, le=360) + brightness: int = Field(ge=0, le=100) + color_temp: int = Field(ge=2500, le=9000) + mode: BulbModeOption + on_off: EnabledOption + - eact: Optional[Action] - etime_opt: TimeOption - emin: int +class BulbScheduleRule(BaseRule): + """Representation of a bulb schedule rule.""" - # Only on bulbs - s_light: Optional[Dict] + s_light: BulbRule = Field(alias="lights") + + +class AntitheftRule(BaseRule): + """Representation of a antitheft rule.""" + + frequency: int = Field(ge=1, le=10) _LOGGER = logging.getLogger(__name__) class RuleModule(Module): - """Base class for rule-based modules, such as countdown and antitheft.""" + """Base class for rule-based modules, such as antitheft, countdown and schedule.""" + + Rule = BaseRule def query(self): """Prepare the query for rules.""" @@ -60,21 +118,30 @@ def query(self): return merge(q, self.query_for_command("get_next_action")) @property - def rules(self) -> List[Rule]: + def rules(self) -> List[BaseRule]: """Return the list of rules for the service.""" try: return [ - Rule.parse_obj(rule) for rule in self.data["get_rules"]["rule_list"] + self.Rule.parse_obj(rule) + for rule in self.data["get_rules"]["rule_list"] ] except Exception as ex: _LOGGER.error("Unable to read rule list: %s (data: %s)", ex, self.data) return [] - async def set_enabled(self, state: bool): + async def set_enabled(self, state: int): """Enable or disable the service.""" - return await self.call("set_overall_enable", state) + return await self.call("set_overall_enable", {"enable": state}) + + async def add_rule(self, rule: BaseRule): + """Add a new rule.""" + return await self.call("add_rule", json.loads(rule.json(exclude_none=True))) + + async def edit_rule(self, rule: BaseRule): + """Edit the given rule.""" + return await self.call("edit_rule", json.loads(rule.json(exclude_none=True))) - async def delete_rule(self, rule: Rule): + async def delete_rule(self, rule: BaseRule): """Delete the given rule.""" return await self.call("delete_rule", {"id": rule.id}) diff --git a/kasa/modules/schedule.py b/kasa/modules/schedule.py index 62371692b..b67e86cfe 100644 --- a/kasa/modules/schedule.py +++ b/kasa/modules/schedule.py @@ -1,6 +1,14 @@ """Schedule module implementation.""" -from .rulemodule import RuleModule +from .rulemodule import BulbScheduleRule, RuleModule, ScheduleRule class Schedule(RuleModule): """Implements the scheduling interface.""" + + Rule = ScheduleRule + + +class BulbSchedule(RuleModule): + """Implements the scheduling interface.""" + + Rule = BulbScheduleRule diff --git a/kasa/modules/time.py b/kasa/modules/time.py index d72e2d600..faf72e5b6 100644 --- a/kasa/modules/time.py +++ b/kasa/modules/time.py @@ -4,6 +4,669 @@ from ..exceptions import SmartDeviceException from .module import Module, merge +timezones = [ + { + "index": 0, + "zone_str": "(UTC-12:00) International Date Line West", + "tz_str": "12", + "dst_offset": 0, + }, + { + "index": 1, + "zone_str": "(UTC-11:00) Coordinated Universal Time-11", + "tz_str": "11", + "dst_offset": 0, + }, + { + "index": 2, + "zone_str": "(UTC-10:00) Hawaii", + "tz_str": "HST10", + "dst_offset": 0, + }, + { + "index": 3, + "zone_str": "(UTC-09:00) Alaska", + "tz_str": "AKST9AKDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 4, + "zone_str": "(UTC-08:00) Baja California", + "tz_str": "PST8PDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 5, + "zone_str": "(UTC-08:00) Pacific Standard Time (US & Canada)", + "tz_str": "PST8", + "dst_offset": 0, + }, + { + "index": 6, + "zone_str": "(UTC-08:00) Pacific Daylight Time (US & Canada)", + "tz_str": "PST8PDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 7, + "zone_str": "(UTC-07:00) Arizona", + "tz_str": "MST7", + "dst_offset": 0, + }, + { + "index": 8, + "zone_str": "(UTC-07:00) Chihuahua, La Paz, Mazatlan", + "tz_str": "MST7MDT,M4.1.0,M10.5.0", + "dst_offset": 60, + }, + { + "index": 9, + "zone_str": "(UTC-07:00) Mountain Standard Time (US & Canada)", + "tz_str": "MST7", + "dst_offset": 0, + }, + { + "index": 10, + "zone_str": "(UTC-07:00) Mountain Daylight Time (US & Canada)", + "tz_str": "MST7MDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 11, + "zone_str": "(UTC-06:00) Central America", + "tz_str": "CST6", + "dst_offset": 0, + }, + { + "index": 12, + "zone_str": "(UTC-06:00) Central Standard Time (US & Canada)", + "tz_str": "CST6", + "dst_offset": 0, + }, + { + "index": 13, + "zone_str": "(UTC-06:00) Central Daylight Time (US & Canada)", + "tz_str": "CST6CDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 14, + "zone_str": "(UTC-06:00) Guadalajara, Mexico City, Monterrey", + "tz_str": "CST6CDT,M4.1.0,M10.5.0", + "dst_offset": 60, + }, + { + "index": 15, + "zone_str": "(UTC-06:00) Saskatchewan", + "tz_str": "6", + "dst_offset": 0, + }, + { + "index": 16, + "zone_str": "(UTC-05:00) Bogota, Lima, Quito, Rio Branco", + "tz_str": "COT5", + "dst_offset": 0, + }, + { + "index": 17, + "zone_str": "(UTC-05:00) Eastern Standard Time (US & Canada)", + "tz_str": "EST5", + "dst_offset": 0, + }, + { + "index": 18, + "zone_str": "(UTC-05:00) Eastern Daylight Time (US & Canada)", + "tz_str": "EST5EDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 19, + "zone_str": "(UTC-05:00) Indiana (East)", + "tz_str": "EST5EDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 20, + "zone_str": "(UTC-04:30) Caracas", + "tz_str": "VET4:30", + "dst_offset": 0, + }, + { + "index": 21, + "zone_str": "(UTC-04:00) Asuncion", + "tz_str": "PYT4PYST,M10.1.0/0,M3.4.0/0", + "dst_offset": 60, + }, + { + "index": 22, + "zone_str": "(UTC-04:00) Atlantic Standard Time (Canada)", + "tz_str": "AST4", + "dst_offset": 0, + }, + { + "index": 23, + "zone_str": "(UTC-04:00) Atlantic Daylight Time (Canada)", + "tz_str": "AST4ADT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 24, + "zone_str": "(UTC-04:00) Cuiaba", + "tz_str": "AMT4AMST,M10.3.0/0,M2.3.0/0", + "dst_offset": 60, + }, + { + "index": 25, + "zone_str": "(UTC-04:00) Georgetown, La Paz, Manaus, San Juan", + "tz_str": "BOT4", + "dst_offset": 0, + }, + { + "index": 26, + "zone_str": "(UTC-04:00) Santiago", + "tz_str": "AMT4AMST,M10.3.0/0,M2.3.0/0", + "dst_offset": 60, + }, + { + "index": 27, + "zone_str": "(UTC-03:30) Newfoundland", + "tz_str": "NST3:30NDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 28, + "zone_str": "(UTC-03:00) Brasilia", + "tz_str": "BRT3BRST,M10.3.0/0,M2.3.0/0", + "dst_offset": 60, + }, + { + "index": 29, + "zone_str": "(UTC-03:00) Buenos Aires", + "tz_str": "3", + "dst_offset": 0, + }, + { + "index": 30, + "zone_str": "(UTC-03:00) Cayenne, Fortaleza", + "tz_str": "3", + "dst_offset": 0, + }, + { + "index": 31, + "zone_str": "(UTC-03:00) Greenland", + "tz_str": "PMST3PMDT,M3.2.0,M11.1.0", + "dst_offset": 60, + }, + { + "index": 32, + "zone_str": "(UTC-03:00) Montevideo", + "tz_str": "UYT3UYST,M10.1.0,M3.2.0", + "dst_offset": 60, + }, + { + "index": 33, + "zone_str": "(UTC-03:00) Salvador", + "tz_str": "3", + "dst_offset": 0, + }, + { + "index": 34, + "zone_str": "(UTC-02:00) Coordinated Universal Time-02", + "tz_str": "2", + "dst_offset": 0, + }, + { + "index": 35, + "zone_str": "(UTC-01:00) Azores", + "tz_str": "AZOT1AZOST,M3.5.0/0,M10.5.0/1", + "dst_offset": 60, + }, + { + "index": 36, + "zone_str": "(UTC-01:00) Cabo Verde Is.", + "tz_str": "CVT1", + "dst_offset": 0, + }, + { + "index": 37, + "zone_str": "(UTC) Casablanca", + "tz_str": "WET0WEST,M3.5.0,M10.5.0/3", + "dst_offset": 60, + }, + { + "index": 38, + "zone_str": "(UTC) Coordinated Universal Time", + "tz_str": "GMT0", + "dst_offset": 0, + }, + { + "index": 39, + "zone_str": "(UTC) Dublin, Edinburgh, Lisbon, London", + "tz_str": "GMT0BST,M3.5.0/1,M10.5.0", + "dst_offset": 60, + }, + { + "index": 40, + "zone_str": "(UTC) Monrovia, Reykjavik", + "tz_str": "GMT0", + "dst_offset": 0, + }, + { + "index": 41, + "zone_str": "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna", + "tz_str": "CET-1CEST,M3.5.0,M10.5.0/3", + "dst_offset": 60, + }, + { + "index": 42, + "zone_str": "(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague", + "tz_str": "CET-1CEST,M3.5.0,M10.5.0/3", + "dst_offset": 60, + }, + { + "index": 43, + "zone_str": "(UTC+01:00) Brussels, Copenhagen, Madrid, Paris", + "tz_str": "CET-1CEST,M3.5.0,M10.5.0/3", + "dst_offset": 60, + }, + { + "index": 44, + "zone_str": "(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb", + "tz_str": "CET-1CEST,M3.5.0,M10.5.0/3", + "dst_offset": 60, + }, + { + "index": 45, + "zone_str": "(UTC+01:00) West Central Africa", + "tz_str": "WAT-1", + "dst_offset": 0, + }, + { + "index": 46, + "zone_str": "(UTC+01:00) Windhoek", + "tz_str": "WAT-1WAST,M9.1.0,M4.1.0", + "dst_offset": 60, + }, + { + "index": 47, + "zone_str": "(UTC+02:00) Amman", + "tz_str": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "dst_offset": 60, + }, + { + "index": 48, + "zone_str": "(UTC+02:00) Athens, Bucharest", + "tz_str": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "dst_offset": 60, + }, + { + "index": 49, + "zone_str": "(UTC+02:00) Beirut", + "tz_str": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "dst_offset": 60, + }, + { + "index": 50, + "zone_str": "(UTC+02:00) Cairo", + "tz_str": "-2", + "dst_offset": 0, + }, + { + "index": 51, + "zone_str": "(UTC+02:00) Damascus", + "tz_str": "EET-2EEST,M3.5.5/0,M10.5.5/0", + "dst_offset": 60, + }, + { + "index": 52, + "zone_str": "(UTC+02:00) E. Europe", + "tz_str": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "dst_offset": 60, + }, + { + "index": 53, + "zone_str": "(UTC+02:00) Harare, Pretoria", + "tz_str": "-2", + "dst_offset": 0, + }, + { + "index": 54, + "zone_str": "(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius", + "tz_str": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "dst_offset": 60, + }, + { + "index": 55, + "zone_str": "(UTC+02:00) Istanbul", + "tz_str": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "dst_offset": 60, + }, + { + "index": 56, + "zone_str": "(UTC+02:00) Jerusalem", + "tz_str": "EET-2EEST,M3.5.0/3,M10.5.0/4", + "dst_offset": 60, + }, + { + "index": 57, + "zone_str": "(UTC+02:00) Kaliningrad (RTZ 1)", + "tz_str": "EET-2", + "dst_offset": 0, + }, + { + "index": 58, + "zone_str": "(UTC+02:00) Tripoli", + "tz_str": "-2", + "dst_offset": 0, + }, + { + "index": 59, + "zone_str": "(UTC+03:00) Baghdad", + "tz_str": "AST-3", + "dst_offset": 0, + }, + { + "index": 60, + "zone_str": "(UTC+03:00) Kuwait, Riyadh", + "tz_str": "AST-3", + "dst_offset": 0, + }, + { + "index": 61, + "zone_str": "(UTC+03:00) Minsk", + "tz_str": "MSK-3", + "dst_offset": 0, + }, + { + "index": 62, + "zone_str": "(UTC+03:00) Moscow, St. Petersburg, Volgograd (RTZ 2)", + "tz_str": "MSK-3", + "dst_offset": 0, + }, + { + "index": 63, + "zone_str": "(UTC+03:00) Nairobi", + "tz_str": "-3", + "dst_offset": 0, + }, + { + "index": 64, + "zone_str": "(UTC+03:30) Tehran", + "tz_str": "AZT-3:30AZST,M3.5.0/4,M10.5.0/5", + "dst_offset": 60, + }, + { + "index": 65, + "zone_str": "(UTC+04:00) Abu Dhabi, Muscat", + "tz_str": "GST-4", + "dst_offset": 0, + }, + { + "index": 66, + "zone_str": "(UTC+04:00) Baku", + "tz_str": "AZT-4AZST,M3.5.0/4,M10.5.0/5", + "dst_offset": 60, + }, + { + "index": 67, + "zone_str": "(UTC+04:00) Izhevsk, Samara (RTZ 3)", + "tz_str": "SAMT-4", + "dst_offset": 0, + }, + { + "index": 68, + "zone_str": "(UTC+04:00) Port Louis", + "tz_str": "-4", + "dst_offset": 0, + }, + { + "index": 69, + "zone_str": "(UTC+04:00) Tbilisi", + "tz_str": "GET-4", + "dst_offset": 0, + }, + { + "index": 70, + "zone_str": "(UTC+04:00) Yerevan", + "tz_str": "AMT-4", + "dst_offset": 0, + }, + { + "index": 71, + "zone_str": "(UTC+04:30) Kabul", + "tz_str": "AFT-4:30", + "dst_offset": 0, + }, + { + "index": 72, + "zone_str": "(UTC+05:00) Ashgabat, Tashkent", + "tz_str": "TMT-5", + "dst_offset": 0, + }, + { + "index": 73, + "zone_str": "(UTC+05:00) Ekaterinburg (RTZ 4)", + "tz_str": "YEKT-5", + "dst_offset": 0, + }, + { + "index": 74, + "zone_str": "(UTC+05:00) Islamabad, Karachi", + "tz_str": "PKT-5", + "dst_offset": 0, + }, + { + "index": 75, + "zone_str": "(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi", + "tz_str": "IST-5:30", + "dst_offset": 0, + }, + { + "index": 76, + "zone_str": "(UTC+05:30) Sri Jayawardenepura", + "tz_str": "IST-5:30", + "dst_offset": 0, + }, + { + "index": 77, + "zone_str": "(UTC+05:45) Kathmandu", + "tz_str": "NPT-5:45", + "dst_offset": 0, + }, + { + "index": 78, + "zone_str": "(UTC+06:00) Astana", + "tz_str": "-6", + "dst_offset": 0, + }, + { + "index": 79, + "zone_str": "(UTC+06:00) Dhaka", + "tz_str": "BDT-6", + "dst_offset": 0, + }, + { + "index": 80, + "zone_str": "(UTC+06:00) Novosibirsk (RTZ 5)", + "tz_str": "NOVT-6", + "dst_offset": 0, + }, + { + "index": 81, + "zone_str": "(UTC+06:30) Yangon (Rangoon)", + "tz_str": "MMT-6:30", + "dst_offset": 0, + }, + { + "index": 82, + "zone_str": "(UTC+07:00) Bangkok, Hanoi, Jakarta", + "tz_str": "ICT-7", + "dst_offset": 0, + }, + { + "index": 83, + "zone_str": "(UTC+07:00) Krasnoyarsk (RTZ 6)", + "tz_str": "KRAT-7", + "dst_offset": 0, + }, + { + "index": 84, + "zone_str": "(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi", + "tz_str": "CST-8", + "dst_offset": 0, + }, + { + "index": 85, + "zone_str": "(UTC+08:00) Irkutsk (RTZ 7)", + "tz_str": "IRKT-8", + "dst_offset": 0, + }, + { + "index": 86, + "zone_str": "(UTC+08:00) Kuala Lumpur, Singapore", + "tz_str": "MYT-8", + "dst_offset": 0, + }, + { + "index": 87, + "zone_str": "(UTC+08:00) Perth", + "tz_str": "-8", + "dst_offset": 0, + }, + { + "index": 88, + "zone_str": "(UTC+08:00) Taipei", + "tz_str": "CST-8", + "dst_offset": 0, + }, + { + "index": 89, + "zone_str": "(UTC+08:00) Ulaanbaatar", + "tz_str": "-8", + "dst_offset": 0, + }, + { + "index": 90, + "zone_str": "(UTC+09:00) Osaka, Sapporo, Tokyo", + "tz_str": "JST-9", + "dst_offset": 0, + }, + { + "index": 91, + "zone_str": "(UTC+09:00) Seoul", + "tz_str": "KST-9", + "dst_offset": 0, + }, + { + "index": 92, + "zone_str": "(UTC+09:00) Yakutsk (RTZ 8)", + "tz_str": "YAKT-9", + "dst_offset": 0, + }, + { + "index": 93, + "zone_str": "(UTC+09:30) Adelaide", + "tz_str": "ACST-9:30ACDT,M10.1.0,M4.1.0/3", + "dst_offset": 60, + }, + { + "index": 94, + "zone_str": "(UTC+09:30) Darwin", + "tz_str": "ACST-9:30", + "dst_offset": 0, + }, + { + "index": 95, + "zone_str": "(UTC+10:00) Brisbane", + "tz_str": "-10", + "dst_offset": 0, + }, + { + "index": 96, + "zone_str": "(UTC+10:00) Canberra, Melbourne, Sydney", + "tz_str": "AEST-10AEDT,M10.1.0,M4.1.0/3", + "dst_offset": 60, + }, + { + "index": 97, + "zone_str": "(UTC+10:00) Guam, Port Moresby", + "tz_str": "ChST-10", + "dst_offset": 0, + }, + { + "index": 98, + "zone_str": "(UTC+10:00) Hobart", + "tz_str": "AEST-10AEDT,M10.1.0,M4.1.0/3", + "dst_offset": 60, + }, + { + "index": 99, + "zone_str": "(UTC+10:00) Magadan", + "tz_str": "MAGT-10", + "dst_offset": 0, + }, + { + "index": 100, + "zone_str": "(UTC+10:00) Vladivostok, Magadan (RTZ 9)", + "tz_str": "VLAT-10", + "dst_offset": 0, + }, + { + "index": 101, + "zone_str": "(UTC+11:00) Chokurdakh (RTZ 10)", + "tz_str": "-11", + "dst_offset": 0, + }, + { + "index": 102, + "zone_str": "(UTC+11:00) Solomon Is., New Caledonia", + "tz_str": "SBT-11", + "dst_offset": 0, + }, + { + "index": 103, + "zone_str": "(UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky (RTZ 11)", + "tz_str": "ANAT-12", + "dst_offset": 0, + }, + { + "index": 104, + "zone_str": "(UTC+12:00) Auckland, Wellington", + "tz_str": "NZST-12NZDT,M9.5.0,M4.1.0/3", + "dst_offset": 60, + }, + { + "index": 105, + "zone_str": "(UTC+12:00) Coordinated Universal Time+12", + "tz_str": "-12", + "dst_offset": 0, + }, + { + "index": 106, + "zone_str": "(UTC+12:00) Fiji", + "tz_str": "NZST-12NZDT,M9.5.0,M4.1.0/3", + "dst_offset": 60, + }, + { + "index": 107, + "zone_str": "(UTC+13:00) Nuku'alofa", + "tz_str": "TKT-13", + "dst_offset": 0, + }, + { + "index": 108, + "zone_str": "(UTC+13:00) Samoa", + "tz_str": "WSST-13WSDT,M9.5.0/3,M4.1.0/4", + "dst_offset": 60, + }, + { + "index": 109, + "zone_str": "(UTC+14:00) Kiritimati Island", + "tz_str": "LINT-14", + "dst_offset": 0, + }, +] + class Time(Module): """Implements the timezone settings.""" @@ -52,3 +715,11 @@ async def get_time(self): async def get_timezone(self): """Request timezone information from the device.""" return await self.call("get_timezone") + + def get_timezones(self): + """Return allowed timezones.""" + return timezones + + async def set_timezone(self, tz_index): + """Request timezone information from the device.""" + return await self.call("set_timezone", {"index": tz_index}) diff --git a/kasa/smartbulb.py b/kasa/smartbulb.py index 1d6ba31e3..c0ad21e06 100644 --- a/kasa/smartbulb.py +++ b/kasa/smartbulb.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, Field, root_validator -from .modules import Antitheft, Cloud, Countdown, Emeter, Schedule, Time, Usage +from .modules import Antitheft, BulbSchedule, Cloud, Countdown, Emeter, Time, Usage from .smartdevice import DeviceType, SmartDevice, SmartDeviceException, requires_update @@ -202,7 +202,7 @@ class SmartBulb(SmartDevice): def __init__(self, host: str, *, port: Optional[int] = None) -> None: super().__init__(host=host, port=port) self._device_type = DeviceType.Bulb - self.add_module("schedule", Schedule(self, "smartlife.iot.common.schedule")) + self.add_module("schedule", BulbSchedule(self, "smartlife.iot.common.schedule")) self.add_module("usage", Usage(self, "smartlife.iot.common.schedule")) self.add_module("antitheft", Antitheft(self, "smartlife.iot.common.anti_theft")) self.add_module("time", Time(self, "smartlife.iot.common.timesetting"))