diff --git a/miio/integrations/airdog/airpurifier/airpurifier_airdog.py b/miio/integrations/airdog/airpurifier/airpurifier_airdog.py index ab6f19395..cad28bc47 100644 --- a/miio/integrations/airdog/airpurifier/airpurifier_airdog.py +++ b/miio/integrations/airdog/airpurifier/airpurifier_airdog.py @@ -7,6 +7,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -48,16 +49,23 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """True if device is turned on.""" return self.power == "on" @property + @setting( + "Mode", + setter_name="set_mode_and_speed", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode. @@ -66,26 +74,37 @@ def mode(self) -> OperationMode: return OperationMode(self.data["mode"]) @property + @setting( + "Speed", + setter_name="set_mode_and_speed", + min_value=1, + max_value=5, + step=1, + ) def speed(self) -> int: """Current speed level.""" return self.data["speed"] @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["lock"] == "lock" @property + @sensor("Clean Filters") def clean_filters(self) -> bool: """True if the display shows "-C-" and the filter must be cleaned.""" return self.data["clean"] == "y" @property + @sensor("PM2.5", unit="μg/m³") def pm25(self) -> int: """Return particulate matter value (0...300μg/m³).""" return self.data["pm"] @property + @sensor("Formaldehyde") def hcho(self) -> int | None: """Return formaldehyde value.""" if self.data["hcho"] is not None: diff --git a/miio/integrations/cgllc/airmonitor/airqualitymonitor.py b/miio/integrations/cgllc/airmonitor/airqualitymonitor.py index 1d26bb794..a0c48ca13 100644 --- a/miio/integrations/cgllc/airmonitor/airqualitymonitor.py +++ b/miio/integrations/cgllc/airmonitor/airqualitymonitor.py @@ -5,6 +5,7 @@ from miio.click_common import command, format_output from miio.device import Device, DeviceStatus +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -57,16 +58,19 @@ def __init__(self, data): self.data = data @property + @sensor("Power") def power(self) -> str | None: """Current power state.""" return self.data.get("power") @property + @sensor("Is On") def is_on(self) -> bool: """Return True if the device is turned on.""" return self.power == "on" @property + @sensor("USB Power") def usb_power(self) -> bool | None: """Return True if the device's usb is on.""" if "usb_state" in self.data and self.data["usb_state"] is not None: @@ -74,16 +78,19 @@ def usb_power(self) -> bool | None: return None @property + @sensor("AQI") def aqi(self) -> int | None: """Air quality index value (0..600).""" return self.data.get("aqi") @property + @sensor("Battery", unit="%") def battery(self) -> int | None: """Current battery level (0..100).""" return self.data.get("battery") @property + @setting("Display Clock", setter_name="set_display_clock") def display_clock(self) -> bool | None: """Display a clock instead the AQI.""" if "time_state" in self.data and self.data["time_state"] is not None: @@ -91,6 +98,7 @@ def display_clock(self) -> bool | None: return None @property + @setting("Night Mode", setter_name="set_night_mode") def night_mode(self) -> bool | None: """Return True if the night mode is on.""" if "night_state" in self.data and self.data["night_state"] is not None: @@ -98,16 +106,19 @@ def night_mode(self) -> bool | None: return None @property + @sensor("Night Time Begin") def night_time_begin(self) -> str | None: """Return the begin of the night time.""" return self.data.get("night_beg_time") @property + @sensor("Night Time End") def night_time_end(self) -> str | None: """Return the end of the night time.""" return self.data.get("night_end_time") @property + @sensor("Sensor State") def sensor_state(self) -> str | None: """Sensor state.""" return self.data.get("sensor_state") @@ -123,21 +134,25 @@ def co2e(self) -> int | None: return self.data.get("co2e") @property + @sensor("Humidity", unit="%") def humidity(self) -> float | None: """Return humidity value (0...100%).""" return self.data.get("humidity") @property + @sensor("PM2.5", unit="μg/m³") def pm25(self) -> float | None: """Return pm2.5 value (0...999μg/m³).""" return self.data.get("pm25") @property + @sensor("Temperature", unit="°C") def temperature(self) -> float | None: """Return temperature value (-10...50°C).""" return self.data.get("temperature") @property + @sensor("TVOC") def tvoc(self) -> int | None: """Return tvoc value.""" return self.data.get("tvoc") diff --git a/miio/integrations/cgllc/airmonitor/airqualitymonitor_miot.py b/miio/integrations/cgllc/airmonitor/airqualitymonitor_miot.py index 676f0ae15..24da44e7e 100644 --- a/miio/integrations/cgllc/airmonitor/airqualitymonitor_miot.py +++ b/miio/integrations/cgllc/airmonitor/airqualitymonitor_miot.py @@ -4,6 +4,7 @@ import click from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting from miio.miot_device import DeviceStatus, MiotDevice _LOGGER = logging.getLogger(__name__) @@ -107,57 +108,90 @@ def __init__(self, data): self.data = data @property + @sensor("Humidity", unit="%") def humidity(self) -> int: """Return humidity value (0...100%).""" return self.data["humidity"] @property + @sensor("PM2.5", unit="µg/m³") def pm25(self) -> int: """Return PM 2.5 value (0...1000ppm).""" return self.data["pm25"] @property + @sensor("PM10", unit="µg/m³") def pm10(self) -> int: """Return PM 10 value (0...1000ppm).""" return self.data["pm10"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> float: """Return temperature value (-30...100°C).""" return self.data["temperature"] @property + @sensor("CO2", unit="ppm") def co2(self) -> int: """Return co2 value (0...9999ppm).""" return self.data["co2"] @property + @sensor("Battery", unit="%") def battery(self) -> int: """Return battery level (0...100%).""" return self.data["battery"] @property + @sensor("Charging State") def charging_state(self) -> ChargingState: """Return charging state.""" return ChargingState(self.data["charging_state"]) @property + @setting( + "Monitoring Frequency", + unit="s", + setter_name="set_monitoring_frequency_duration", + min_value=0, + max_value=600, + ) def monitoring_frequency(self) -> int: """Return monitoring frequency time (0..600 s).""" return self.data["monitoring_frequency"] @property + @setting( + "Screen Off", + unit="s", + setter_name="set_screen_off_duration", + min_value=0, + max_value=300, + ) def screen_off(self) -> int: """Return screen off time (0..300 s).""" return self.data["screen_off"] @property + @setting( + "Device Off", + unit="min", + setter_name="set_device_off_duration", + min_value=0, + max_value=60, + ) def device_off(self) -> int: """Return device off time (0..60 min).""" return self.data["device_off"] @property - def display_temperature_unit(self): + @setting( + "Display Temperature Unit", + setter_name="set_display_temperature_unit", + choices=DisplayTemperatureUnitCGDN1, + ) + def display_temperature_unit(self) -> DisplayTemperatureUnitCGDN1: """Return display temperature unit.""" return DisplayTemperatureUnitCGDN1(self.data["temperature_unit"]) diff --git a/miio/integrations/chuangmi/camera/chuangmi_camera.py b/miio/integrations/chuangmi/camera/chuangmi_camera.py index dac1b4744..39dd402da 100644 --- a/miio/integrations/chuangmi/camera/chuangmi_camera.py +++ b/miio/integrations/chuangmi/camera/chuangmi_camera.py @@ -11,6 +11,7 @@ from miio.click_common import EnumType, command, format_output from miio.device import Device, DeviceStatus +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -126,6 +127,7 @@ def wdr(self) -> bool: return self.data["wdr"] == "on" @property + @sensor(name="Track") def track(self) -> bool: """Tracking status.""" return self.data["track"] == "on" @@ -136,21 +138,25 @@ def watermark(self) -> bool: return self.data["watermark"] == "on" @property + @sensor(name="SD Card Status") def sdcard_status(self) -> int: """SD card status.""" return self.data["sdcard_status"] @property + @sensor(name="Max Client") def max_client(self) -> int: """Unknown.""" return self.data["max_client"] @property + @sensor(name="Night Mode") def night_mode(self) -> int: """Night mode.""" return self.data["night_mode"] @property + @sensor(name="Mini Level") def mini_level(self) -> int: """Unknown.""" return self.data["mini_level"] diff --git a/miio/integrations/chuangmi/plug/chuangmi_plug.py b/miio/integrations/chuangmi/plug/chuangmi_plug.py index 3429e6acf..c0d448bd8 100644 --- a/miio/integrations/chuangmi/plug/chuangmi_plug.py +++ b/miio/integrations/chuangmi/plug/chuangmi_plug.py @@ -6,6 +6,7 @@ from miio import Device, DeviceException, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting from miio.utils import deprecated _LOGGER = logging.getLogger(__name__) @@ -45,6 +46,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> bool: """Current power state.""" if "on" in self.data: @@ -60,6 +62,7 @@ def is_on(self) -> bool: return self.power @property + @sensor("Temperature", unit="°C") def temperature(self) -> int: return self.data["temperature"] @@ -71,6 +74,7 @@ def usb_power(self) -> bool | None: return None @property + @sensor("Load Power", unit="W") def load_power(self) -> float | None: """Current power load, if available.""" if "load_power" in self.data and self.data["load_power"] is not None: @@ -84,6 +88,7 @@ def wifi_led(self) -> bool | None: return self.led @property + @setting("LED", setter_name="set_led") def led(self) -> bool | None: """True if the wifi led is turned on.""" if "wifi_led" in self.data and self.data["wifi_led"] is not None: diff --git a/miio/integrations/chunmi/cooker/cooker.py b/miio/integrations/chunmi/cooker/cooker.py index 1131a259c..189667af2 100644 --- a/miio/integrations/chunmi/cooker/cooker.py +++ b/miio/integrations/chunmi/cooker/cooker.py @@ -8,6 +8,7 @@ from miio.click_common import command, format_output from miio.device import Device, DeviceStatus +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -125,10 +126,12 @@ def __init__(self, data: str): self.data = [] @property + @sensor("Temperatures", unit="°C") def temperatures(self) -> list[int]: return self.data @property + @sensor("Raw") def raw(self) -> str: return "".join([f"{value:02x}" for value in self.data]) @@ -164,26 +167,32 @@ def __init__(self, custom: str): self.custom = [int(custom[i : i + 2], 16) for i in range(0, len(custom), 2)] @property + @sensor("Jingzhu Appointment") def jingzhu_appointment(self) -> time: return time(hour=self.custom[0], minute=self.custom[1]) @property + @sensor("Kuaizhu Appointment") def kuaizhu_appointment(self) -> time: return time(hour=self.custom[2], minute=self.custom[3]) @property + @sensor("Zhuzhou Appointment") def zhuzhou_appointment(self) -> time: return time(hour=self.custom[4], minute=self.custom[5]) @property + @sensor("Zhuzhou Cooking") def zhuzhou_cooking(self) -> time: return time(hour=self.custom[6], minute=self.custom[7]) @property + @sensor("Favorite Appointment") def favorite_appointment(self) -> time: return time(hour=self.custom[8], minute=self.custom[9]) @property + @sensor("Favorite Cooking") def favorite_cooking(self) -> time: return time(hour=self.custom[10], minute=self.custom[11]) @@ -207,6 +216,7 @@ def __init__(self, stage: str): self.stage = stage @property + @sensor("State") def state(self) -> int: """ @@ -217,14 +227,17 @@ def state(self) -> int: return int(self.stage[0:2], 16) @property + @sensor("Rice ID") def rice_id(self) -> int: return int(self.stage[2:6], 16) @property + @sensor("Taste") def taste(self) -> int: return int(self.stage[6:8], 16) @property + @sensor("Taste Phase") def taste_phase(self) -> int: phase = int(self.taste / 33) @@ -233,6 +246,7 @@ def taste_phase(self) -> int: return phase @property + @sensor("Name") def name(self) -> str: try: return COOKING_STAGES[self.state]["name"] @@ -240,6 +254,7 @@ def name(self) -> str: return "Unknown stage" @property + @sensor("Description") def description(self) -> str: try: return COOKING_STAGES[self.state]["description"] @@ -247,6 +262,7 @@ def description(self) -> str: return "" @property + @sensor("Raw") def raw(self) -> str: return self.stage @@ -269,6 +285,7 @@ def __init__(self, timeouts: str | None = None): ] @property + @sensor("LED Off Timeout", unit="s") def led_off(self) -> int: return self.timeouts[0] @@ -277,6 +294,7 @@ def led_off(self, delay: int): self.timeouts[0] = delay @property + @sensor("Lid Open Timeout", unit="s") def lid_open(self) -> int: return self.timeouts[1] @@ -285,6 +303,7 @@ def lid_open(self, timeout: int): self.timeouts[1] = timeout @property + @sensor("Lid Open Warning Timeout", unit="s") def lid_open_warning(self) -> int: return self.timeouts[2] @@ -324,6 +343,7 @@ def __init__(self, settings: str | None = None): ] @property + @sensor("Pressure Supported") def pressure_supported(self) -> bool: return self._settings[0] & 1 != 0 @@ -335,6 +355,7 @@ def pressure_supported(self, supported: bool): self._settings[0] &= 254 @property + @sensor("LED On") def led_on(self) -> bool: return self._settings[0] & 2 != 0 @@ -346,6 +367,7 @@ def led_on(self, on: bool): self._settings[0] &= 253 @property + @sensor("Auto Keep Warm") def auto_keep_warm(self) -> bool: return self._settings[0] & 4 != 0 @@ -357,6 +379,7 @@ def auto_keep_warm(self, keep_warm: bool): self._settings[0] &= 251 @property + @sensor("Lid Open Warning") def lid_open_warning(self) -> bool: return self._settings[0] & 8 != 0 @@ -368,6 +391,7 @@ def lid_open_warning(self, alarm: bool): self._settings[0] &= 247 @property + @sensor("Lid Open Warning Delayed") def lid_open_warning_delayed(self) -> bool: return self._settings[0] & 16 != 0 @@ -379,6 +403,7 @@ def lid_open_warning_delayed(self, alarm: bool): self._settings[0] &= 239 @property + @sensor("Jingzhu Auto Keep Warm") def jingzhu_auto_keep_warm(self) -> bool: return self._settings[1] & 1 != 0 @@ -390,6 +415,7 @@ def jingzhu_auto_keep_warm(self, auto_keep_warm: bool): self._settings[1] &= 254 @property + @sensor("Kuaizhu Auto Keep Warm") def kuaizhu_auto_keep_warm(self) -> bool: return self._settings[1] & 2 != 0 @@ -401,6 +427,7 @@ def kuaizhu_auto_keep_warm(self, auto_keep_warm: bool): self._settings[1] &= 253 @property + @sensor("Zhuzhou Auto Keep Warm") def zhuzhou_auto_keep_warm(self) -> bool: return self._settings[1] & 4 != 0 @@ -412,6 +439,7 @@ def zhuzhou_auto_keep_warm(self, auto_keep_warm: bool): self._settings[1] &= 251 @property + @sensor("Favorite Auto Keep Warm") def favorite_auto_keep_warm(self) -> bool: return self._settings[1] & 8 != 0 @@ -470,16 +498,19 @@ def __init__(self, data): self.data = data @property + @sensor("Mode") def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["func"]) @property + @sensor("Menu") def menu(self) -> int: """Selected recipe id.""" return int(self.data["menu"], 16) @property + @sensor("Stage") def stage(self) -> CookingStage | None: """Current stage if cooking.""" stage = self.data["stage"] @@ -489,6 +520,7 @@ def stage(self) -> CookingStage | None: return None @property + @sensor("Temperature", unit="°C") def temperature(self) -> int | None: """Current temperature, if idle. @@ -501,6 +533,7 @@ def temperature(self) -> int | None: return None @property + @sensor("Start Time") def start_time(self) -> time | None: """Start time of cooking? @@ -514,11 +547,13 @@ def start_time(self) -> time | None: return None @property + @sensor("Remaining", unit="min") def remaining(self) -> int: """Remaining minutes of the cooking process.""" return int(self.data["t_func"]) @property + @sensor("Cooking Delayed", unit="min") def cooking_delayed(self) -> int | None: """Wait n minutes before cooking / scheduled cooking.""" delay = int(self.data["t_precook"]) @@ -529,31 +564,37 @@ def cooking_delayed(self) -> int | None: return None @property + @sensor("Duration", unit="min") def duration(self) -> int: """Duration of the cooking process.""" return int(self.data["t_cook"]) @property + @sensor("Cooker Settings") def cooker_settings(self) -> CookerSettings: """Settings of the cooker.""" return CookerSettings(self.data["setting"]) @property + @sensor("Interaction Timeouts") def interaction_timeouts(self) -> InteractionTimeouts: """Interaction timeouts.""" return InteractionTimeouts(self.data["delay"]) @property + @sensor("Hardware Version") def hardware_version(self) -> int: """Hardware version.""" return int(self.data["version"][0:4], 16) @property + @sensor("Firmware Version") def firmware_version(self) -> int: """Firmware version.""" return int(self.data["version"][4:8], 16) @property + @sensor("Favorite") def favorite(self) -> int: """Favored recipe id. @@ -562,6 +603,7 @@ def favorite(self) -> int: return int(self.data["favorite"], 16) @property + @sensor("Custom") def custom(self) -> CookerCustomizations | None: custom = self.data["custom"] diff --git a/miio/integrations/deerma/humidifier/airhumidifier_jsqs.py b/miio/integrations/deerma/humidifier/airhumidifier_jsqs.py index dd9ca74fc..6af103eee 100644 --- a/miio/integrations/deerma/humidifier/airhumidifier_jsqs.py +++ b/miio/integrations/deerma/humidifier/airhumidifier_jsqs.py @@ -5,6 +5,7 @@ import click from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting from miio.miot_device import DeviceStatus, MiotDevice _LOGGER = logging.getLogger(__name__) @@ -74,16 +75,23 @@ def is_on(self) -> bool: return self.data["power"] @property + @sensor(name="Power") def power(self) -> str: """Return power state.""" return "on" if self.is_on else "off" @property + @sensor(name="Error") def error(self) -> int: """Return error state.""" return self.data["fault"] @property + @setting( + name="Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Return current operation mode.""" @@ -103,11 +111,13 @@ def target_humidity(self) -> int | None: # Environment @property + @sensor(name="Relative Humidity", unit="%") def relative_humidity(self) -> int | None: """Return current humidity.""" return self.data.get("relative_humidity") @property + @sensor(name="Temperature", unit="C") def temperature(self) -> float | None: """Return current temperature, if available.""" return self.data.get("temperature") @@ -115,6 +125,7 @@ def temperature(self) -> float | None: # Alarm @property + @setting(name="Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool | None: """Return True if buzzer is on.""" return self.data.get("buzzer") @@ -122,6 +133,7 @@ def buzzer(self) -> bool | None: # Indicator Light @property + @setting(name="LED Light", setter_name="set_light") def led_light(self) -> bool | None: """Return status of the LED.""" return self.data.get("led_light") @@ -129,16 +141,19 @@ def led_light(self) -> bool | None: # Other @property + @sensor(name="Tank Filed") def tank_filed(self) -> bool | None: """Return the tank filed.""" return self.data.get("tank_filed") @property + @sensor(name="Water Shortage Fault") def water_shortage_fault(self) -> bool | None: """Return water shortage fault.""" return self.data.get("water_shortage_fault") @property + @setting(name="Overwet Protect", setter_name="set_overwet_protect") def overwet_protect(self) -> bool | None: """Return True if overwet mode is active.""" return self.data.get("overwet_protect") diff --git a/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py b/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py index 5fc93705b..8be9c010f 100644 --- a/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py +++ b/miio/integrations/deerma/humidifier/airhumidifier_mjjsq.py @@ -7,6 +7,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -55,6 +56,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Power") def power(self) -> str: """Power state.""" return "on" if self.data["OnOff_State"] == 1 else "off" @@ -65,6 +67,11 @@ def is_on(self) -> bool: return self.power == "on" @property + @setting( + name="Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode. @@ -73,41 +80,55 @@ def mode(self) -> OperationMode: return OperationMode(self.data["Humidifier_Gear"]) @property + @sensor(name="Temperature", unit="C") def temperature(self) -> int: """Current temperature in degree celsius.""" return self.data["TemperatureValue"] @property + @sensor(name="Humidity", unit="%") def humidity(self) -> int: """Current humidity in percent.""" return self.data["Humidity_Value"] @property + @setting(name="Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["TipSound_State"] == 1 @property + @setting(name="LED", setter_name="set_led") def led(self) -> bool: """True if LED is turned on.""" return self.data["Led_State"] == 1 @property + @setting( + name="Target Humidity", + setter_name="set_target_humidity", + unit="%", + min_value=0, + max_value=99, + ) def target_humidity(self) -> int: """Target humiditiy in percent.""" return self.data["HumiSet_Value"] @property + @sensor(name="No Water") def no_water(self) -> bool: """True if the water tank is empty.""" return self.data["waterstatus"] == 0 @property + @sensor(name="Water Tank Detached") def water_tank_detached(self) -> bool: """True if the water tank is detached.""" return self.data["watertankstatus"] == 0 @property + @setting(name="Wet Protection", setter_name="set_wet_protection") def wet_protection(self) -> bool | None: """True if wet protection is enabled.""" if self.data["wet_and_protect"] is not None: @@ -116,6 +137,7 @@ def wet_protection(self) -> bool | None: return None @property + @sensor(name="Use Time", unit="s") def use_time(self) -> int | None: """How long the device has been active in seconds. diff --git a/miio/integrations/dmaker/airfresh/airfresh_t2017.py b/miio/integrations/dmaker/airfresh/airfresh_t2017.py index 9abf695d4..87101cd98 100644 --- a/miio/integrations/dmaker/airfresh/airfresh_t2017.py +++ b/miio/integrations/dmaker/airfresh/airfresh_t2017.py @@ -7,6 +7,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -115,71 +116,93 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On") def is_on(self) -> bool: """Return True if device is on.""" return self.data["power"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode) def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["mode"]) @property + @sensor("PM2.5", unit="μg/m³") def pm25(self) -> int: """Fine particulate patter (PM2.5).""" return self.data["pm25"] @property + @sensor( + "CO2", + unit="ppm", + ) def co2(self) -> int: """Carbon dioxide.""" return self.data["co2"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> int: """Current temperature in degree celsions.""" return self.data["temperature_outside"] @property + @setting( + "Favorite Speed", + setter_name="set_favorite_speed", + min_value=0, + max_value=150, + ) def favorite_speed(self) -> int: """Favorite speed.""" return self.data["favourite_speed"] @property + @sensor("Control Speed") def control_speed(self) -> int: """Control speed.""" return self.data["control_speed"] @property + @sensor("Dust Filter Life Remaining", unit="%") def dust_filter_life_remaining(self) -> int | None: """Remaining dust filter life in percent.""" return self.data.get("filter_intermediate", self.data.get("filter_rate")) @property + @sensor("Dust Filter Life Remaining Days", unit="days") def dust_filter_life_remaining_days(self) -> int | None: """Remaining dust filter life in days.""" return self.data.get("filter_inter_day", self.data.get("filter_day")) @property + @sensor("Upper Filter Life Remaining", unit="%") def upper_filter_life_remaining(self) -> int | None: """Remaining upper filter life in percent.""" return self.data.get("filter_efficient") @property + @sensor("Upper Filter Life Remaining Days", unit="days") def upper_filter_life_remaining_days(self) -> int | None: """Remaining upper filter life in days.""" return self.data.get("filter_effi_day") @property + @setting("PTC", setter_name="set_ptc") def ptc(self) -> bool: """Return True if PTC is on.""" return self.data["ptc_on"] @property + @setting("PTC Level", setter_name="set_ptc_level", choices=PtcLevel) def ptc_level(self) -> PtcLevel | None: """PTC level.""" try: @@ -188,21 +211,25 @@ def ptc_level(self) -> PtcLevel | None: return None @property + @sensor("PTC Status") def ptc_status(self) -> bool: """Return true if PTC status is on.""" return self.data["ptc_status"] @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """Return True if sound is on.""" return self.data["sound"] @property + @setting("Display", setter_name="set_display") def display(self) -> bool: """Return True if the display is on.""" return self.data["display"] diff --git a/miio/integrations/dmaker/fan/fan.py b/miio/integrations/dmaker/fan/fan.py index e2a4220b4..a33f45cbc 100644 --- a/miio/integrations/dmaker/fan/fan.py +++ b/miio/integrations/dmaker/fan/fan.py @@ -5,6 +5,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting class MoveDirection(enum.Enum): @@ -49,51 +50,77 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode.""" return OperationMode(self.data["mode"]) @property + @setting( + "Speed", + unit="%", + setter_name="set_speed", + min_value=0, + max_value=100, + ) def speed(self) -> int: """Speed of the motor.""" return self.data["speed"] @property + @setting("Oscillate", setter_name="set_oscillate") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["roll_enable"] @property + @setting( + "Angle", + unit="°", + setter_name="set_angle", + min_value=30, + max_value=140, + ) def angle(self) -> int: """Oscillation angle.""" return self.data["roll_angle"] @property + @sensor("Delay Off Countdown", unit="s") def delay_off_countdown(self) -> int: """Countdown until turning off in seconds.""" return self.data["time_off"] @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """True if LED is turned on, if available.""" return self.data["light"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["beep_sound"] @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """True if child lock is on.""" return self.data["child_lock"] diff --git a/miio/integrations/dmaker/fan/fan_miot.py b/miio/integrations/dmaker/fan/fan_miot.py index 28b73564b..9a192d266 100644 --- a/miio/integrations/dmaker/fan/fan_miot.py +++ b/miio/integrations/dmaker/fan/fan_miot.py @@ -5,6 +5,7 @@ from miio import DeviceStatus, MiotDevice from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting class OperationMode(enum.Enum): @@ -189,16 +190,19 @@ def __init__(self, data: dict[str, Any], model: str) -> None: self.model = model @property + @sensor("Power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode) def mode(self) -> OperationMode: """Operation mode.""" if self.model == MODEL_FAN_P45: @@ -206,36 +210,49 @@ def mode(self) -> OperationMode: return OperationMode[OperationModeMiot(self.data["mode"]).name] @property + @setting("Speed", setter_name="set_speed", min_value=0, max_value=100) def speed(self) -> int: """Speed of the motor.""" return self.data["fan_speed"] @property + @setting("Oscillate", setter_name="set_oscillate") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["swing_mode"] @property + @setting("Angle", setter_name="set_angle") def angle(self) -> int: """Oscillation angle.""" return self.data["swing_mode_angle"] @property + @setting( + "Delay Off Countdown", + setter_name="delay_off", + unit="min", + min_value=0, + max_value=480, + ) def delay_off_countdown(self) -> int: """Countdown until turning off in minutes.""" return self.data["power_off_time"] @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """True if LED is turned on, if available.""" return self.data["light"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """True if child lock is on.""" return self.data["child_lock"] @@ -265,46 +282,61 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode) def mode(self) -> OperationMode: """Operation mode.""" return OperationMode[OperationModeMiot(self.data["mode"]).name] @property + @setting("Speed", setter_name="set_speed", min_value=1, max_value=3) def speed(self) -> int: """Speed of the motor.""" return self.data["fan_level"] @property + @setting("Oscillate", setter_name="set_oscillate") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["swing_mode"] @property + @setting( + "Delay Off Countdown", + setter_name="delay_off", + unit="min", + min_value=0, + max_value=480, + ) def delay_off_countdown(self) -> int: """Countdown until turning off in minutes.""" return self.data["power_off_time"] @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """True if LED is turned on.""" return self.data["light"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """True if child lock is on.""" return self.data["child_lock"] diff --git a/miio/integrations/dreame/vacuum/dreamevacuum_miot.py b/miio/integrations/dreame/vacuum/dreamevacuum_miot.py index 0bcd4c01a..a098c76a2 100644 --- a/miio/integrations/dreame/vacuum/dreamevacuum_miot.py +++ b/miio/integrations/dreame/vacuum/dreamevacuum_miot.py @@ -7,6 +7,7 @@ import click from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting from miio.miot_device import DeviceStatus as DeviceStatusContainer from miio.miot_device import MiotDevice, MiotMapping from miio.updater import OneShotServer @@ -305,34 +306,42 @@ def __init__(self, data, model): self.model = model @property + @sensor("Battery Level", unit="%") def battery_level(self) -> str: return self.data["battery_level"] @property + @sensor("Brush Left Time", unit="h") def brush_left_time(self) -> str: return self.data["brush_left_time"] @property + @sensor("Side Brush Left Time", unit="h") def brush_left_time2(self) -> str: return self.data["brush_left_time2"] @property + @sensor("Side Brush Life Level", unit="%") def brush_life_level2(self) -> str: return self.data["brush_life_level2"] @property + @sensor("Brush Life Level", unit="%") def brush_life_level(self) -> str: return self.data["brush_life_level"] @property + @sensor("Filter Left Time", unit="h") def filter_left_time(self) -> str: return self.data["filter_left_time"] @property + @sensor("Filter Life Level", unit="%") def filter_life_level(self) -> str: return self.data["filter_life_level"] @property + @sensor("Device Fault") def device_fault(self) -> FaultStatus | None: try: return FaultStatus(self.data["device_fault"]) @@ -341,6 +350,7 @@ def device_fault(self) -> FaultStatus | None: return None @property + @sensor("Charging State") def charging_state(self) -> ChargingState | None: try: return ChargingState(self.data["charging_state"]) @@ -349,6 +359,7 @@ def charging_state(self) -> ChargingState | None: return None @property + @sensor("Operating Mode") def operating_mode(self) -> OperatingMode | None: try: return OperatingMode(self.data["operating_mode"]) @@ -357,6 +368,7 @@ def operating_mode(self) -> OperatingMode | None: return None @property + @sensor("Device Status") def device_status(self) -> DeviceStatus | None: try: return DeviceStatus(self.data["device_status"]) @@ -365,58 +377,79 @@ def device_status(self) -> DeviceStatus | None: return None @property + @sensor("Timer Enable") def timer_enable(self) -> str: return self.data["timer_enable"] @property + @sensor("Start Time") def start_time(self) -> str: return self.data["start_time"] @property + @sensor("Stop Time") def stop_time(self) -> str: return self.data["stop_time"] @property + @sensor("Map View") def map_view(self) -> str: return self.data["map_view"] @property + @setting( + "Volume", + setter_name="set_sound_volume", + unit="%", + min_value=0, + max_value=100, + step=1, + ) def volume(self) -> str: return self.data["volume"] @property + @sensor("Voice Package") def voice_package(self) -> str: return self.data["voice_package"] @property + @sensor("Timezone") def timezone(self) -> str: return self.data["timezone"] @property + @sensor("Cleaning Time", unit="min") def cleaning_time(self) -> str: return self.data["cleaning_time"] @property + @sensor("Cleaning Area", unit="m²") def cleaning_area(self) -> str: return self.data["cleaning_area"] @property + @sensor("First Clean Time") def first_clean_time(self) -> str: return self.data["first_clean_time"] @property + @sensor("Total Clean Time", unit="min") def total_clean_time(self) -> str: return self.data["total_clean_time"] @property + @sensor("Total Clean Times") def total_clean_times(self) -> str: return self.data["total_clean_times"] @property + @sensor("Total Clean Area", unit="m²") def total_clean_area(self) -> str: return self.data["total_clean_area"] @property + @setting("Cleaning Mode", setter_name="set_fan_speed") def cleaning_mode(self): cleaning_mode = self.data["cleaning_mode"] cleaning_mode_enum_class = _get_cleaning_mode_enum_class(self.model) @@ -431,19 +464,23 @@ def cleaning_mode(self): return None @property + @sensor("Life Sieve", unit="%") def life_sieve(self) -> str | None: return self.data.get("life_sieve") @property + @sensor("Life Brush Side", unit="%") def life_brush_side(self) -> str | None: return self.data.get("life_brush_side") @property + @sensor("Life Brush Main", unit="%") def life_brush_main(self) -> str | None: return self.data.get("life_brush_main") # TODO: get/set water flow for Dreame 1C @property + @setting("Water Flow", setter_name="set_waterflow", choices=WaterFlow) def water_flow(self) -> WaterFlow | None: try: water_flow = self.data["water_flow"] @@ -456,6 +493,7 @@ def water_flow(self) -> WaterFlow | None: return None @property + @sensor("Water Box Carriage Attached") def is_water_box_carriage_attached(self) -> bool | None: """Return True if water box carriage (mop) is installed, None if sensor not present.""" diff --git a/miio/integrations/huayi/light/huizuo.py b/miio/integrations/huayi/light/huizuo.py index a5bd0076a..951ac84cf 100644 --- a/miio/integrations/huayi/light/huizuo.py +++ b/miio/integrations/huayi/light/huizuo.py @@ -11,6 +11,7 @@ from miio import DeviceStatus, MiotDevice, UnsupportedFeatureException from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -117,21 +118,37 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Is On") def is_on(self) -> bool: """Return True if device is on.""" return self.data["power"] @property + @setting( + "Brightness", + unit="%", + setter_name="set_brightness", + min_value=0, + max_value=100, + ) def brightness(self) -> int: """Return current brightness.""" return self.data["brightness"] @property + @setting( + "Color Temperature", + unit="K", + setter_name="set_color_temp", + min_value=3000, + max_value=6400, + ) def color_temp(self) -> int: """Return current color temperature.""" return self.data["color_temp"] @property + @sensor("Fan On") def is_fan_on(self) -> bool | None: """Return True if Fan is on.""" if "fan_power" in self.data: @@ -146,6 +163,7 @@ def fan_speed_level(self) -> int | None: return None @property + @sensor("Fan Reverse") def is_fan_reverse(self) -> bool | None: """Return True if Fan reverse is on.""" if "fan_motor_reverse" in self.data: @@ -153,6 +171,7 @@ def is_fan_reverse(self) -> bool | None: return None @property + @sensor("Fan Mode") def fan_mode(self) -> int | None: """Return 0 if 'Basic' and 1 if 'Natural wind'.""" if "fan_mode" in self.data: @@ -160,6 +179,7 @@ def fan_mode(self) -> int | None: return None @property + @sensor("Heater On") def is_heater_on(self) -> bool | None: """Return True if Heater is on.""" if "heater_power" in self.data: @@ -167,6 +187,7 @@ def is_heater_on(self) -> bool | None: return None @property + @sensor("Heater Fault Code") def heater_fault_code(self) -> int | None: """Return Heater's fault code. diff --git a/miio/integrations/ksmb/walkingpad/walkingpad.py b/miio/integrations/ksmb/walkingpad/walkingpad.py index 7384a015d..f14005365 100644 --- a/miio/integrations/ksmb/walkingpad/walkingpad.py +++ b/miio/integrations/ksmb/walkingpad/walkingpad.py @@ -7,6 +7,7 @@ from miio import Device, DeviceException, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -43,51 +44,81 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @sensor("Walking Time") def walking_time(self) -> timedelta: """Current walking duration in seconds.""" return timedelta(seconds=int(self.data["time"])) @property + @setting( + "Speed", + unit="km/h", + setter_name="set_speed", + min_value=0, + max_value=6, + ) def speed(self) -> float: """Current speed.""" return float(self.data["sp"]) @property + @setting( + "Start Speed", + unit="km/h", + setter_name="set_start_speed", + min_value=0, + max_value=6, + ) def start_speed(self) -> float: """Current start speed.""" return self.data["start_speed"] @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Current mode.""" return OperationMode(self.data["mode"]) @property + @setting( + "Sensitivity", + setter_name="set_sensitivity", + choices=OperationSensitivity, + ) def sensitivity(self) -> OperationSensitivity: """Current sensitivity.""" return OperationSensitivity(self.data["sensitivity"]) @property + @sensor("Step Count") def step_count(self) -> int: """Current steps.""" return int(self.data["step"]) @property + @sensor("Distance", unit="m") def distance(self) -> int: """Current distance in meters.""" return int(self.data["dist"]) @property + @sensor("Calories", unit="cal") def calories(self) -> int: """Current calories burnt.""" return int(self.data["cal"]) diff --git a/miio/integrations/leshow/fan/fan_leshow.py b/miio/integrations/leshow/fan/fan_leshow.py index 6f553227a..f734c4a49 100644 --- a/miio/integrations/leshow/fan/fan_leshow.py +++ b/miio/integrations/leshow/fan/fan_leshow.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -45,41 +46,67 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] == 1 else "off" @property + @sensor("Is On") def is_on(self) -> bool: """True if device is turned on.""" return self.data["power"] == 1 @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode.""" return OperationMode(self.data["mode"]) @property + @setting( + "Speed", + setter_name="set_speed", + unit="%", + min_value=0, + max_value=100, + step=1, + ) def speed(self) -> int: """Speed of the fan in percent.""" return self.data["blow"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["sound"] == 1 @property + @setting("Oscillate", setter_name="set_oscillate") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["yaw"] == 1 @property + @setting( + "Delay Off Countdown", + setter_name="delay_off", + unit="min", + min_value=0, + max_value=540, + step=1, + ) def delay_off_countdown(self) -> int: """Countdown until turning off in minutes.""" return self.data["timer"] @property + @sensor("Error Detected") def error_detected(self) -> bool: """True if a fault was detected.""" return self.data["fault"] == 1 diff --git a/miio/integrations/lumi/acpartner/airconditioningcompanion.py b/miio/integrations/lumi/acpartner/airconditioningcompanion.py index 24da4d035..25952fb9d 100644 --- a/miio/integrations/lumi/acpartner/airconditioningcompanion.py +++ b/miio/integrations/lumi/acpartner/airconditioningcompanion.py @@ -5,6 +5,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -97,11 +98,13 @@ def __init__(self, data): self.state = data["model_and_state"][1] @property + @sensor("Load Power", unit="W") def load_power(self) -> int: """Current power load of the air conditioner.""" return int(self.data["model_and_state"][2]) @property + @sensor("Power Socket") def power_socket(self) -> str | None: """Current socket power state.""" if "power_socket" in self.data and self.data["power_socket"] is not None: @@ -110,21 +113,25 @@ def power_socket(self) -> str | None: return None @property + @sensor("Air Condition Model") def air_condition_model(self) -> bytes: """Model of the air conditioner.""" return bytes.fromhex(self.model) @property + @sensor("Model Format") def model_format(self) -> int: """Version number of the model format.""" return self.air_condition_model[0] @property + @sensor("Device Type") def device_type(self) -> int: """Device type identifier.""" return self.air_condition_model[1] @property + @sensor("Air Condition Brand") def air_condition_brand(self) -> int: """Brand of the air conditioner. @@ -133,6 +140,7 @@ def air_condition_brand(self) -> int: return int(self.air_condition_model[2:4].hex(), 16) @property + @sensor("Air Condition Remote") def air_condition_remote(self) -> int: """Remote id. @@ -148,6 +156,7 @@ def air_condition_remote(self) -> int: return int(self.air_condition_model[4:8].hex(), 16) @property + @sensor("State Format") def state_format(self) -> int: """Version number of the state format. @@ -156,15 +165,18 @@ def state_format(self) -> int: return int(self.air_condition_model[8]) @property + @sensor("Air Condition Configuration") def air_condition_configuration(self) -> int: return self.state[2:10] @property + @sensor("Power") def power(self) -> str: """Current power state.""" return "on" if int(self.state[2:3]) == Power.On.value else "off" @property + @sensor("LED") def led(self) -> bool | None: """Current LED state.""" state = self.state[8:9] @@ -178,11 +190,13 @@ def led(self) -> bool | None: return None @property + @sensor("Is On") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @sensor("Target Temperature", unit="°C") def target_temperature(self) -> int | None: """Target temperature.""" try: @@ -191,6 +205,7 @@ def target_temperature(self) -> int | None: return None @property + @sensor("Swing Mode") def swing_mode(self) -> SwingMode | None: """Current swing mode.""" try: @@ -200,6 +215,7 @@ def swing_mode(self) -> SwingMode | None: return None @property + @sensor("Fan Speed") def fan_speed(self) -> FanSpeed | None: """Current fan speed.""" try: @@ -209,6 +225,7 @@ def fan_speed(self) -> FanSpeed | None: return None @property + @sensor("Mode") def mode(self) -> OperationMode | None: """Current operation mode.""" try: diff --git a/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py b/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py index ba6b57db6..fd8d2dcd6 100644 --- a/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py +++ b/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py @@ -5,6 +5,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -44,11 +45,13 @@ def __init__(self, data): self.data = data @property + @sensor("Load Power", unit="W") def load_power(self) -> int: """Current power load of the air conditioner.""" return int(self.data[-1]) @property + @sensor("Power") def power(self) -> str: """Current power state.""" return self.data[0] @@ -59,6 +62,7 @@ def is_on(self) -> bool: return self.power == "on" @property + @setting("Mode", setter_name="send_command", choices=OperationMode) def mode(self) -> OperationMode | None: """Current operation mode.""" try: @@ -68,6 +72,11 @@ def mode(self) -> OperationMode | None: return None @property + @setting( + "Target Temperature", + setter_name="send_command", + unit="°C", + ) def target_temperature(self) -> int | None: """Target temperature.""" try: @@ -76,6 +85,7 @@ def target_temperature(self) -> int | None: return None @property + @setting("Fan Speed", setter_name="send_command", choices=FanSpeed) def fan_speed(self) -> FanSpeed | None: """Current fan speed.""" try: @@ -85,6 +95,7 @@ def fan_speed(self) -> FanSpeed | None: return None @property + @setting("Swing Mode", setter_name="send_command", choices=SwingMode) def swing_mode(self) -> SwingMode | None: """Current swing mode.""" try: diff --git a/miio/integrations/lumi/camera/aqaracamera.py b/miio/integrations/lumi/camera/aqaracamera.py index 2a5a0cc4b..04c63ba30 100644 --- a/miio/integrations/lumi/camera/aqaracamera.py +++ b/miio/integrations/lumi/camera/aqaracamera.py @@ -17,6 +17,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -75,16 +76,19 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Type") def type(self) -> str: """TODO: Type of the camera? Name?""" return self.data["app_type"] @property + @sensor("Video Status") def video_status(self) -> bool: """Video state.""" return bool(self.data["video_state"]) @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently on.""" return self.video_status == 1 @@ -95,6 +99,10 @@ def md(self) -> bool: return bool(self.data["md_status"]) @property + @setting( + "Motion Detection Sensitivity", + setter_name="md_sensitivity", + ) def md_sensitivity(self): """Motion detection sensitivity.""" return self.data["mdsensitivity"] @@ -115,6 +123,7 @@ def flipped(self) -> bool: return self.data["flip_state"] @property + @sensor("Camera Offsets") def offsets(self) -> CameraOffset: """Camera offset information.""" return CameraOffset( @@ -124,6 +133,7 @@ def offsets(self) -> CameraOffset: ) @property + @sensor("Channel ID") def channel_id(self) -> int: """TODO: Zigbee channel?""" return self.data["channel_id"] @@ -134,16 +144,19 @@ def fullstop(self) -> bool: return self.data["fullstop"] != 0 @property + @sensor("P2P ID") def p2p_id(self) -> str: """P2P ID for video and audio.""" return self.data["p2p_id"] @property + @sensor("AV ID") def av_id(self) -> str: """TODO: What is this? ID for the cloud?""" return self.data["avID"] @property + @sensor("AV Password") def av_password(self) -> str: """TODO: What is this? Password for the cloud?""" return self.data["avPass"] diff --git a/miio/integrations/lumi/curtain/curtain_youpin.py b/miio/integrations/lumi/curtain/curtain_youpin.py index a64fa3fa5..e2cfb7d89 100644 --- a/miio/integrations/lumi/curtain/curtain_youpin.py +++ b/miio/integrations/lumi/curtain/curtain_youpin.py @@ -6,6 +6,7 @@ from miio import DeviceStatus, MiotDevice from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -73,46 +74,75 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Status") def status(self) -> Status: """Device status.""" return Status(self.data["status"]) @property + @setting("Manual Enabled", setter_name="set_manual_enabled") def is_manual_enabled(self) -> bool: """True if manual controls are enabled.""" return bool(self.data["is_manual_enabled"]) @property + @setting( + "Polarity", + setter_name="set_polarity", + choices=Polarity, + ) def polarity(self) -> Polarity: """Motor rotation polarity.""" return Polarity(self.data["polarity"]) @property + @setting( + "Position Limited", + setter_name="set_position_limit", + ) def is_position_limited(self) -> bool: """Position limit.""" return bool(self.data["is_position_limited"]) @property + @setting("Night Tip Light", setter_name="set_night_tip_light") def night_tip_light(self) -> bool: """Night tip light status.""" return bool(self.data["night_tip_light"]) @property + @sensor("Run Time", unit="s") def run_time(self) -> int: """Run time of the motor.""" return self.data["run_time"] @property + @sensor("Current Position", unit="%") def current_position(self) -> int: """Current curtain position.""" return self.data["current_position"] @property + @setting( + "Target Position", + setter_name="set_target_position", + unit="%", + min_value=0, + max_value=100, + step=1, + ) def target_position(self) -> int: """Target curtain position.""" return self.data["target_position"] @property + @setting( + "Adjust Value", + setter_name="set_adjust_value", + min_value=-100, + max_value=100, + step=1, + ) def adjust_value(self) -> int: """Adjust value.""" return self.data["adjust_value"] diff --git a/miio/integrations/mijia/vacuum/g1vacuum.py b/miio/integrations/mijia/vacuum/g1vacuum.py index b8fbe8dc6..fc20bd033 100644 --- a/miio/integrations/mijia/vacuum/g1vacuum.py +++ b/miio/integrations/mijia/vacuum/g1vacuum.py @@ -5,6 +5,7 @@ import click from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting from miio.miot_device import DeviceStatus, MiotDevice _LOGGER = logging.getLogger(__name__) @@ -159,21 +160,25 @@ def __init__(self, data): self.data = data @property + @sensor("Battery", unit="%") def battery(self) -> int: """Battery Level.""" return self.data["battery"] @property + @sensor("Charge State") def charge_state(self) -> G1ChargeState: """Charging State.""" return G1ChargeState(self.data["charge_state"]) @property + @sensor("Error Code") def error_code(self) -> int: """Error code as returned by the device.""" return int(self.data["error_code"]) @property + @sensor("Error") def error(self) -> str: """Human readable error description, see also :func:`error_code`.""" try: @@ -182,66 +187,79 @@ def error(self) -> str: return f"Definition missing for error {self.error_code}" @property + @sensor("State") def state(self) -> G1State: """Vacuum Status.""" return G1State(self.data["state"]) @property + @setting("Fan Speed", setter_name="set_fan_speed", choices=G1FanSpeed) def fan_speed(self) -> G1FanSpeed: """Fan Speed.""" return G1FanSpeed(self.data["fan_speed"]) @property + @sensor("Operating Mode") def operating_mode(self) -> G1VacuumMode: """Operating Mode.""" return G1VacuumMode(self.data["operating_mode"]) @property + @sensor("Mop State") def mop_state(self) -> G1MopState: """Mop State.""" return G1MopState(self.data["mop_state"]) @property + @sensor("Water Level") def water_level(self) -> G1WaterLevel: """Water Level.""" return G1WaterLevel(self.data["water_level"]) @property + @sensor("Main Brush Life Level", unit="%") def main_brush_life_level(self) -> int: """Main Brush Life Level in %.""" return self.data["main_brush_life_level"] @property + @sensor("Main Brush Time Left") def main_brush_time_left(self) -> timedelta: """Main Brush Remaining Time in Minutes.""" return timedelta(minutes=self.data["main_brush_time_left"]) @property + @sensor("Side Brush Life Level", unit="%") def side_brush_life_level(self) -> int: """Side Brush Life Level in %.""" return self.data["side_brush_life_level"] @property + @sensor("Side Brush Time Left") def side_brush_time_left(self) -> timedelta: """Side Brush Remaining Time in Minutes.""" return timedelta(minutes=self.data["side_brush_time_left"]) @property + @sensor("Filter Life Level", unit="%") def filter_life_level(self) -> int: """Filter Life Level in %.""" return self.data["filter_life_level"] @property + @sensor("Filter Time Left") def filter_time_left(self) -> timedelta: """Filter remaining time.""" return timedelta(minutes=self.data["filter_time_left"]) @property + @sensor("Clean Area", unit="cm²") def clean_area(self) -> int: """Clean Area in cm2.""" return self.data["clean_area"] @property + @sensor("Clean Time") def clean_time(self) -> timedelta: """Clean time.""" return timedelta(minutes=self.data["clean_time"]) @@ -263,16 +281,19 @@ def __init__(self, data) -> None: self.data = data @property + @sensor("Total Clean Count") def total_clean_count(self) -> int: """Total Number of Cleanings.""" return self.data["total_clean_count"] @property + @sensor("Total Clean Area", unit="m²") def total_clean_area(self) -> int: """Total Area Cleaned in m2.""" return self.data["total_clean_area"] @property + @sensor("Total Clean Time") def total_clean_time(self) -> timedelta: """Total Cleaning Time.""" return timedelta(hours=self.data["total_clean_area"]) diff --git a/miio/integrations/mmgg/petwaterdispenser/status.py b/miio/integrations/mmgg/petwaterdispenser/status.py index 2704bc281..ac90c6d72 100644 --- a/miio/integrations/mmgg/petwaterdispenser/status.py +++ b/miio/integrations/mmgg/petwaterdispenser/status.py @@ -2,6 +2,7 @@ from datetime import timedelta from typing import Any +from miio.devicestatus import sensor, setting from miio.miot_device import DeviceStatus @@ -34,6 +35,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Sponge Filter Left Days") def sponge_filter_left_days(self) -> timedelta: """Filter life time remaining in days.""" return timedelta(days=self.data["filter_left_time"]) @@ -44,26 +46,35 @@ def is_on(self) -> bool: return self.data["on"] @property + @setting( + name="Mode", + setter_name="set_mode", + choices=OperatingMode, + ) def mode(self) -> OperatingMode: """OperatingMode.""" return OperatingMode(self.data["mode"]) @property + @setting(name="LED", setter_name="set_led") def is_led_on(self) -> bool: """True if enabled.""" return self.data["indicator_light"] @property + @sensor(name="Cotton Left Days") def cotton_left_days(self) -> timedelta: """Cotton filter life time remaining in days.""" return timedelta(days=self.data["cotton_left_time"]) @property + @sensor(name="Before Cleaning Days") def before_cleaning_days(self) -> timedelta: """Days before cleaning.""" return timedelta(days=self.data["remain_clean_time"]) @property + @sensor(name="No Water") def is_no_water(self) -> bool: """True if there is no water left.""" if self.data["no_water_flag"]: @@ -71,31 +82,42 @@ def is_no_water(self) -> bool: return True @property + @sensor(name="No Water Duration") def no_water_minutes(self) -> timedelta: """Minutes without water.""" return timedelta(minutes=self.data["no_water_time"]) @property + @sensor(name="Pump Blocked") def is_pump_blocked(self) -> bool: """True if pump is blocked.""" return self.data["pump_block_flag"] @property + @sensor(name="Lid Up") def is_lid_up(self) -> bool: """True if lid is up.""" return self.data["lid_up_flag"] @property + @setting( + name="Timezone", + setter_name="set_timezone", + min_value=-12, + max_value=12, + ) def timezone(self) -> int: """Timezone from -12 to +12.""" return self.data["timezone"] @property + @setting(name="Location", setter_name="set_location") def location(self) -> str: """Device location string.""" return self.data["location"] @property + @sensor(name="Error Detected") def is_error_detected(self) -> bool: """True if fault detected.""" return self.data["fault"] > 0 diff --git a/miio/integrations/nwt/dehumidifier/airdehumidifier.py b/miio/integrations/nwt/dehumidifier/airdehumidifier.py index 2885cb2b7..c63d0b04d 100644 --- a/miio/integrations/nwt/dehumidifier/airdehumidifier.py +++ b/miio/integrations/nwt/dehumidifier/airdehumidifier.py @@ -7,6 +7,7 @@ from miio import Device, DeviceError, DeviceException, DeviceInfo, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -63,16 +64,23 @@ def __init__(self, data: dict[str, Any], device_info: DeviceInfo) -> None: self.device_info = device_info @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["on_off"] @property + @sensor("Is On") def is_on(self) -> bool: """True if device is turned on.""" return self.power == "on" @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode. @@ -81,6 +89,7 @@ def mode(self) -> OperationMode: return OperationMode(self.data["mode"]) @property + @sensor("Temperature", unit="°C") def temperature(self) -> float | None: """Current temperature, if available.""" if "temp" in self.data and self.data["temp"] is not None: @@ -88,21 +97,25 @@ def temperature(self) -> float | None: return None @property + @sensor("Humidity", unit="%") def humidity(self) -> int: """Current humidity.""" return self.data["humidity"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] == "on" @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """LED brightness if available.""" return self.data["led"] == "on" @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] == "on" @@ -125,26 +138,31 @@ def fan_speed(self) -> FanSpeed | None: return None @property + @sensor("Tank Full") def tank_full(self) -> bool: """The remaining amount of water in percent.""" return self.data["tank_full"] == "on" @property + @sensor("Compressor Status") def compressor_status(self) -> bool: """Compressor status.""" return self.data["compressor_status"] == "on" @property + @sensor("Defrost Status") def defrost_status(self) -> bool: """Defrost status.""" return self.data["defrost_status"] == "on" @property + @sensor("Fan St") def fan_st(self) -> int: """Fan st.""" return self.data["fan_st"] @property + @sensor("Alarm") def alarm(self) -> str: """Alarm.""" return self.data["alarm"] diff --git a/miio/integrations/philips/light/ceil.py b/miio/integrations/philips/light/ceil.py index 36772694a..a808aa365 100644 --- a/miio/integrations/philips/light/ceil.py +++ b/miio/integrations/philips/light/ceil.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -25,41 +26,72 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @setting( + "Brightness", + setter_name="set_brightness", + unit="%", + min_value=1, + max_value=100, + step=1, + ) def brightness(self) -> int: """Current brightness.""" return self.data["bright"] @property + @setting( + "Scene", + setter_name="set_scene", + min_value=1, + max_value=4, + step=1, + ) def scene(self) -> int: """Current fixed scene (brightness & colortemp).""" return self.data["snm"] @property + @setting( + "Delay Off Countdown", + setter_name="delay_off", + unit="s", + ) def delay_off_countdown(self) -> int: """Countdown until turning off in seconds.""" return self.data["dv"] @property + @setting( + "Color Temperature", + setter_name="set_color_temperature", + min_value=1, + max_value=100, + step=1, + ) def color_temperature(self) -> int: """Current color temperature.""" return self.data["cct"] @property + @sensor("Smart Night Light") def smart_night_light(self) -> bool: """Smart night mode state.""" return self.data["bl"] == 1 @property + @sensor("Automatic Color Temperature") def automatic_color_temperature(self) -> bool: """Automatic color temperature state.""" return self.data["ac"] == 1 diff --git a/miio/integrations/philips/light/philips_bulb.py b/miio/integrations/philips/light/philips_bulb.py index d91d88cf5..3c45ea46b 100644 --- a/miio/integrations/philips/light/philips_bulb.py +++ b/miio/integrations/philips/light/philips_bulb.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -37,6 +38,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: return self.data["power"] @@ -45,6 +47,7 @@ def is_on(self) -> bool: return self.power == "on" @property + @setting("Brightness", setter_name="set_brightness", unit="%") def brightness(self) -> int | None: if "bright" in self.data: return self.data["bright"] @@ -53,18 +56,24 @@ def brightness(self) -> int | None: return None @property + @setting("Color Temperature", setter_name="set_color_temperature") def color_temperature(self) -> int | None: if "cct" in self.data: return self.data["cct"] return None @property + @setting("Scene", setter_name="set_scene") def scene(self) -> int | None: if "snm" in self.data: return self.data["snm"] return None @property + @sensor( + "Delay Off Countdown", + unit="s", + ) def delay_off_countdown(self) -> int: return self.data["dv"] diff --git a/miio/integrations/philips/light/philips_eyecare.py b/miio/integrations/philips/light/philips_eyecare.py index 96ae3d461..b9ed3843c 100644 --- a/miio/integrations/philips/light/philips_eyecare.py +++ b/miio/integrations/philips/light/philips_eyecare.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -20,16 +21,25 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @setting( + "Brightness", + unit="%", + setter_name="set_brightness", + min_value=1, + max_value=100, + ) def brightness(self) -> int: """Current brightness of the primary light.""" return self.data["bright"] @@ -45,6 +55,13 @@ def ambient(self) -> bool: return self.data["ambstatus"] == "on" @property + @setting( + "Ambient Brightness", + unit="%", + setter_name="set_ambient_brightness", + min_value=1, + max_value=100, + ) def ambient_brightness(self) -> int: """Brightness of the ambient light.""" return self.data["ambvalue"] @@ -55,6 +72,12 @@ def eyecare(self) -> bool: return self.data["eyecare"] == "on" @property + @setting( + "Scene", + setter_name="set_scene", + min_value=1, + max_value=4, + ) def scene(self) -> int: """Current fixed scene.""" return self.data["scene_num"] @@ -65,6 +88,10 @@ def smart_night_light(self) -> bool: return self.data["bls"] == "on" @property + @sensor( + "Delay Off Countdown", + unit="min", + ) def delay_off_countdown(self) -> int: """Countdown until turning off in minutes.""" return self.data["dvalue"] diff --git a/miio/integrations/philips/light/philips_moonlight.py b/miio/integrations/philips/light/philips_moonlight.py index 097e190aa..b442e8e48 100644 --- a/miio/integrations/philips/light/philips_moonlight.py +++ b/miio/integrations/philips/light/philips_moonlight.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor, setting from miio.utils import int_to_rgb _LOGGER = logging.getLogger(__name__) @@ -23,6 +24,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Power") def power(self) -> str: return self.data["pow"] @@ -31,23 +33,44 @@ def is_on(self) -> bool: return self.power == "on" @property + @setting( + name="Brightness", + setter_name="set_brightness", + unit="%", + min_value=1, + max_value=100, + ) def brightness(self) -> int: return self.data["bri"] @property + @setting( + name="Color Temperature", + setter_name="set_color_temperature", + min_value=1, + max_value=100, + ) def color_temperature(self) -> int: return self.data["cct"] @property + @setting(name="RGB", setter_name="set_rgb") def rgb(self) -> tuple[int, int, int]: """Return color in RGB.""" return int_to_rgb(int(self.data["rgb"])) @property + @setting( + name="Scene", + setter_name="set_scene", + min_value=1, + max_value=6, + ) def scene(self) -> int: return self.data["snm"] @property + @sensor(name="Sleep Assistant") def sleep_assistant(self) -> int: """Example values: @@ -59,24 +82,29 @@ def sleep_assistant(self) -> int: return self.data["sta"] @property + @sensor(name="Sleep Off Time", unit="s") def sleep_off_time(self) -> int: return self.data["spr"] @property + @sensor(name="Total Assistant Sleep Time", unit="s") def total_assistant_sleep_time(self) -> int: return self.data["spt"] @property + @sensor(name="Brand Sleep") def brand_sleep(self) -> bool: # sp_sleep_open? return self.data["ms"] == 1 @property + @sensor(name="Brand") def brand(self) -> bool: # sp_xm_bracelet? return self.data["mb"] == 1 @property + @sensor(name="Wake Up Time") def wake_up_time(self) -> list[int]: # Example: [weekdays?, hour, minute] return self.data["wkp"] diff --git a/miio/integrations/philips/light/philips_rwread.py b/miio/integrations/philips/light/philips_rwread.py index 3cb7de9c0..d6ad392d9 100644 --- a/miio/integrations/philips/light/philips_rwread.py +++ b/miio/integrations/philips/light/philips_rwread.py @@ -7,6 +7,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -35,41 +36,70 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @setting( + "Brightness", + setter_name="set_brightness", + unit="%", + min_value=1, + max_value=100, + step=1, + ) def brightness(self) -> int: """Current brightness.""" return self.data["bright"] @property + @setting( + "Delay Off Countdown", + setter_name="delay_off", + unit="s", + ) def delay_off_countdown(self) -> int: """Countdown until turning off in seconds.""" return self.data["dv"] @property + @setting( + "Scene", + setter_name="set_scene", + min_value=1, + max_value=4, + step=1, + ) def scene(self) -> int: """Current fixed scene.""" return self.data["snm"] @property + @setting("Motion Detection", setter_name="set_motion_detection") def motion_detection(self) -> bool: """True if motion detection is enabled.""" return self.data["flm"] == 1 @property + @setting( + "Motion Detection Sensitivity", + setter_name="set_motion_detection_sensitivity", + choices=MotionDetectionSensitivity, + ) def motion_detection_sensitivity(self) -> MotionDetectionSensitivity: """The sensitivity of the motion detection.""" return MotionDetectionSensitivity(self.data["flmv"]) @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """True if child lock is enabled.""" return self.data["chl"] == 1 diff --git a/miio/integrations/pwzn/relay/pwzn_relay.py b/miio/integrations/pwzn/relay/pwzn_relay.py index 3380de57b..3855705f7 100644 --- a/miio/integrations/pwzn/relay/pwzn_relay.py +++ b/miio/integrations/pwzn/relay/pwzn_relay.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -70,6 +71,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Relay State") def relay_state(self) -> int | None: """Current relay state.""" if "relay_status" in self.data: @@ -77,6 +79,7 @@ def relay_state(self) -> int | None: return None @property + @sensor("Relay Names") def relay_names(self) -> dict[int, str]: def _extract_index_from_key(name) -> int: """extract the index from the variable.""" @@ -89,6 +92,7 @@ def _extract_index_from_key(name) -> int: } @property + @sensor("Active Relay Count") def on_count(self) -> int | None: """Number of on relay.""" if "on_count" in self.data: diff --git a/miio/integrations/roidmi/vacuum/roidmivacuum_miot.py b/miio/integrations/roidmi/vacuum/roidmivacuum_miot.py index 9e633f4c0..e2eeb091b 100644 --- a/miio/integrations/roidmi/vacuum/roidmivacuum_miot.py +++ b/miio/integrations/roidmi/vacuum/roidmivacuum_miot.py @@ -9,6 +9,7 @@ import click from miio.click_common import EnumType, command +from miio.devicestatus import sensor, setting from miio.integrations.roborock.vacuum.vacuumcontainers import ( # TODO: remove roborock import DNDStatus, ) @@ -224,16 +225,19 @@ def __init__(self, data): self.data = data @property + @sensor("Battery", unit="%") def battery(self) -> int: """Remaining battery in percentage.""" return self.data["battery_level"] @property + @sensor("Error Code") def error_code(self) -> int: """Error code as returned by the device.""" return int(self.data["error_code"]) @property + @sensor("Error") def error(self) -> str: """Human readable error description, see also :func:`error_code`.""" try: @@ -242,6 +246,7 @@ def error(self) -> str: return f"Definition missing for error {self.error_code}" @property + @sensor("Charging State") def charging_state(self) -> ChargingState: """Charging state (Charging/Discharging)""" try: @@ -251,6 +256,7 @@ def charging_state(self) -> ChargingState: return ChargingState.Unknown @property + @sensor("Sweep Mode") def sweep_mode(self) -> SweepMode: """Sweep mode point/area/total etc.""" try: @@ -260,6 +266,7 @@ def sweep_mode(self) -> SweepMode: return SweepMode.Unknown @property + @setting("Fan Speed", setter_name="set_fanspeed", choices=FanSpeed) def fan_speed(self) -> FanSpeed: """Current fan speed.""" try: @@ -269,6 +276,11 @@ def fan_speed(self) -> FanSpeed: return FanSpeed.Unknown @property + @setting( + "Sweep Type", + setter_name="set_sweep_type", + choices=SweepType, + ) def sweep_type(self) -> SweepType: """Current sweep type sweep/mop/sweep&mop.""" try: @@ -278,6 +290,11 @@ def sweep_type(self) -> SweepType: return SweepType.Unknown @property + @setting( + "Path Mode", + setter_name="set_path_mode", + choices=PathMode, + ) def path_mode(self) -> PathMode: """Current path-mode: normal/y-mopping etc.""" try: @@ -287,11 +304,19 @@ def path_mode(self) -> PathMode: return PathMode.Unknown @property + @sensor("Mop Attached") def is_mop_attached(self) -> bool: """Return True if mop is attached.""" return self.data["mop_present"] @property + @setting( + "Dust Collection Frequency", + setter_name="set_dust_collection_frequency", + min_value=0, + max_value=3, + step=1, + ) def dust_collection_frequency(self) -> int: """Frequency for emptying the dust bin. @@ -300,6 +325,7 @@ def dust_collection_frequency(self) -> int: return self.data["work_station_freq"] @property + @setting("Timing", setter_name="set_timing") def timing(self) -> str: """Repeated cleaning. @@ -337,6 +363,7 @@ def timing(self) -> str: return self.data["timing"] @property + @setting("Carpet Mode", setter_name="set_carpet_mode") def carpet_mode(self) -> bool: """Auto boost on carpet.""" return self.data["auto_boost"] @@ -363,11 +390,17 @@ def _seconds_to_components(val): ) @property + @sensor("DND Status") def dnd_status(self) -> DNDStatus: """Returns do-not-disturb status.""" return self._parse_forbid_mode(self.data["forbid_mode"]) @property + @setting( + "Water Level", + setter_name="set_water_level", + choices=WaterLevel, + ) def water_level(self) -> WaterLevel: """Get current water level.""" try: @@ -377,32 +410,41 @@ def water_level(self) -> WaterLevel: return WaterLevel.Unknown @property + @setting("Double Clean", setter_name="set_double_clean") def double_clean(self) -> bool: """Is double clean enabled.""" return self.data["double_clean"] @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """Return True if led/display on vaccum is on.""" return self.data["led_switch"] @property + @setting( + "Lidar Collision Sensor", + setter_name="set_lidar_collision_sensor", + ) def is_lidar_collision_sensor(self) -> bool: """When ON, the robot will use lidar as the main detection sensor to help reduce collisions.""" return self.data["lidar_collision"] @property + @sensor("Station Key") def station_key(self) -> bool: """When ON: long press the display will turn on dust collection.""" return self.data["station_key"] @property + @setting("Station LED", setter_name="set_station_led") def station_led(self) -> bool: """Return if station display is on.""" return self.data["station_led"] @property + @sensor("Current Audio") def current_audio(self) -> str: """Current voice setting. @@ -411,21 +453,25 @@ def current_audio(self) -> str: return self.data["current_audio"] @property + @sensor("Clean Time") def clean_time(self) -> timedelta: """Time used for cleaning (if finished, shows how long it took).""" return timedelta(seconds=self.data["clean_time_sec"]) @property + @sensor("Clean Area", unit="m²") def clean_area(self) -> int: """Cleaned area in m2.""" return self.data["clean_area"] @property + @sensor("State Code") def state_code(self) -> int: """State code as returned by the device.""" return int(self.data["state"]) @property + @sensor("State") def state(self) -> RoidmiState: """Human readable state description, see also :func:`state_code`.""" try: @@ -435,26 +481,38 @@ def state(self) -> RoidmiState: return RoidmiState.Unknown @property + @setting( + "Volume", + setter_name="set_sound_volume", + unit="%", + min_value=0, + max_value=100, + step=1, + ) def volume(self) -> int: """Return device sound volumen level.""" return self.data["volume"] @property + @setting("Muted", setter_name="set_sound_muted") def is_muted(self) -> bool: """True if device is muted.""" return bool(self.data["mute"]) @property + @sensor("Is Paused") def is_paused(self) -> bool: """Return True if vacuum is paused.""" return self.state in [RoidmiState.Paused, RoidmiState.FindChargerPause] @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently cleaning in any mode.""" return self.state == RoidmiState.Sweeping @property + @sensor("Got Error") def got_error(self) -> bool: """True if an error has occurred.""" return self.error_code != 0 @@ -467,16 +525,19 @@ def __init__(self, data) -> None: self.data = data @property + @sensor("Total Duration") def total_duration(self) -> timedelta: """Total cleaning duration.""" return timedelta(seconds=self.data["total_clean_time_sec"]) @property + @sensor("Total Area", unit="m²") def total_area(self) -> int: """Total cleaned area.""" return self.data["total_clean_areas"] @property + @sensor("Count") def count(self) -> int: """Number of cleaning runs.""" return self.data["clean_counts"] @@ -500,16 +561,19 @@ def _calcUsageTime( return original_total * (1 - remaning_fraction) @property + @sensor("Filter Usage") def filter(self) -> timedelta: """Filter usage time.""" return self._calcUsageTime(self.filter_left, self.data["filter_life_level"]) @property + @sensor("Filter Left") def filter_left(self) -> timedelta: """How long until the filter should be changed.""" return timedelta(minutes=self.data["filter_left_minutes"]) @property + @sensor("Main Brush Usage") def main_brush(self) -> timedelta: """Main brush usage time.""" return self._calcUsageTime( @@ -517,11 +581,13 @@ def main_brush(self) -> timedelta: ) @property + @sensor("Main Brush Left") def main_brush_left(self) -> timedelta: """How long until the main brush should be changed.""" return timedelta(minutes=self.data["main_brush_left_minutes"]) @property + @sensor("Side Brush Usage") def side_brush(self) -> timedelta: """Main brush usage time.""" return self._calcUsageTime( @@ -529,11 +595,13 @@ def side_brush(self) -> timedelta: ) @property + @sensor("Side Brush Left") def side_brush_left(self) -> timedelta: """How long until the side brushes should be changed.""" return timedelta(minutes=self.data["side_brushes_left_minutes"]) @property + @sensor("Sensor Dirty") def sensor_dirty(self) -> timedelta: """Return time since last sensor clean.""" return self._calcUsageTime( @@ -541,6 +609,7 @@ def sensor_dirty(self) -> timedelta: ) @property + @sensor("Sensor Dirty Left") def sensor_dirty_left(self) -> timedelta: """How long until the sensors should be cleaned.""" return timedelta(minutes=self.data["sensor_dirty_time_left_minutes"]) diff --git a/miio/integrations/shuii/humidifier/airhumidifier_jsq.py b/miio/integrations/shuii/humidifier/airhumidifier_jsq.py index d0364e32a..219ef6ee4 100644 --- a/miio/integrations/shuii/humidifier/airhumidifier_jsq.py +++ b/miio/integrations/shuii/humidifier/airhumidifier_jsq.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -59,6 +60,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] == 1 else "off" @@ -69,6 +71,11 @@ def is_on(self) -> bool: return self.power == "on" @property + @setting( + name="Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode. @@ -84,21 +91,29 @@ def mode(self) -> OperationMode: return mode @property + @sensor(name="Temperature", unit="C") def temperature(self) -> int: """Current temperature in degree celsius.""" return self.data["temperature"] @property + @sensor(name="Humidity", unit="%") def humidity(self) -> int: """Current humidity in percent.""" return self.data["humidity"] @property + @setting(name="Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] == 1 @property + @setting( + name="LED Brightness", + setter_name="set_led_brightness", + choices=LedBrightness, + ) def led_brightness(self) -> LedBrightness: """Buttons illumination Brightness level.""" try: @@ -110,26 +125,31 @@ def led_brightness(self) -> LedBrightness: return brightness @property + @setting(name="LED", setter_name="set_led") def led(self) -> bool: """True if LED is turned on.""" return self.led_brightness is not LedBrightness.Off @property + @setting(name="Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] == 1 @property + @sensor(name="No Water") def no_water(self) -> bool: """True if the water tank is empty.""" return self.data["no_water"] == 1 @property + @sensor(name="Lid Opened") def lid_opened(self) -> bool: """True if the water tank is detached.""" return self.data["lid_opened"] == 1 @property + @sensor(name="Use Time", unit="s") def use_time(self) -> int | None: """How long the device has been active in seconds. diff --git a/miio/integrations/tinymu/toiletlid/toiletlid.py b/miio/integrations/tinymu/toiletlid/toiletlid.py index 2ebf0fb67..f47dc7246 100644 --- a/miio/integrations/tinymu/toiletlid/toiletlid.py +++ b/miio/integrations/tinymu/toiletlid/toiletlid.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -41,30 +42,43 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Work State") def work_state(self) -> int: """Device state code.""" return self.data["work_state"] @property + @sensor("Work Mode") def work_mode(self) -> ToiletlidOperatingMode: """Device working mode.""" return ToiletlidOperatingMode((self.work_state - 1) // 16) @property + @sensor("Power") def is_on(self) -> bool: return self.work_state != 1 @property + @sensor("Filter Life Remaining") def filter_use_percentage(self) -> str: """Filter percentage of remaining life.""" return "{}%".format(self.data["filter_use_flux"]) @property + @sensor( + "Filter Remaining Time", + unit="d", + ) def filter_remaining_time(self) -> int: """Filter remaining life days.""" return self.data["filter_use_time"] @property + @setting( + "Ambient Light", + setter_name="set_ambient_light", + choices=AmbientLightColor, + ) def ambient_light(self) -> str: """Ambient light color.""" return self.data["ambient_light"] diff --git a/miio/integrations/viomi/viomidishwasher/viomidishwasher.py b/miio/integrations/viomi/viomidishwasher/viomidishwasher.py index ff452d787..55caf6d04 100644 --- a/miio/integrations/viomi/viomidishwasher/viomidishwasher.py +++ b/miio/integrations/viomi/viomidishwasher/viomidishwasher.py @@ -8,6 +8,7 @@ from miio.click_common import EnumType, command, format_output from miio.device import Device, DeviceStatus +from miio.devicestatus import sensor, setting from miio.exceptions import DeviceException _LOGGER = logging.getLogger(__name__) @@ -103,6 +104,7 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @setting(name="Child Lock", setter_name="child_lock") def child_lock(self) -> bool: """Returns the child lock status of the device.""" value = self.data["child_lock"] @@ -112,6 +114,11 @@ def child_lock(self) -> bool: raise DeviceException(f"{value} is not a valid child lock status.") @property + @setting( + name="Program", + setter_name="start", + choices=Program, + ) def program(self) -> Program: """Returns the current selected program of the device.""" program = self.data["program"] @@ -122,12 +129,14 @@ def program(self) -> Program: return Program.Unknown @property + @sensor(name="Door Open") def door_open(self) -> bool: """Returns True if the door is open.""" return bool(self.data["run_status"] & (1 << 7)) @property + @sensor(name="System Status Raw") def system_status_raw(self) -> int: """Returns the raw status number of the device. @@ -139,12 +148,14 @@ def system_status_raw(self) -> int: return self.data["run_status"] @property + @sensor(name="Status") def status(self) -> MachineStatus: """Returns the machine status of the device.""" return MachineStatus(self.data["wash_status"]) @property + @sensor(name="Temperature", unit="C") def temperature(self) -> int: """Returns the temperature in degree Celsius as determined by the NTC thermistor.""" @@ -162,6 +173,7 @@ def power(self) -> bool: raise DeviceException(f"{value} is not a valid power status.") @property + @sensor(name="Time Left") def time_left(self) -> timedelta: """Returns the timedelta in seconds of time left of the current program. @@ -174,6 +186,7 @@ def time_left(self) -> timedelta: raise DeviceException(f"{value} is not a valid integer for time_left.") @property + @sensor(name="Schedule") def schedule(self) -> datetime | None: """Returns a datetime when the scheduled program should be finished. @@ -189,6 +202,7 @@ def schedule(self) -> datetime | None: ) @property + @setting(name="Air Refresh Interval", setter_name="airrefresh") def air_refresh_interval(self) -> int: """Returns an integer on how often the air in the device should be refreshed. @@ -203,6 +217,7 @@ def air_refresh_interval(self) -> int: raise DeviceException(f"{value} is not a valid integer for freshdry_interval.") @property + @sensor(name="Program Progress") def program_progress(self) -> ProgramStatus: """Returns the program status of the running program.""" value = self.data["wash_process"] @@ -213,6 +228,7 @@ def program_progress(self) -> ProgramStatus: return ProgramStatus.Unknown @property + @sensor(name="Errors") def errors(self) -> list[SystemStatus]: """Returns list of errors if detected in the system.""" diff --git a/miio/integrations/xiaomi/aircondition/airconditioner_miot.py b/miio/integrations/xiaomi/aircondition/airconditioner_miot.py index 5b291cea3..5ee23f16b 100644 --- a/miio/integrations/xiaomi/aircondition/airconditioner_miot.py +++ b/miio/integrations/xiaomi/aircondition/airconditioner_miot.py @@ -7,6 +7,7 @@ from miio import DeviceStatus, MiotDevice from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -80,14 +81,17 @@ def __init__(self, status: str): self.status = [int(value) for value in status.split(",")] @property + @sensor("Cleaning") def cleaning(self) -> bool: return bool(self.status[0]) @property + @sensor("Progress", unit="%") def progress(self) -> int: return int(self.status[1]) @property + @sensor("Stage") def stage(self) -> str: try: return CLEANING_STAGES[self.status[2]] @@ -95,6 +99,7 @@ def stage(self) -> str: return "Unknown stage" @property + @sensor("Cancellable") def cancellable(self) -> bool: return bool(self.status[3]) @@ -140,18 +145,22 @@ def __init__(self, status): self.status = [int(value) for value in status.split(",")] @property + @sensor("Enabled") def enabled(self) -> bool: return bool(self.status[0]) @property + @sensor("Countdown") def countdown(self) -> timedelta: return timedelta(minutes=self.status[1]) @property + @sensor("Power On") def power_on(self) -> bool: return bool(self.status[2]) @property + @sensor("Time Left") def time_left(self) -> timedelta: return timedelta(minutes=self.status[3]) @@ -186,91 +195,127 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Is On") def is_on(self) -> bool: """True if the device is turned on.""" return self.data["power"] @property + @sensor("Power") def power(self) -> str: """Current power state.""" return "on" if self.is_on else "off" @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["mode"]) @property + @setting( + "Target Temperature", + setter_name="set_target_temperature", + unit="°C", + min_value=16, + max_value=31, + step=1, + ) def target_temperature(self) -> float: """Target temperature in Celsius.""" return self.data["target_temperature"] @property + @setting("ECO", setter_name="set_eco") def eco(self) -> bool: """True if ECO mode is on.""" return self.data["eco"] @property + @setting("Heater", setter_name="set_heater") def heater(self) -> bool: """True if aux heat mode is on.""" return self.data["heater"] @property + @setting("Dryer", setter_name="set_dryer") def dryer(self) -> bool: """True if aux dryer mode is on.""" return self.data["dryer"] @property + @setting("Sleep Mode", setter_name="set_sleep_mode") def sleep_mode(self) -> bool: """True if sleep mode is on.""" return self.data["sleep_mode"] @property + @setting("Fan Speed", setter_name="set_fan_speed", choices=FanSpeed) def fan_speed(self) -> FanSpeed: """Current Fan speed.""" return FanSpeed(self.data["fan_speed"]) @property + @setting("Vertical Swing", setter_name="set_vertical_swing") def vertical_swing(self) -> bool: """True if vertical swing is on.""" return self.data["vertical_swing"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> float: """Current ambient temperature in Celsius.""" return self.data["temperature"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is on.""" return self.data["buzzer"] @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """True if LED is on.""" return self.data["led"] @property + @sensor("Electricity", unit="kWh") def electricity(self) -> float: """Power consumption accumulation in kWh.""" return self.data["electricity"] @property + @sensor("Clean") def clean(self) -> CleaningStatus: """Auto clean mode indicator.""" return CleaningStatus(self.data["clean"]) @property + @sensor("Total Running Duration") def total_running_duration(self) -> timedelta: """Total running duration in hours.""" return timedelta(hours=self.data["running_duration"]) @property + @setting( + "Fan Speed Percent", + setter_name="set_fan_speed_percent", + unit="%", + min_value=1, + max_value=101, + step=1, + ) def fan_speed_percent(self) -> int: """Current fan speed in percent.""" return self.data["fan_speed_percent"] @property + @sensor("Timer") def timer(self) -> TimerStatus: """Countdown timer indicator.""" return TimerStatus(self.data["timer"]) diff --git a/miio/integrations/xiaomi/repeater/wifirepeater.py b/miio/integrations/xiaomi/repeater/wifirepeater.py index 670b64328..adc586006 100644 --- a/miio/integrations/xiaomi/repeater/wifirepeater.py +++ b/miio/integrations/xiaomi/repeater/wifirepeater.py @@ -4,6 +4,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -25,11 +26,13 @@ def __init__(self, data): self.data = data @property + @sensor("Access Policy") def access_policy(self) -> int: """Access policy of the associated stations.""" return self.data["sta"]["access_policy"] @property + @sensor("Associated Stations") def associated_stations(self) -> dict: """List of associated stations.""" return self.data["mat"] @@ -51,14 +54,17 @@ def __init__(self, data): self.data = data @property + @sensor("SSID") def ssid(self) -> str: return self.data["ssid"] @property + @sensor("Password") def password(self) -> str: return self.data["pwd"] @property + @sensor("SSID Hidden") def ssid_hidden(self) -> bool: return self.data["hidden"] == 1 diff --git a/miio/integrations/xiaomi/wifispeaker/wifispeaker.py b/miio/integrations/xiaomi/wifispeaker/wifispeaker.py index e37d11078..ee3c32bc5 100644 --- a/miio/integrations/xiaomi/wifispeaker/wifispeaker.py +++ b/miio/integrations/xiaomi/wifispeaker/wifispeaker.py @@ -5,6 +5,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -46,46 +47,56 @@ def __init__(self, data): self.data = data @property + @sensor("Device Name") def device_name(self) -> str: """Name of the device.""" return self.data["DeviceName"] @property + @sensor("Channel") def channel(self) -> str: """Name of the channel.""" return self.data["channel_title"] @property + @sensor("State") def state(self) -> PlayState: """State of the device, e.g. PLAYING.""" return PlayState(self.data["current_state"]) @property + @sensor("Hardware Version") def hardware_version(self) -> str: + """Hardware version.""" return self.data["hardware_version"] @property - def play_mode(self): + @sensor("Play Mode") + def play_mode(self) -> str: """Play mode such as REPEAT_ALL.""" # note: this can be enumized when all values are known return self.data["play_mode"] @property + @sensor("Track Artist") def track_artist(self) -> str: """Artist of the current track.""" return self.data["track_artist"] @property + @sensor("Track Title") def track_title(self) -> str: """Title of the current track.""" return self.data["track_title"] @property + @sensor("Track Duration") def track_duration(self) -> str: """Total duration of the current track.""" return self.data["track_duration"] @property + @sensor("Transport Channel") def transport_channel(self) -> TransportChannel: """Transport channel, e.g. PLAYLIST.""" return TransportChannel(self.data["transport_channel"]) diff --git a/miio/integrations/yeelight/dual_switch/yeelight_dual_switch.py b/miio/integrations/yeelight/dual_switch/yeelight_dual_switch.py index d95109b70..07cac2af1 100644 --- a/miio/integrations/yeelight/dual_switch/yeelight_dual_switch.py +++ b/miio/integrations/yeelight/dual_switch/yeelight_dual_switch.py @@ -4,6 +4,7 @@ import click from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting from miio.miot_device import DeviceStatus, MiotDevice, MiotMapping @@ -61,46 +62,75 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Switch 1 State") def switch_1_state(self) -> bool: """First switch state.""" return bool(self.data["switch_1_state"]) @property + @setting( + "Switch 1 Default State", + setter_name="set_default_state", + ) def switch_1_default_state(self) -> bool: """First switch default state.""" return bool(self.data["switch_1_default_state"]) @property + @setting( + "Switch 1 Off Delay", + setter_name="set_switch_off_delay", + unit="s", + min_value=-1, + max_value=43200, + step=1, + ) def switch_1_off_delay(self) -> int: """First switch off delay.""" return self.data["switch_1_off_delay"] @property + @sensor("Switch 2 State") def switch_2_state(self) -> bool: """Second switch state.""" return bool(self.data["switch_2_state"]) @property + @setting( + "Switch 2 Default State", + setter_name="set_default_state", + ) def switch_2_default_state(self) -> bool: """Second switch default state.""" return bool(self.data["switch_2_default_state"]) @property + @setting( + "Switch 2 Off Delay", + setter_name="set_switch_off_delay", + unit="s", + min_value=-1, + max_value=43200, + step=1, + ) def switch_2_off_delay(self) -> int: """Second switch off delay.""" return self.data["switch_2_off_delay"] @property + @setting("Interlock", setter_name="set_interlock") def interlock(self) -> bool: """Interlock.""" return bool(self.data["interlock"]) @property + @setting("Flex Mode", setter_name="set_flex_mode") def flex_mode(self) -> int: """Flex mode.""" return self.data["flex_mode"] @property + @sensor("RC List") def rc_list(self) -> str: """List of paired remote controls.""" return self.data["rc_list"] diff --git a/miio/integrations/yunmi/waterpurifier/waterpurifier.py b/miio/integrations/yunmi/waterpurifier/waterpurifier.py index 26abdf7e7..3782e9d84 100644 --- a/miio/integrations/yunmi/waterpurifier/waterpurifier.py +++ b/miio/integrations/yunmi/waterpurifier/waterpurifier.py @@ -3,6 +3,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -14,78 +15,96 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: return self.power == "on" @property + @sensor("Mode") def mode(self) -> str: """Current operation mode.""" return self.data["mode"] @property + @sensor("TDS", unit="ppm") def tds(self) -> str: return self.data["tds"] @property + @sensor("Filter Life Remaining", unit="%") def filter_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["filter1_life"] @property + @sensor("Filter State") def filter_state(self) -> str: return self.data["filter1_state"] @property + @sensor("Filter 2 Life Remaining", unit="%") def filter2_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["filter_life"] @property + @sensor("Filter 2 State") def filter2_state(self) -> str: return self.data["filter_state"] @property + @sensor("Life") def life(self) -> str: return self.data["life"] @property + @sensor("State") def state(self) -> str: return self.data["state"] @property + @sensor("Level") def level(self) -> str: return self.data["level"] @property + @sensor("Volume") def volume(self) -> str: return self.data["volume"] @property + @sensor("Filter") def filter(self) -> str: return self.data["filter"] @property + @sensor("Usage") def usage(self) -> str: return self.data["usage"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> str: return self.data["temperature"] @property + @sensor("UV Filter Life Remaining", unit="%") def uv_filter_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["uv_life"] @property + @sensor("UV Filter State") def uv_filter_state(self) -> str: return self.data["uv_state"] @property + @sensor("Valve") def valve(self) -> str: return self.data["elecval_state"] diff --git a/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py b/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py index 9ae07e71a..da314eeb8 100644 --- a/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py +++ b/miio/integrations/yunmi/waterpurifier/waterpurifier_yunmi.py @@ -4,6 +4,7 @@ from miio import Device, DeviceStatus from miio.click_common import command, format_output +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -95,6 +96,7 @@ def __init__(self, operation_status: int): ] @property + @sensor("Errors") def errors(self) -> list: return self.err_list @@ -119,121 +121,145 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Operation Status") def operation_status(self) -> OperationStatus: """Current operation status.""" return OperationStatus(self.data["run_status"]) @property + @sensor("Filter 1 Life Total") def filter1_life_total(self) -> timedelta: """Filter1 total available time in hours.""" return timedelta(hours=self.data["f1_totaltime"]) @property + @sensor("Filter 1 Life Used") def filter1_life_used(self) -> timedelta: """Filter1 used time in hours.""" return timedelta(hours=self.data["f1_usedtime"]) @property + @sensor("Filter 1 Life Remaining") def filter1_life_remaining(self) -> timedelta: """Filter1 remaining time in hours.""" return self.filter1_life_total - self.filter1_life_used @property + @sensor("Filter 1 Flow Total", unit="L") def filter1_flow_total(self) -> int: """Filter1 total available flow in Metric Liter (L).""" return self.data["f1_totalflow"] @property + @sensor("Filter 1 Flow Used", unit="L") def filter1_flow_used(self) -> int: """Filter1 used flow in Metric Liter (L).""" return self.data["f1_usedflow"] @property + @sensor("Filter 1 Flow Remaining", unit="L") def filter1_flow_remaining(self) -> int: """Filter1 remaining flow in Metric Liter (L).""" return self.filter1_flow_total - self.filter1_flow_used @property + @sensor("Filter 2 Life Total") def filter2_life_total(self) -> timedelta: """Filter2 total available time in hours.""" return timedelta(hours=self.data["f2_totaltime"]) @property + @sensor("Filter 2 Life Used") def filter2_life_used(self) -> timedelta: """Filter2 used time in hours.""" return timedelta(hours=self.data["f2_usedtime"]) @property + @sensor("Filter 2 Life Remaining") def filter2_life_remaining(self) -> timedelta: """Filter2 remaining time in hours.""" return self.filter2_life_total - self.filter2_life_used @property + @sensor("Filter 2 Flow Total", unit="L") def filter2_flow_total(self) -> int: """Filter2 total available flow in Metric Liter (L).""" return self.data["f2_totalflow"] @property + @sensor("Filter 2 Flow Used", unit="L") def filter2_flow_used(self) -> int: """Filter2 used flow in Metric Liter (L).""" return self.data["f2_usedflow"] @property + @sensor("Filter 2 Flow Remaining", unit="L") def filter2_flow_remaining(self) -> int: """Filter2 remaining flow in Metric Liter (L).""" return self.filter2_flow_total - self.filter2_flow_used @property + @sensor("Filter 3 Life Total") def filter3_life_total(self) -> timedelta: """Filter3 total available time in hours.""" return timedelta(hours=self.data["f3_totaltime"]) @property + @sensor("Filter 3 Life Used") def filter3_life_used(self) -> timedelta: """Filter3 used time in hours.""" return timedelta(hours=self.data["f3_usedtime"]) @property + @sensor("Filter 3 Life Remaining") def filter3_life_remaining(self) -> timedelta: """Filter3 remaining time in hours.""" return self.filter3_life_total - self.filter3_life_used @property + @sensor("Filter 3 Flow Total", unit="L") def filter3_flow_total(self) -> int: """Filter3 total available flow in Metric Liter (L).""" return self.data["f3_totalflow"] @property + @sensor("Filter 3 Flow Used", unit="L") def filter3_flow_used(self) -> int: """Filter3 used flow in Metric Liter (L).""" return self.data["f3_usedflow"] @property + @sensor("Filter 3 Flow Remaining", unit="L") def filter3_flow_remaining(self) -> int: """Filter1 remaining flow in Metric Liter (L).""" return self.filter3_flow_total - self.filter3_flow_used @property + @sensor("TDS In", unit="ppm") def tds_in(self) -> int: """TDS value of input water.""" return self.data["tds_in"] @property + @sensor("TDS Out", unit="ppm") def tds_out(self) -> int: """TDS value of output water.""" return self.data["tds_out"] @property + @sensor("Rinse") def rinse(self) -> bool: """True if the device is rinsing.""" return self.data["rinse"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> int: """Current water temperature in Celsius.""" return self.data["temperature"] @property + @sensor("TDS Warning Threshold", unit="ppm") def tds_warn_thd(self) -> int: """TDS warning threshold.""" return self.data["tds_warn_thd"] diff --git a/miio/integrations/zhimi/airpurifier/airfresh.py b/miio/integrations/zhimi/airpurifier/airfresh.py index 677ddcfc6..409a9a99b 100644 --- a/miio/integrations/zhimi/airpurifier/airfresh.py +++ b/miio/integrations/zhimi/airpurifier/airfresh.py @@ -7,6 +7,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -92,36 +93,43 @@ def __init__(self, data: dict[str, Any], model: str) -> None: self.model = model @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """Return True if device is on.""" return self.power == "on" @property + @sensor("AQI", unit="μg/m³") def aqi(self) -> int: """Air quality index.""" return self.data["aqi"] @property + @sensor("Average AQI", unit="μg/m³") def average_aqi(self) -> int: """Average of the air quality index.""" return self.data["average_aqi"] @property + @sensor("CO2", unit="ppm") def co2(self) -> int: """Carbon dioxide.""" return self.data["co2"] @property + @sensor("Humidity", unit="%") def humidity(self) -> int: """Current humidity.""" return self.data["humidity"] @property + @setting("PTC", setter_name="set_ptc") def ptc(self) -> bool | None: """Return True if PTC is on.""" if self.data["ptc_state"] is not None: @@ -130,6 +138,7 @@ def ptc(self) -> bool | None: return None @property + @sensor("Temperature", unit="°C") def temperature(self) -> float | None: """Current temperature, if available.""" if self.data["temp_dec"] is not None: @@ -141,6 +150,7 @@ def temperature(self) -> float | None: return None @property + @sensor("NTC Temperature", unit="°C") def ntc_temperature(self) -> float | None: """Current ntc temperature, if available.""" if self.data["ntcT"] is not None: @@ -149,16 +159,19 @@ def ntc_temperature(self) -> float | None: return None @property + @setting("Mode", setter_name="set_mode", choices=OperationMode) def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["mode"]) @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """Return True if LED is on.""" return self.data["led"] == "on" @property + @setting("LED Brightness", setter_name="set_led_brightness", choices=LedBrightness) def led_brightness(self) -> LedBrightness | None: """Brightness of the LED.""" if self.data["led_level"] is not None: @@ -173,6 +186,7 @@ def led_brightness(self) -> LedBrightness | None: return None @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool | None: """Return True if buzzer is on.""" if self.data["buzzer"] is not None: @@ -181,31 +195,37 @@ def buzzer(self) -> bool | None: return None @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] == "on" @property + @sensor("Filter Life Remaining", unit="%") def filter_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["filter_life"] @property + @sensor("Filter Hours Used", unit="h") def filter_hours_used(self) -> int: """How long the filter has been in use.""" return self.data["f1_hour_used"] @property + @sensor("Use Time", unit="s") def use_time(self) -> int: """How long the device has been active in seconds.""" return self.data["use_time"] @property + @sensor("Motor Speed", unit="rpm") def motor_speed(self) -> int: """Speed of the motor.""" return self.data["motor1_speed"] @property + @setting("Extra Features", setter_name="set_extra_features") def extra_features(self) -> int | None: return self.data["app_extra"] diff --git a/miio/integrations/zhimi/airpurifier/airpurifier.py b/miio/integrations/zhimi/airpurifier/airpurifier.py index 286e258f6..d6c8c6767 100644 --- a/miio/integrations/zhimi/airpurifier/airpurifier.py +++ b/miio/integrations/zhimi/airpurifier/airpurifier.py @@ -7,6 +7,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting from .airfilter_util import FilterType, FilterTypeUtil @@ -130,31 +131,37 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """Return True if device is on.""" return self.power == "on" @property + @sensor("AQI", unit="μg/m³") def aqi(self) -> int: """Air quality index.""" return self.data["aqi"] @property + @sensor("Average AQI", unit="μg/m³") def average_aqi(self) -> int: """Average of the air quality index.""" return self.data["average_aqi"] @property + @sensor("Humidity", unit="%") def humidity(self) -> int: """Current humidity.""" return self.data["humidity"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> float | None: """Current temperature, if available.""" if self.data["temp_dec"] is not None: @@ -163,11 +170,13 @@ def temperature(self) -> float | None: return None @property + @setting("Mode", setter_name="set_mode", choices=OperationMode) def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["mode"]) @property + @sensor("Sleep Mode") def sleep_mode(self) -> SleepMode | None: """Operation mode of the sleep state. @@ -179,11 +188,13 @@ def sleep_mode(self) -> SleepMode | None: return None @property + @setting("LED", setter_name="set_led") def led(self) -> bool: """Return True if LED is on.""" return self.data["led"] == "on" @property + @setting("LED Brightness", setter_name="set_led_brightness", choices=LedBrightness) def led_brightness(self) -> LedBrightness | None: """Brightness of the LED.""" if self.data["led_b"] is not None: @@ -195,6 +206,7 @@ def led_brightness(self) -> LedBrightness | None: return None @property + @sensor("Illuminance", unit="lx") def illuminance(self) -> int | None: """Environment illuminance level in lux [0-200]. @@ -203,6 +215,7 @@ def illuminance(self) -> int | None: return self.data["bright"] @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool | None: """Return True if buzzer is on.""" if self.data["buzzer"] is not None: @@ -211,62 +224,82 @@ def buzzer(self) -> bool | None: return None @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] == "on" @property + @setting( + "Favorite Level", + setter_name="set_favorite_level", + min_value=0, + max_value=17, + step=1, + ) def favorite_level(self) -> int: """Return favorite level, which is used if the mode is ``favorite``.""" # Favorite level used when the mode is `favorite`. return self.data["favorite_level"] @property + @sensor("Filter Life Remaining", unit="%") def filter_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["filter1_life"] @property + @sensor("Filter Hours Used", unit="h") def filter_hours_used(self) -> int: """How long the filter has been in use.""" return self.data["f1_hour_used"] @property + @sensor("Use Time", unit="s") def use_time(self) -> int: """How long the device has been active in seconds.""" return self.data["use_time"] @property + @sensor("Purify Volume", unit="m³") def purify_volume(self) -> int: """The volume of purified air in cubic meter.""" return self.data["purify_volume"] @property + @sensor("Motor Speed", unit="rpm") def motor_speed(self) -> int: """Speed of the motor.""" return self.data["motor1_speed"] @property + @sensor("Motor 2 Speed", unit="rpm") def motor2_speed(self) -> int | None: """Speed of the 2nd motor.""" return self.data["motor2_speed"] @property + @setting( + "Volume", setter_name="set_volume", unit="%", min_value=0, max_value=100, step=1 + ) def volume(self) -> int | None: """Volume of sound notifications [0-100].""" return self.data["volume"] @property + @sensor("Filter RFID Product ID") def filter_rfid_product_id(self) -> str | None: """RFID product ID of installed filter.""" return self.data["rfid_product_id"] @property + @sensor("Filter RFID Tag") def filter_rfid_tag(self) -> str | None: """RFID tag ID of installed filter.""" return self.data["rfid_tag"] @property + @sensor("Filter Type") def filter_type(self) -> FilterType | None: """Type of installed filter.""" return self.filter_type_util.determine_filter_type( @@ -274,23 +307,28 @@ def filter_type(self) -> FilterType | None: ) @property + @setting("Learn Mode", setter_name="set_learn_mode") def learn_mode(self) -> bool: """Return True if Learn Mode is enabled.""" return self.data["act_sleep"] == "single" @property + @sensor("Sleep Time", unit="s") def sleep_time(self) -> int | None: return self.data["sleep_time"] @property + @sensor("Sleep Mode Learn Count") def sleep_mode_learn_count(self) -> int | None: return self.data["sleep_data_num"] @property + @setting("Extra Features", setter_name="set_extra_features") def extra_features(self) -> int | None: return self.data["app_extra"] @property + @sensor("Turbo Mode Supported") def turbo_mode_supported(self) -> bool | None: if self.data["app_extra"] is not None: return self.data["app_extra"] == 1 @@ -298,6 +336,7 @@ def turbo_mode_supported(self) -> bool | None: return None @property + @setting("Auto Detect", setter_name="set_auto_detect") def auto_detect(self) -> bool | None: """Return True if auto detect is enabled.""" if self.data["act_det"] is not None: @@ -306,6 +345,7 @@ def auto_detect(self) -> bool | None: return None @property + @sensor("Button Pressed") def button_pressed(self) -> str | None: """Last pressed button.""" return self.data["button_pressed"] diff --git a/miio/integrations/zhimi/fan/zhimi_miot.py b/miio/integrations/zhimi/fan/zhimi_miot.py index 6aee67559..df7d6248c 100644 --- a/miio/integrations/zhimi/fan/zhimi_miot.py +++ b/miio/integrations/zhimi/fan/zhimi_miot.py @@ -5,6 +5,7 @@ from miio import DeviceException, DeviceStatus, MiotDevice from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting from miio.utils import deprecated @@ -82,16 +83,19 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @setting("Ionizer", setter_name="set_ionizer") def ionizer(self) -> bool: """True if negative ions generation is enabled.""" return self.data["anion"] @property + @sensor("Battery Supported") def battery_supported(self) -> bool: """True if battery is supported.""" return self.data["battery_supported"] @property + @sensor("Buttons Pressed") def buttons_pressed(self) -> str: """What buttons on the fan are pressed now.""" code = self.data["buttons_pressed"] @@ -104,16 +108,25 @@ def buttons_pressed(self) -> str: return "Unknown" @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """True if child lock if on.""" return self.data["child_lock"] @property + @setting( + "Fan Level", + setter_name="set_fan_speed", + min_value=1, + max_value=4, + step=1, + ) def fan_level(self) -> int: """Fan level (1-4).""" return self.data["fan_level"] @@ -125,61 +138,91 @@ def fan_speed(self) -> int: return self.speed @property + @setting( + "Speed", + setter_name="set_speed", + min_value=1, + max_value=100, + step=1, + unit="%", + ) def speed(self) -> int: """Fan speed (1-100).""" return self.data["fan_speed"] @property + @sensor("Humidity", unit="%") def humidity(self) -> int: """Air humidity in percent.""" return self.data["humidity"] @property + @setting( + "LED Brightness", + setter_name="set_led_brightness", + min_value=0, + max_value=100, + step=1, + unit="%", + ) def led_brightness(self) -> int: """LED brightness (1-100).""" return self.data["light"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode) def mode(self) -> OperationMode: """Operation mode (normal or nature).""" return OperationMode[OperationModeFanZA5(self.data["mode"]).name] @property + @sensor("Power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting( + "Delay Off Countdown", + setter_name="delay_off", + unit="s", + ) def delay_off_countdown(self) -> int: """Countdown until turning off in minutes.""" return self.data["power_off_time"] @property + @sensor("Power Supply Attached") def powersupply_attached(self) -> bool: """True is power supply is attached.""" return self.data["powersupply_attached"] @property + @sensor("Speed RPM", unit="rpm") def speed_rpm(self) -> int: """Fan rotations per minute.""" return self.data["speed_rpm"] @property + @setting("Oscillate", setter_name="set_oscillate") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["swing_mode"] @property + @setting("Angle", setter_name="set_angle") def angle(self) -> int: """Oscillation angle.""" return self.data["swing_mode_angle"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> Any: """Air temperature (degree celsius).""" return self.data["temperature"] diff --git a/miio/integrations/zhimi/heater/heater.py b/miio/integrations/zhimi/heater/heater.py index 242875ffc..30b9fc4a9 100644 --- a/miio/integrations/zhimi/heater/heater.py +++ b/miio/integrations/zhimi/heater/heater.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) @@ -57,16 +58,19 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently on.""" return self.power == "on" @property + @sensor("Humidity", unit="%") def humidity(self) -> int | None: """Current humidity.""" if ( @@ -78,36 +82,53 @@ def humidity(self) -> int | None: return None @property + @sensor("Temperature", unit="°C") def temperature(self) -> float: """Current temperature.""" return self.data["temperature"] @property + @setting( + "Target Temperature", + unit="°C", + setter_name="set_target_temperature", + min_value=16, + max_value=32, + ) def target_temperature(self) -> int: """Target temperature.""" return self.data["target_temperature"] @property + @setting( + "Brightness", + setter_name="set_brightness", + choices=Brightness, + ) def brightness(self) -> Brightness: """Display brightness.""" return Brightness(self.data["brightness"]) @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] in ["on", 1, 2] @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """True if child lock is on.""" return self.data["child_lock"] == "on" @property + @sensor("Use Time", unit="s") def use_time(self) -> int: """How long the device has been active in seconds.""" return self.data["use_time"] @property + @sensor("Delay Off Countdown", unit="s") def delay_off_countdown(self) -> int | None: """Countdown until turning off in seconds.""" if "poweroff_time" in self.data and self.data["poweroff_time"] is not None: diff --git a/miio/integrations/zhimi/heater/heater_miot.py b/miio/integrations/zhimi/heater/heater_miot.py index d30b8ea29..e9b2ea089 100644 --- a/miio/integrations/zhimi/heater/heater_miot.py +++ b/miio/integrations/zhimi/heater/heater_miot.py @@ -6,6 +6,7 @@ from miio import DeviceStatus, MiotDevice from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor, setting _LOGGER = logging.getLogger(__name__) _MAPPINGS = { @@ -121,46 +122,65 @@ def __init__(self, data: dict[str, Any], model: str) -> None: self.model = model @property + @sensor("Power") def power(self) -> str: """Power state.""" return "on" if self.is_on else "off" @property + @sensor("Is On") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting( + "Target Temperature", + unit="°C", + setter_name="set_target_temperature", + min_value=18, + max_value=28, + ) def target_temperature(self) -> int: """Target temperature.""" return self.data["target_temperature"] @property + @sensor("Delay Off Countdown", unit="s") def delay_off_countdown(self) -> int: """Countdown until turning off in seconds.""" return self.data["countdown_time"] @property + @sensor("Temperature", unit="°C") def temperature(self) -> float: """Current temperature.""" return self.data["temperature"] @property + @sensor("Relative Humidity", unit="%") def relative_humidity(self) -> int | None: """Current relative humidity.""" return self.data.get("relative_humidity") @property + @setting("Child Lock", setter_name="set_child_lock") def child_lock(self) -> bool: """True if child lock is on, False otherwise.""" return self.data["child_lock"] is True @property + @setting("Buzzer", setter_name="set_buzzer") def buzzer(self) -> bool: """True if buzzer is turned on, False otherwise.""" return self.data["buzzer"] is True @property + @setting( + "LED Brightness", + setter_name="set_led_brightness", + choices=LedBrightness, + ) def led_brightness(self) -> LedBrightness: """LED indicator brightness.""" value = self.data["led_brightness"] diff --git a/miio/integrations/zimi/clock/alarmclock.py b/miio/integrations/zimi/clock/alarmclock.py index ac12ce7c3..d82d50cc1 100644 --- a/miio/integrations/zimi/clock/alarmclock.py +++ b/miio/integrations/zimi/clock/alarmclock.py @@ -5,6 +5,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command +from miio.devicestatus import sensor class HourlySystem(enum.Enum): @@ -36,15 +37,18 @@ def __init__(self, data): self._end = data[2] @property + @sensor("Enabled") def enabled(self) -> bool: return self._enabled @property - def start(self): + @sensor("Start Time") + def start(self) -> str: return self._start @property - def end(self): + @sensor("End Time") + def end(self) -> str: return self._end