from __future__ import annotations from abc import ABC, abstractmethod from enum import Enum, IntFlag, auto from typing import Dict, List, Optional, Union import typing class WeekDay(IntFlag): MON = 0b1 TUE = 0b10 WED = 0b100 THU = 0b1000 FRI = 0b10000 SAT = 0b100000 SUN = 0b1000000 class Manufacturer: def __init__(self, name: str): self.name: str = name def __repr__(self): return f"name: {self.name}" def to_json(self): return {"name": self.name} @staticmethod def parse_dict(manufacturer: Dict): return Manufacturer(manufacturer["name"]) class FirmwareVersion: def __init__(self, search: bool, current: str, update: bool, running: bool): self.search: bool = search self.current: str = current self.update: bool = update self.running: bool = running def __repr__(self): return f"search: {self.search}; current: {self.current}; update: {self.update}; running: {self.running}" def to_json(self): return {"search": self.search, "current": self.current, "update": self.update, "running": self.running} @staticmethod def parse_dict(firmware_version: dict): return FirmwareVersion(firmware_version["search"], firmware_version["current"], firmware_version["update"], firmware_version["running"]) class PushService: def __init__(self, mail_address: str, unit_settings: List, is_enabled: bool): self.mail_address: str = mail_address self.unit_settings: List = unit_settings self.is_enabled: bool = is_enabled def __repr__(self): return f"" def to_json(self): return {"mailAddress": self.mail_address, "unitSettings": self.unit_settings, "isEnabled": self.is_enabled} @staticmethod def parse_dict(push_service: Dict): return PushService(push_service["mailAddress"], push_service["unitSettings"], push_service["isEnabled"]) class SkillType(Enum): SmartHomeTemperatureSensor = auto() SmartHomeThermostat = auto() SmartHomeBattery = auto() class Skill(ABC): def __init__(self, skill_type: SkillType): self.type: SkillType = skill_type @abstractmethod def to_json(self): pass @abstractmethod def to_web_data(self, device_id: int): pass @staticmethod def parse_dict(skill: Dict): skill_type = SkillType[skill["type"]] if skill_type == SkillType.SmartHomeTemperatureSensor: return TemperatureSkill.parse_dict(skill) elif skill_type == SkillType.SmartHomeThermostat: return ThermostatSkill.parse_dict(skill) elif skill_type == SkillType.SmartHomeBattery: return BatterySkill.parse_dict(skill) else: raise NotImplementedError(skill_type) class PresetName(Enum): LOWER_TEMPERATURE = auto() UPPER_TEMPERATURE = auto() HOLIDAY_TEMPERATURE = auto() class Preset: def __init__(self, name: PresetName, temperature: Optional[int]): self.name: PresetName = name self.temperature: Optional[int] = temperature def __repr__(self): return f"" def to_json(self): return {"name": self.name.name, "temperature": self.temperature} class Description: def __init__(self, action: str, preset_temperature: Optional[Preset]): self.action: str = action self.preset_temperature: Optional[Preset] = preset_temperature def __repr__(self): desc = f"" def to_json(self): return {"description": self.description.to_json(), "timeSetting": self.time_setting.to_json()} @staticmethod def parse_dict(change: Dict): return Change(Description.parse_dict(change["description"]), TimeSetting.parse_dict(change["timeSetting"])) class TemperatureDropDetection: def __init__(self, do_not_heat_offset_in_minutes: int, sensitivity: int): self.do_not_heat_offset_in_minutes: int = do_not_heat_offset_in_minutes self.sensitivity: int = sensitivity def __repr__(self): # ToDo pass def to_json(self): return { "doNotHeatOffsetInMinutes": self.do_not_heat_offset_in_minutes, "sensitivity": self.sensitivity } def to_web_data(self, device_id: int): return { "WindowOpenTrigger": self.sensitivity + 3, "WindowOpenTimer": self.do_not_heat_offset_in_minutes } @staticmethod def parse_dict(drop_detection: Dict): return TemperatureDropDetection(drop_detection["doNotHeatOffsetInMinutes"], drop_detection["sensitivity"]) class ScheduleKind(Enum): REPETITIVE = auto() WEEKLY_TIMETABLE = auto() class ThermostatSkillMode(Enum): TARGET_TEMPERATURE = auto() class Action: def __init__(self, is_enabled: bool, time_setting: TimeSetting, description: Description): self.is_enabled: bool = is_enabled self.time_setting: TimeSetting = time_setting self.description: Description = description def __repr__(self): return f"" def to_json(self): return {"isEnabled": self.is_enabled, "timeSetting": self.time_setting.to_json(), "description": self.description.to_json()} @staticmethod def parse_dict(action: Dict): return Action(action["isEnabled"], TimeSetting.parse_dict(action["timeSetting"]), Description.parse_dict(action["description"])) class Schedule: def __init__(self, is_enabled: bool, kind: ScheduleKind, name: str, actions: List[Action]): self.is_enabled: bool = is_enabled self.kind: ScheduleKind = kind self.name: str = name self.actions: List[Action] = actions def __repr__(self): return f"" def to_json(self): return { "isEnabled": self.is_enabled, "kind": self.kind.name, "name": self.name, "actions": [action.to_json() for action in self.actions] } def to_web_data(self, device_id: int): data = {} if self.kind == ScheduleKind.REPETITIVE and self.name == "HOLIDAYS": # ToDo: Enum for names? enabled_count = 0 for num, holiday in enumerate(self.actions): num += 1 _, start_month, start_day = holiday.time_setting.start_date.split("-") _, end_month, end_day = holiday.time_setting.end_date.split("-") data.update({ f"Holiday{num}StartDay": start_day, f"Holiday{num}StartMonth": start_month, f"Holiday{num}StartHour": holiday.time_setting.start_time.split(":")[0], f"Holiday{num}EndDay": end_day, f"Holiday{num}EndMonth": end_month, f"Holiday{num}EndHour": holiday.time_setting.end_time.split(":")[0], f"Holiday{num}Enabled": 1 if holiday.is_enabled else 0, f"Holiday{num}ID": num }) if holiday.is_enabled: enabled_count += 1 data["HolidayEnabledCount"] = enabled_count elif self.kind == ScheduleKind.REPETITIVE and self.name == "SUMMER_TIME": _, start_month, start_day = self.actions[0].time_setting.start_date.split("-") _, end_month, end_day = self.actions[0].time_setting.end_date.split("-") data = { "SummerStartDay": start_day, "SummerStartMonth": start_month, "SummerEndDay": end_day, "SummerEndMonth": end_month, "SummerEnabled": 1 if self.is_enabled else 0 } elif self.kind == ScheduleKind.WEEKLY_TIMETABLE and self.name == "TEMPERATURE": timer_items = {} for action in self.actions: if not action.is_enabled: continue time = "".join(action.time_setting.start_time.split(":")[:-1]) if time not in timer_items.keys(): timer_items[time] = [0, 0] heat = 1 if action.description.preset_temperature.name == PresetName.UPPER_TEMPERATURE else 0 days = timer_items[time][heat] days |= action.time_setting.day_of_week timer_items[time][heat] = days num = 0 for key in timer_items.keys(): if timer_items[key][0] != 0: data.update({f"timer_item_{num}": f"{key};0;{timer_items[key][0].as_integer_ratio()[0]}"}) num += 1 if timer_items[key][1] != 0: data.update({f"timer_item_{num}": f"{key};1;{timer_items[key][1].as_integer_ratio()[0]}"}) num += 1 else: raise NotImplementedError(self.name) return data @staticmethod def parse_dict(schedule: Dict): # ToDo: Make TypedDicts for all those dicts return Schedule(schedule["isEnabled"], ScheduleKind[schedule["kind"]], schedule["name"], [Action.parse_dict(action) for action in schedule["actions"]]) class TimeControl: def __init__(self, is_enabled: bool, time_schedules: List[Schedule]): self.is_enabled: bool = is_enabled self.time_schedules: List[Schedule] = time_schedules def __repr__(self): return f"