diff --git a/miio/integrations/airdog/airpurifier/airpurifier_airdog.py b/miio/integrations/airdog/airpurifier/airpurifier_airdog.py index 6d8ca06ba..70eeea6ce 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,24 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if device is turned on.""" return self.power == "on" @property + @setting( + "Mode", + setter_name="set_mode_and_speed", + icon="mdi:fan", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode. @@ -66,26 +75,38 @@ def mode(self) -> OperationMode: return OperationMode(self.data["mode"]) @property + @setting( + "Speed", + setter_name="set_mode_and_speed", + icon="mdi:speedometer", + 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", icon="mdi:lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["lock"] == "lock" @property + @sensor("Clean Filters", icon="mdi:air-filter") 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³", icon="mdi:blur", device_class="pm25") def pm25(self) -> int: """Return particulate matter value (0...300μg/m³).""" return self.data["pm"] @property + @sensor("Formaldehyde", icon="mdi:chemical-weapon") def hcho(self) -> Optional[int]: """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 358f987b3..0dffc89be 100644 --- a/miio/integrations/cgllc/airmonitor/airqualitymonitor.py +++ b/miio/integrations/cgllc/airmonitor/airqualitymonitor.py @@ -6,6 +6,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__) @@ -58,16 +59,19 @@ def __init__(self, data): self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> Optional[str]: """Current power state.""" return self.data.get("power") @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """Return True if the device is turned on.""" return self.power == "on" @property + @sensor("USB Power", icon="mdi:usb") def usb_power(self) -> Optional[bool]: """Return True if the device's usb is on.""" if "usb_state" in self.data and self.data["usb_state"] is not None: @@ -75,16 +79,19 @@ def usb_power(self) -> Optional[bool]: return None @property + @sensor("AQI", icon="mdi:air-filter") def aqi(self) -> Optional[int]: """Air quality index value (0..600).""" return self.data.get("aqi") @property + @sensor("Battery", unit="%", device_class="battery", icon="mdi:battery") def battery(self) -> Optional[int]: """Current battery level (0..100).""" return self.data.get("battery") @property + @setting("Display Clock", setter_name="set_display_clock", icon="mdi:clock-outline") def display_clock(self) -> Optional[bool]: """Display a clock instead the AQI.""" if "time_state" in self.data and self.data["time_state"] is not None: @@ -92,6 +99,7 @@ def display_clock(self) -> Optional[bool]: return None @property + @setting("Night Mode", setter_name="set_night_mode", icon="mdi:weather-night") def night_mode(self) -> Optional[bool]: """Return True if the night mode is on.""" if "night_state" in self.data and self.data["night_state"] is not None: @@ -99,46 +107,67 @@ def night_mode(self) -> Optional[bool]: return None @property + @sensor("Night Time Begin", icon="mdi:clock-start") def night_time_begin(self) -> Optional[str]: """Return the begin of the night time.""" return self.data.get("night_beg_time") @property + @sensor("Night Time End", icon="mdi:clock-end") def night_time_end(self) -> Optional[str]: """Return the end of the night time.""" return self.data.get("night_end_time") @property + @sensor("Sensor State", icon="mdi:eye") def sensor_state(self) -> Optional[str]: """Sensor state.""" return self.data.get("sensor_state") @property + @sensor( + "CO2", + unit="ppm", + device_class="carbon_dioxide", + icon="mdi:molecule-co2", + ) def co2(self) -> Optional[int]: """Return co2 value (400...9999ppm).""" return self.data.get("co2") @property + @sensor( + "CO2e", + unit="ppm", + device_class="carbon_dioxide", + icon="mdi:molecule-co2", + ) def co2e(self) -> Optional[int]: """Return co2e value (400...9999ppm).""" return self.data.get("co2e") @property + @sensor("Humidity", unit="%", device_class="humidity", icon="mdi:water-percent") def humidity(self) -> Optional[float]: """Return humidity value (0...100%).""" return self.data.get("humidity") @property + @sensor("PM2.5", unit="μg/m³", device_class="pm25", icon="mdi:blur") def pm25(self) -> Optional[float]: """Return pm2.5 value (0...999μg/m³).""" return self.data.get("pm25") @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> Optional[float]: """Return temperature value (-10...50°C).""" return self.data.get("temperature") @property + @sensor("TVOC", icon="mdi:cloud") def tvoc(self) -> Optional[int]: """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 a405f9a46..fccfaa4fb 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,96 @@ def __init__(self, data): self.data = data @property + @sensor("Humidity", unit="%", icon="mdi:water-percent", device_class="humidity") def humidity(self) -> int: """Return humidity value (0...100%).""" return self.data["humidity"] @property + @sensor("PM2.5", unit="µg/m³", icon="mdi:blur") def pm25(self) -> int: """Return PM 2.5 value (0...1000ppm).""" return self.data["pm25"] @property + @sensor("PM10", unit="µg/m³", icon="mdi:blur-linear") def pm10(self) -> int: """Return PM 10 value (0...1000ppm).""" return self.data["pm10"] @property + @sensor( + "Temperature", unit="°C", icon="mdi:thermometer", device_class="temperature" + ) def temperature(self) -> float: """Return temperature value (-30...100°C).""" return self.data["temperature"] @property + @sensor("CO2", unit="ppm", icon="mdi:molecule-co2", device_class="carbon_dioxide") def co2(self) -> int: """Return co2 value (0...9999ppm).""" return self.data["co2"] @property + @sensor("Battery", unit="%", icon="mdi:battery", device_class="battery") def battery(self) -> int: """Return battery level (0...100%).""" return self.data["battery"] @property + @sensor("Charging State", icon="mdi:battery-charging") 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, + icon="mdi:update", + ) 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, + icon="mdi:monitor-off", + ) 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, + icon="mdi:power-off", + ) 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, + icon="mdi:temperature-celsius", + ) + 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 b53867209..3a1e70d09 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, setting _LOGGER = logging.getLogger(__name__) @@ -91,66 +92,83 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @setting(name="Power", setter_name="on", icon="mdi:power") def power(self) -> bool: """Camera power.""" return self.data["power"] == "on" @property + @setting( + name="Motion Record", setter_name="motion_record_on", icon="mdi:motion-sensor" + ) def motion_record(self) -> bool: """Motion record status.""" return self.data["motion_record"] == "on" @property + @setting(name="Light", setter_name="light_on", icon="mdi:lightbulb") def light(self) -> bool: """Camera light status.""" return self.data["light"] == "on" @property + @setting(name="Full Color", setter_name="full_color_on", icon="mdi:palette") def full_color(self) -> bool: """Full color with bad lighting conditions.""" return self.data["full_color"] == "on" @property + @setting(name="Flip", setter_name="flip_on", icon="mdi:flip-vertical") def flip(self) -> bool: """Image 180 degrees flip status.""" return self.data["flip"] == "on" @property + @setting( + name="Improve Program", setter_name="improve_program_on", icon="mdi:chart-line" + ) def improve_program(self) -> bool: """Customer experience improvement program status.""" return self.data["improve_program"] == "on" @property + @setting(name="WDR", setter_name="wdr_on", icon="mdi:contrast-box") def wdr(self) -> bool: """Wide dynamic range status.""" return self.data["wdr"] == "on" @property + @sensor(name="Track", icon="mdi:crosshairs-gps") def track(self) -> bool: """Tracking status.""" return self.data["track"] == "on" @property + @setting(name="Watermark", setter_name="watermark_on", icon="mdi:watermark") def watermark(self) -> bool: """Apply watermark to video.""" return self.data["watermark"] == "on" @property + @sensor(name="SD Card Status", icon="mdi:sd") def sdcard_status(self) -> int: """SD card status.""" return self.data["sdcard_status"] @property + @sensor(name="Max Client", icon="mdi:account-multiple") def max_client(self) -> int: """Unknown.""" return self.data["max_client"] @property + @sensor(name="Night Mode", icon="mdi:weather-night") def night_mode(self) -> int: """Night mode.""" return self.data["night_mode"] @property + @sensor(name="Mini Level", icon="mdi:volume-low") 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 f60e616aa..2dc04ceac 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", icon="mdi:power-plug") def power(self) -> bool: """Current power state.""" if "on" in self.data: @@ -55,15 +57,20 @@ def power(self) -> bool: raise DeviceException("There was neither 'on' or 'power' in data") @property + @setting("Power", setter_name="on", icon="mdi:power-plug") def is_on(self) -> bool: """True if device is on.""" return self.power @property + @sensor( + "Temperature", unit="°C", icon="mdi:thermometer", device_class="temperature" + ) def temperature(self) -> int: return self.data["temperature"] @property + @setting("USB Power", setter_name="usb_on", icon="mdi:usb") def usb_power(self) -> Optional[bool]: """True if USB is on.""" if "usb_on" in self.data and self.data["usb_on"] is not None: @@ -71,6 +78,7 @@ def usb_power(self) -> Optional[bool]: return None @property + @sensor("Load Power", unit="W", icon="mdi:flash", device_class="power") def load_power(self) -> Optional[float]: """Current power load, if available.""" if "load_power" in self.data and self.data["load_power"] is not None: @@ -84,6 +92,7 @@ def wifi_led(self) -> Optional[bool]: return self.led @property + @setting("LED", setter_name="set_led", icon="mdi:led-on") def led(self) -> Optional[bool]: """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 3470b5916..911eae448 100644 --- a/miio/integrations/chunmi/cooker/cooker.py +++ b/miio/integrations/chunmi/cooker/cooker.py @@ -9,6 +9,7 @@ from miio.click_common import command, format_output from miio.device import Device, DeviceStatus +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -126,10 +127,12 @@ def __init__(self, data: str): self.data = [] @property + @sensor("Temperatures", unit="°C", icon="mdi:thermometer") def temperatures(self) -> list[int]: return self.data @property + @sensor("Raw", icon="mdi:code-string") def raw(self) -> str: return "".join([f"{value:02x}" for value in self.data]) @@ -165,26 +168,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", icon="mdi:clock-outline") def jingzhu_appointment(self) -> time: return time(hour=self.custom[0], minute=self.custom[1]) @property + @sensor("Kuaizhu Appointment", icon="mdi:clock-outline") def kuaizhu_appointment(self) -> time: return time(hour=self.custom[2], minute=self.custom[3]) @property + @sensor("Zhuzhou Appointment", icon="mdi:clock-outline") def zhuzhou_appointment(self) -> time: return time(hour=self.custom[4], minute=self.custom[5]) @property + @sensor("Zhuzhou Cooking", icon="mdi:clock-outline") def zhuzhou_cooking(self) -> time: return time(hour=self.custom[6], minute=self.custom[7]) @property + @sensor("Favorite Appointment", icon="mdi:clock-outline") def favorite_appointment(self) -> time: return time(hour=self.custom[8], minute=self.custom[9]) @property + @sensor("Favorite Cooking", icon="mdi:clock-outline") def favorite_cooking(self) -> time: return time(hour=self.custom[10], minute=self.custom[11]) @@ -208,6 +217,7 @@ def __init__(self, stage: str): self.stage = stage @property + @sensor("State", icon="mdi:pot-steam") def state(self) -> int: """ @@ -218,14 +228,17 @@ def state(self) -> int: return int(self.stage[0:2], 16) @property + @sensor("Rice ID", icon="mdi:rice") def rice_id(self) -> int: return int(self.stage[2:6], 16) @property + @sensor("Taste", icon="mdi:food-variant") def taste(self) -> int: return int(self.stage[6:8], 16) @property + @sensor("Taste Phase", icon="mdi:food-variant") def taste_phase(self) -> int: phase = int(self.taste / 33) @@ -234,6 +247,7 @@ def taste_phase(self) -> int: return phase @property + @sensor("Name", icon="mdi:label") def name(self) -> str: try: return COOKING_STAGES[self.state]["name"] @@ -241,6 +255,7 @@ def name(self) -> str: return "Unknown stage" @property + @sensor("Description", icon="mdi:text") def description(self) -> str: try: return COOKING_STAGES[self.state]["description"] @@ -248,6 +263,7 @@ def description(self) -> str: return "" @property + @sensor("Raw", icon="mdi:code-string") def raw(self) -> str: return self.stage @@ -270,6 +286,7 @@ def __init__(self, timeouts: Optional[str] = None): ] @property + @sensor("LED Off Timeout", unit="s", icon="mdi:led-off") def led_off(self) -> int: return self.timeouts[0] @@ -278,6 +295,7 @@ def led_off(self, delay: int): self.timeouts[0] = delay @property + @sensor("Lid Open Timeout", unit="s", icon="mdi:pot-steam-outline") def lid_open(self) -> int: return self.timeouts[1] @@ -286,6 +304,7 @@ def lid_open(self, timeout: int): self.timeouts[1] = timeout @property + @sensor("Lid Open Warning Timeout", unit="s", icon="mdi:alert") def lid_open_warning(self) -> int: return self.timeouts[2] @@ -325,6 +344,7 @@ def __init__(self, settings: Optional[str] = None): ] @property + @sensor("Pressure Supported", icon="mdi:gauge") def pressure_supported(self) -> bool: return self._settings[0] & 1 != 0 @@ -336,6 +356,7 @@ def pressure_supported(self, supported: bool): self._settings[0] &= 254 @property + @sensor("LED On", icon="mdi:led-on") def led_on(self) -> bool: return self._settings[0] & 2 != 0 @@ -347,6 +368,7 @@ def led_on(self, on: bool): self._settings[0] &= 253 @property + @sensor("Auto Keep Warm", icon="mdi:pot-steam") def auto_keep_warm(self) -> bool: return self._settings[0] & 4 != 0 @@ -358,6 +380,7 @@ def auto_keep_warm(self, keep_warm: bool): self._settings[0] &= 251 @property + @sensor("Lid Open Warning", icon="mdi:alert") def lid_open_warning(self) -> bool: return self._settings[0] & 8 != 0 @@ -369,6 +392,7 @@ def lid_open_warning(self, alarm: bool): self._settings[0] &= 247 @property + @sensor("Lid Open Warning Delayed", icon="mdi:alert-outline") def lid_open_warning_delayed(self) -> bool: return self._settings[0] & 16 != 0 @@ -380,6 +404,7 @@ def lid_open_warning_delayed(self, alarm: bool): self._settings[0] &= 239 @property + @sensor("Jingzhu Auto Keep Warm", icon="mdi:pot-steam") def jingzhu_auto_keep_warm(self) -> bool: return self._settings[1] & 1 != 0 @@ -391,6 +416,7 @@ def jingzhu_auto_keep_warm(self, auto_keep_warm: bool): self._settings[1] &= 254 @property + @sensor("Kuaizhu Auto Keep Warm", icon="mdi:pot-steam") def kuaizhu_auto_keep_warm(self) -> bool: return self._settings[1] & 2 != 0 @@ -402,6 +428,7 @@ def kuaizhu_auto_keep_warm(self, auto_keep_warm: bool): self._settings[1] &= 253 @property + @sensor("Zhuzhou Auto Keep Warm", icon="mdi:pot-steam") def zhuzhou_auto_keep_warm(self) -> bool: return self._settings[1] & 4 != 0 @@ -413,6 +440,7 @@ def zhuzhou_auto_keep_warm(self, auto_keep_warm: bool): self._settings[1] &= 251 @property + @sensor("Favorite Auto Keep Warm", icon="mdi:pot-steam") def favorite_auto_keep_warm(self) -> bool: return self._settings[1] & 8 != 0 @@ -471,16 +499,19 @@ def __init__(self, data): self.data = data @property + @sensor("Mode", icon="mdi:pot-steam") def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["func"]) @property + @sensor("Menu", icon="mdi:book-open-variant") def menu(self) -> int: """Selected recipe id.""" return int(self.data["menu"], 16) @property + @sensor("Stage", icon="mdi:pot-steam") def stage(self) -> Optional[CookingStage]: """Current stage if cooking.""" stage = self.data["stage"] @@ -490,6 +521,9 @@ def stage(self) -> Optional[CookingStage]: return None @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> Optional[int]: """Current temperature, if idle. @@ -502,6 +536,7 @@ def temperature(self) -> Optional[int]: return None @property + @sensor("Start Time", icon="mdi:clock-start") def start_time(self) -> Optional[time]: """Start time of cooking? @@ -515,11 +550,13 @@ def start_time(self) -> Optional[time]: return None @property + @sensor("Remaining", unit="min", icon="mdi:timer-outline") def remaining(self) -> int: """Remaining minutes of the cooking process.""" return int(self.data["t_func"]) @property + @sensor("Cooking Delayed", unit="min", icon="mdi:timer-sand") def cooking_delayed(self) -> Optional[int]: """Wait n minutes before cooking / scheduled cooking.""" delay = int(self.data["t_precook"]) @@ -530,31 +567,37 @@ def cooking_delayed(self) -> Optional[int]: return None @property + @sensor("Duration", unit="min", icon="mdi:timer-outline") def duration(self) -> int: """Duration of the cooking process.""" return int(self.data["t_cook"]) @property + @sensor("Cooker Settings", icon="mdi:cog") def cooker_settings(self) -> CookerSettings: """Settings of the cooker.""" return CookerSettings(self.data["setting"]) @property + @sensor("Interaction Timeouts", icon="mdi:timer-cog-outline") def interaction_timeouts(self) -> InteractionTimeouts: """Interaction timeouts.""" return InteractionTimeouts(self.data["delay"]) @property + @sensor("Hardware Version", icon="mdi:chip") def hardware_version(self) -> int: """Hardware version.""" return int(self.data["version"][0:4], 16) @property + @sensor("Firmware Version", icon="mdi:update") def firmware_version(self) -> int: """Firmware version.""" return int(self.data["version"][4:8], 16) @property + @sensor("Favorite", icon="mdi:star") def favorite(self) -> int: """Favored recipe id. @@ -563,6 +606,7 @@ def favorite(self) -> int: return int(self.data["favorite"], 16) @property + @sensor("Custom", icon="mdi:tune") def custom(self) -> Optional[CookerCustomizations]: custom = self.data["custom"] diff --git a/miio/integrations/deerma/humidifier/airhumidifier_jsqs.py b/miio/integrations/deerma/humidifier/airhumidifier_jsqs.py index 84d4b3837..1be2c00ee 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__) @@ -69,21 +70,30 @@ def __init__(self, data: dict[str, Any]) -> None: # Air Humidifier @property + @setting(name="Power", setter_name="on", icon="mdi:power") def is_on(self) -> bool: """Return True if device is on.""" return self.data["power"] @property + @sensor(name="Power", icon="mdi:power") def power(self) -> str: """Return power state.""" return "on" if self.is_on else "off" @property + @sensor(name="Error", icon="mdi:alert-circle") def error(self) -> int: """Return error state.""" return self.data["fault"] @property + @setting( + name="Mode", + setter_name="set_mode", + icon="mdi:fan", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Return current operation mode.""" @@ -96,6 +106,14 @@ def mode(self) -> OperationMode: return mode @property + @setting( + name="Target Humidity", + setter_name="set_target_humidity", + unit="%", + icon="mdi:water-percent", + min_value=40, + max_value=80, + ) def target_humidity(self) -> Optional[int]: """Return target humidity.""" return self.data.get("target_humidity") @@ -103,11 +121,13 @@ def target_humidity(self) -> Optional[int]: # Environment @property + @sensor(name="Relative Humidity", unit="%", device_class="humidity") def relative_humidity(self) -> Optional[int]: """Return current humidity.""" return self.data.get("relative_humidity") @property + @sensor(name="Temperature", unit="C", device_class="temperature") def temperature(self) -> Optional[float]: """Return current temperature, if available.""" return self.data.get("temperature") @@ -115,6 +135,7 @@ def temperature(self) -> Optional[float]: # Alarm @property + @setting(name="Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> Optional[bool]: """Return True if buzzer is on.""" return self.data.get("buzzer") @@ -122,6 +143,7 @@ def buzzer(self) -> Optional[bool]: # Indicator Light @property + @setting(name="LED Light", setter_name="set_light", icon="mdi:led-outline") def led_light(self) -> Optional[bool]: """Return status of the LED.""" return self.data.get("led_light") @@ -129,16 +151,23 @@ def led_light(self) -> Optional[bool]: # Other @property + @sensor(name="Tank Filed", icon="mdi:cup-water") def tank_filed(self) -> Optional[bool]: """Return the tank filed.""" return self.data.get("tank_filed") @property + @sensor(name="Water Shortage Fault", icon="mdi:water-off") def water_shortage_fault(self) -> Optional[bool]: """Return water shortage fault.""" return self.data.get("water_shortage_fault") @property + @setting( + name="Overwet Protect", + setter_name="set_overwet_protect", + icon="mdi:shield-check", + ) def overwet_protect(self) -> Optional[bool]: """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 5a85bc3d2..82b0e90b2 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,16 +56,24 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["OnOff_State"] == 1 else "off" @property + @setting(name="Power", setter_name="on", icon="mdi:power") def is_on(self) -> bool: """True if device is turned on.""" return self.power == "on" @property + @setting( + name="Mode", + setter_name="set_mode", + icon="mdi:fan", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode. @@ -73,41 +82,58 @@ def mode(self) -> OperationMode: return OperationMode(self.data["Humidifier_Gear"]) @property + @sensor(name="Temperature", unit="C", device_class="temperature") def temperature(self) -> int: """Current temperature in degree celsius.""" return self.data["TemperatureValue"] @property + @sensor(name="Humidity", unit="%", device_class="humidity") def humidity(self) -> int: """Current humidity in percent.""" return self.data["Humidity_Value"] @property + @setting(name="Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["TipSound_State"] == 1 @property + @setting(name="LED", setter_name="set_led", icon="mdi:led-outline") 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="%", + icon="mdi:water-percent", + 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", icon="mdi:water-off") def no_water(self) -> bool: """True if the water tank is empty.""" return self.data["waterstatus"] == 0 @property + @sensor(name="Water Tank Detached", icon="mdi:cup-water") 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", icon="mdi:shield-check" + ) def wet_protection(self) -> Optional[bool]: """True if wet protection is enabled.""" if self.data["wet_and_protect"] is not None: @@ -116,6 +142,7 @@ def wet_protection(self) -> Optional[bool]: return None @property + @sensor(name="Use Time", unit="s", icon="mdi:timer") def use_time(self) -> Optional[int]: """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 00c873cbf..2dcc063a6 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,100 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """Return True if device is on.""" return self.data["power"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode, icon="mdi:fan") def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["mode"]) @property + @sensor("PM2.5", unit="μg/m³", device_class="pm25", icon="mdi:blur") def pm25(self) -> int: """Fine particulate patter (PM2.5).""" return self.data["pm25"] @property + @sensor( + "CO2", + unit="ppm", + device_class="carbon_dioxide", + icon="mdi:molecule-co2", + ) def co2(self) -> int: """Carbon dioxide.""" return self.data["co2"] @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) 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, + icon="mdi:fan", + ) def favorite_speed(self) -> int: """Favorite speed.""" return self.data["favourite_speed"] @property + @sensor("Control Speed", icon="mdi:fan") def control_speed(self) -> int: """Control speed.""" return self.data["control_speed"] @property + @sensor("Dust Filter Life Remaining", unit="%", icon="mdi:filter") def dust_filter_life_remaining(self) -> Optional[int]: """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", icon="mdi:filter") def dust_filter_life_remaining_days(self) -> Optional[int]: """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="%", icon="mdi:filter") def upper_filter_life_remaining(self) -> Optional[int]: """Remaining upper filter life in percent.""" return self.data.get("filter_efficient") @property + @sensor("Upper Filter Life Remaining Days", unit="days", icon="mdi:filter") def upper_filter_life_remaining_days(self) -> Optional[int]: """Remaining upper filter life in days.""" return self.data.get("filter_effi_day") @property + @setting("PTC", setter_name="set_ptc", icon="mdi:radiator") 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, icon="mdi:radiator" + ) def ptc_level(self) -> Optional[PtcLevel]: """PTC level.""" try: @@ -188,26 +218,36 @@ def ptc_level(self) -> Optional[PtcLevel]: return None @property + @sensor("PTC Status", icon="mdi:radiator") 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", icon="mdi: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", icon="mdi:volume-high") def buzzer(self) -> bool: """Return True if sound is on.""" return self.data["sound"] @property + @setting("Display", setter_name="set_display", icon="mdi:monitor") def display(self) -> bool: """Return True if the display is on.""" return self.data["display"] @property + @setting( + "Display Orientation", + setter_name="set_display_orientation", + choices=DisplayOrientation, + icon="mdi:monitor", + ) def display_orientation(self) -> Optional[DisplayOrientation]: """Display orientation.""" try: diff --git a/miio/integrations/dmaker/fan/fan.py b/miio/integrations/dmaker/fan/fan.py index 443796ec9..4027fa012 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,82 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + icon="mdi:fan", + ) 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, + icon="mdi:speedometer", + ) def speed(self) -> int: """Speed of the motor.""" return self.data["speed"] @property + @setting("Oscillate", setter_name="set_oscillate", icon="mdi:sync") 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, + icon="mdi:angle-acute", + ) def angle(self) -> int: """Oscillation angle.""" return self.data["roll_angle"] @property + @sensor( + "Delay Off Countdown", unit="s", icon="mdi:timer-sand", device_class="duration" + ) def delay_off_countdown(self) -> int: """Countdown until turning off in seconds.""" return self.data["time_off"] @property + @setting("LED", setter_name="set_led", icon="mdi:led-on") def led(self) -> bool: """True if LED is turned on, if available.""" return self.data["light"] @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["beep_sound"] @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi: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 33a072ffa..af13bf7c4 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", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode, icon="mdi:fan") def mode(self) -> OperationMode: """Operation mode.""" if self.model == MODEL_FAN_P45: @@ -206,36 +210,52 @@ def mode(self) -> OperationMode: return OperationMode[OperationModeMiot(self.data["mode"]).name] @property + @setting( + "Speed", setter_name="set_speed", min_value=0, max_value=100, icon="mdi:fan" + ) def speed(self) -> int: """Speed of the motor.""" return self.data["fan_speed"] @property + @setting("Oscillate", setter_name="set_oscillate", icon="mdi:arrow-oscillating") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["swing_mode"] @property + @setting("Angle", setter_name="set_angle", icon="mdi:angle-acute") 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, + icon="mdi:timer", + ) 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", icon="mdi:led-on") def led(self) -> bool: """True if LED is turned on, if available.""" return self.data["light"] @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi:lock") def child_lock(self) -> bool: """True if child lock is on.""" return self.data["child_lock"] @@ -265,46 +285,62 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if device is currently on.""" return self.data["power"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode, icon="mdi:fan") 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, icon="mdi:fan") def speed(self) -> int: """Speed of the motor.""" return self.data["fan_level"] @property + @setting("Oscillate", setter_name="set_oscillate", icon="mdi:arrow-oscillating") 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, + icon="mdi:timer", + ) 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", icon="mdi:led-on") def led(self) -> bool: """True if LED is turned on.""" return self.data["light"] @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi: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 de51a55cf..bba90e9ba 100644 --- a/miio/integrations/dreame/vacuum/dreamevacuum_miot.py +++ b/miio/integrations/dreame/vacuum/dreamevacuum_miot.py @@ -8,6 +8,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 @@ -306,34 +307,42 @@ def __init__(self, data, model): self.model = model @property + @sensor("Battery Level", unit="%", device_class="battery", icon="mdi:battery") def battery_level(self) -> str: return self.data["battery_level"] @property + @sensor("Brush Left Time", unit="h", icon="mdi:brush") def brush_left_time(self) -> str: return self.data["brush_left_time"] @property + @sensor("Side Brush Left Time", unit="h", icon="mdi:brush") def brush_left_time2(self) -> str: return self.data["brush_left_time2"] @property + @sensor("Side Brush Life Level", unit="%", icon="mdi:brush") def brush_life_level2(self) -> str: return self.data["brush_life_level2"] @property + @sensor("Brush Life Level", unit="%", icon="mdi:brush") def brush_life_level(self) -> str: return self.data["brush_life_level"] @property + @sensor("Filter Left Time", unit="h", icon="mdi:filter-outline") def filter_left_time(self) -> str: return self.data["filter_left_time"] @property + @sensor("Filter Life Level", unit="%", icon="mdi:filter-outline") def filter_life_level(self) -> str: return self.data["filter_life_level"] @property + @sensor("Device Fault", icon="mdi:alert-circle") def device_fault(self) -> Optional[FaultStatus]: try: return FaultStatus(self.data["device_fault"]) @@ -342,6 +351,7 @@ def device_fault(self) -> Optional[FaultStatus]: return None @property + @sensor("Charging State", icon="mdi:battery-charging") def charging_state(self) -> Optional[ChargingState]: try: return ChargingState(self.data["charging_state"]) @@ -350,6 +360,7 @@ def charging_state(self) -> Optional[ChargingState]: return None @property + @sensor("Operating Mode", icon="mdi:robot-vacuum") def operating_mode(self) -> Optional[OperatingMode]: try: return OperatingMode(self.data["operating_mode"]) @@ -358,6 +369,7 @@ def operating_mode(self) -> Optional[OperatingMode]: return None @property + @sensor("Device Status", icon="mdi:robot-vacuum") def device_status(self) -> Optional[DeviceStatus]: try: return DeviceStatus(self.data["device_status"]) @@ -366,58 +378,80 @@ def device_status(self) -> Optional[DeviceStatus]: return None @property + @sensor("Timer Enable", icon="mdi:timer") def timer_enable(self) -> str: return self.data["timer_enable"] @property + @sensor("Start Time", icon="mdi:clock-start") def start_time(self) -> str: return self.data["start_time"] @property + @sensor("Stop Time", icon="mdi:clock-end") def stop_time(self) -> str: return self.data["stop_time"] @property + @sensor("Map View", icon="mdi:map") 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, + icon="mdi:volume-high", + ) def volume(self) -> str: return self.data["volume"] @property + @sensor("Voice Package", icon="mdi:account-voice") def voice_package(self) -> str: return self.data["voice_package"] @property + @sensor("Timezone", icon="mdi:earth") def timezone(self) -> str: return self.data["timezone"] @property + @sensor("Cleaning Time", unit="min", icon="mdi:timer-outline") def cleaning_time(self) -> str: return self.data["cleaning_time"] @property + @sensor("Cleaning Area", unit="m²", icon="mdi:texture-box") def cleaning_area(self) -> str: return self.data["cleaning_area"] @property + @sensor("First Clean Time", icon="mdi:clock-outline") def first_clean_time(self) -> str: return self.data["first_clean_time"] @property + @sensor("Total Clean Time", unit="min", icon="mdi:timer-outline") def total_clean_time(self) -> str: return self.data["total_clean_time"] @property + @sensor("Total Clean Times", icon="mdi:counter") def total_clean_times(self) -> str: return self.data["total_clean_times"] @property + @sensor("Total Clean Area", unit="m²", icon="mdi:texture-box") def total_clean_area(self) -> str: return self.data["total_clean_area"] @property + @setting("Cleaning Mode", setter_name="set_fan_speed", icon="mdi:fan") def cleaning_mode(self): cleaning_mode = self.data["cleaning_mode"] cleaning_mode_enum_class = _get_cleaning_mode_enum_class(self.model) @@ -432,19 +466,25 @@ def cleaning_mode(self): return None @property + @sensor("Life Sieve", unit="%", icon="mdi:filter-outline") def life_sieve(self) -> Optional[str]: return self.data.get("life_sieve") @property + @sensor("Life Brush Side", unit="%", icon="mdi:brush") def life_brush_side(self) -> Optional[str]: return self.data.get("life_brush_side") @property + @sensor("Life Brush Main", unit="%", icon="mdi:brush") def life_brush_main(self) -> Optional[str]: 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, icon="mdi:water" + ) def water_flow(self) -> Optional[WaterFlow]: try: water_flow = self.data["water_flow"] @@ -457,6 +497,7 @@ def water_flow(self) -> Optional[WaterFlow]: return None @property + @sensor("Water Box Carriage Attached", icon="mdi:cup-water") def is_water_box_carriage_attached(self) -> Optional[bool]: """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 60811e67c..a43902cc2 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,40 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Is On", icon="mdi:power") 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, + icon="mdi:brightness-6", + ) 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, + icon="mdi:thermometer", + device_class="temperature", + ) def color_temp(self) -> int: """Return current color temperature.""" return self.data["color_temp"] @property + @sensor("Fan On", icon="mdi:fan") def is_fan_on(self) -> Optional[bool]: """Return True if Fan is on.""" if "fan_power" in self.data: @@ -139,6 +159,14 @@ def is_fan_on(self) -> Optional[bool]: return None @property + @setting( + "Fan Speed Level", + unit="%", + setter_name="set_fan_level", + min_value=0, + max_value=100, + icon="mdi:fan", + ) def fan_speed_level(self) -> Optional[int]: """Return current Fan speed level.""" if "fan_level" in self.data: @@ -146,6 +174,7 @@ def fan_speed_level(self) -> Optional[int]: return None @property + @sensor("Fan Reverse", icon="mdi:fan-chevron-down") def is_fan_reverse(self) -> Optional[bool]: """Return True if Fan reverse is on.""" if "fan_motor_reverse" in self.data: @@ -153,6 +182,7 @@ def is_fan_reverse(self) -> Optional[bool]: return None @property + @sensor("Fan Mode", icon="mdi:fan-auto") def fan_mode(self) -> Optional[int]: """Return 0 if 'Basic' and 1 if 'Natural wind'.""" if "fan_mode" in self.data: @@ -160,6 +190,7 @@ def fan_mode(self) -> Optional[int]: return None @property + @sensor("Heater On", icon="mdi:radiator") def is_heater_on(self) -> Optional[bool]: """Return True if Heater is on.""" if "heater_power" in self.data: @@ -167,6 +198,7 @@ def is_heater_on(self) -> Optional[bool]: return None @property + @sensor("Heater Fault Code", icon="mdi:alert-circle") def heater_fault_code(self) -> Optional[int]: """Return Heater's fault code. @@ -177,6 +209,13 @@ def heater_fault_code(self) -> Optional[int]: return None @property + @setting( + "Heat Level", + setter_name="set_heat_level", + min_value=1, + max_value=3, + icon="mdi:radiator", + ) def heat_level(self) -> Optional[int]: """Return Heater's heat level.""" if "heat_level" in self.data: diff --git a/miio/integrations/ksmb/walkingpad/walkingpad.py b/miio/integrations/ksmb/walkingpad/walkingpad.py index 290791c69..99cb704fa 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,85 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @sensor("Walking Time", icon="mdi:timer", device_class="duration") 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, + icon="mdi:speedometer", + ) 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, + icon="mdi:speedometer-slow", + ) def start_speed(self) -> float: """Current start speed.""" return self.data["start_speed"] @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + icon="mdi:run", + ) def mode(self) -> OperationMode: """Current mode.""" return OperationMode(self.data["mode"]) @property + @setting( + "Sensitivity", + setter_name="set_sensitivity", + choices=OperationSensitivity, + icon="mdi:tune", + ) def sensitivity(self) -> OperationSensitivity: """Current sensitivity.""" return OperationSensitivity(self.data["sensitivity"]) @property + @sensor("Step Count", icon="mdi:shoe-print") def step_count(self) -> int: """Current steps.""" return int(self.data["step"]) @property + @sensor("Distance", unit="m", icon="mdi:map-marker-distance") def distance(self) -> int: """Current distance in meters.""" return int(self.data["dist"]) @property + @sensor("Calories", unit="cal", icon="mdi:fire") 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 bcbece3fa..eb20e17f4 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,71 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] == 1 else "off" @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if device is turned on.""" return self.data["power"] == 1 @property + @setting( + "Mode", + setter_name="set_mode", + icon="mdi:fan", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode.""" return OperationMode(self.data["mode"]) @property + @setting( + "Speed", + setter_name="set_speed", + icon="mdi:speedometer", + 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", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["sound"] == 1 @property + @setting("Oscillate", setter_name="set_oscillate", icon="mdi:rotate-3d-variant") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["yaw"] == 1 @property + @setting( + "Delay Off Countdown", + setter_name="delay_off", + icon="mdi:timer", + device_class="duration", + 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", icon="mdi:alert-circle") 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 520363f23..837a52957 100644 --- a/miio/integrations/lumi/acpartner/airconditioningcompanion.py +++ b/miio/integrations/lumi/acpartner/airconditioningcompanion.py @@ -6,6 +6,7 @@ from miio import Device, DeviceStatus from miio.click_common import EnumType, command, format_output +from miio.devicestatus import sensor _LOGGER = logging.getLogger(__name__) @@ -98,11 +99,13 @@ def __init__(self, data): self.state = data["model_and_state"][1] @property + @sensor("Load Power", unit="W", device_class="power", icon="mdi:flash") def load_power(self) -> int: """Current power load of the air conditioner.""" return int(self.data["model_and_state"][2]) @property + @sensor("Power Socket", icon="mdi:power-socket") def power_socket(self) -> Optional[str]: """Current socket power state.""" if "power_socket" in self.data and self.data["power_socket"] is not None: @@ -111,21 +114,25 @@ def power_socket(self) -> Optional[str]: return None @property + @sensor("Air Condition Model", icon="mdi:air-conditioner") def air_condition_model(self) -> bytes: """Model of the air conditioner.""" return bytes.fromhex(self.model) @property + @sensor("Model Format", icon="mdi:information-outline") def model_format(self) -> int: """Version number of the model format.""" return self.air_condition_model[0] @property + @sensor("Device Type", icon="mdi:devices") def device_type(self) -> int: """Device type identifier.""" return self.air_condition_model[1] @property + @sensor("Air Condition Brand", icon="mdi:tag") def air_condition_brand(self) -> int: """Brand of the air conditioner. @@ -134,6 +141,7 @@ def air_condition_brand(self) -> int: return int(self.air_condition_model[2:4].hex(), 16) @property + @sensor("Air Condition Remote", icon="mdi:remote") def air_condition_remote(self) -> int: """Remote id. @@ -149,6 +157,7 @@ def air_condition_remote(self) -> int: return int(self.air_condition_model[4:8].hex(), 16) @property + @sensor("State Format", icon="mdi:information-outline") def state_format(self) -> int: """Version number of the state format. @@ -157,15 +166,18 @@ def state_format(self) -> int: return int(self.air_condition_model[8]) @property + @sensor("Air Condition Configuration", icon="mdi:cog") def air_condition_configuration(self) -> int: return self.state[2:10] @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Current power state.""" return "on" if int(self.state[2:3]) == Power.On.value else "off" @property + @sensor("LED", icon="mdi:led-on") def led(self) -> Optional[bool]: """Current LED state.""" state = self.state[8:9] @@ -179,11 +191,18 @@ def led(self) -> Optional[bool]: return None @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @sensor( + "Target Temperature", + unit="°C", + device_class="temperature", + icon="mdi:thermometer", + ) def target_temperature(self) -> Optional[int]: """Target temperature.""" try: @@ -192,6 +211,7 @@ def target_temperature(self) -> Optional[int]: return None @property + @sensor("Swing Mode", icon="mdi:arrow-oscillating") def swing_mode(self) -> Optional[SwingMode]: """Current swing mode.""" try: @@ -201,6 +221,7 @@ def swing_mode(self) -> Optional[SwingMode]: return None @property + @sensor("Fan Speed", icon="mdi:fan") def fan_speed(self) -> Optional[FanSpeed]: """Current fan speed.""" try: @@ -210,6 +231,7 @@ def fan_speed(self) -> Optional[FanSpeed]: return None @property + @sensor("Mode", icon="mdi:air-conditioner") def mode(self) -> Optional[OperationMode]: """Current operation mode.""" try: diff --git a/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py b/miio/integrations/lumi/acpartner/airconditioningcompanionMCN.py index d463502b2..2960dda5b 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,21 +45,30 @@ def __init__(self, data): self.data = data @property + @sensor("Load Power", unit="W", icon="mdi:flash", device_class="power") def load_power(self) -> int: """Current power load of the air conditioner.""" return int(self.data[-1]) @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Current power state.""" return self.data[0] @property + @setting("Power", setter_name="on", icon="mdi:power") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @setting( + "Mode", + setter_name="send_command", + icon="mdi:air-conditioner", + choices=OperationMode, + ) def mode(self) -> Optional[OperationMode]: """Current operation mode.""" try: @@ -68,6 +78,13 @@ def mode(self) -> Optional[OperationMode]: return None @property + @setting( + "Target Temperature", + setter_name="send_command", + unit="°C", + icon="mdi:thermometer", + device_class="temperature", + ) def target_temperature(self) -> Optional[int]: """Target temperature.""" try: @@ -76,6 +93,7 @@ def target_temperature(self) -> Optional[int]: return None @property + @setting("Fan Speed", setter_name="send_command", icon="mdi:fan", choices=FanSpeed) def fan_speed(self) -> Optional[FanSpeed]: """Current fan speed.""" try: @@ -85,6 +103,12 @@ def fan_speed(self) -> Optional[FanSpeed]: return None @property + @setting( + "Swing Mode", + setter_name="send_command", + icon="mdi:arrow-oscillating", + choices=SwingMode, + ) def swing_mode(self) -> Optional[SwingMode]: """Current swing mode.""" try: diff --git a/miio/integrations/lumi/camera/aqaracamera.py b/miio/integrations/lumi/camera/aqaracamera.py index 1a16723b7..d0cc1bdae 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,46 +76,59 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Type", icon="mdi:camera") def type(self) -> str: """TODO: Type of the camera? Name?""" return self.data["app_type"] @property + @sensor("Video Status", icon="mdi:video") def video_status(self) -> bool: """Video state.""" return bool(self.data["video_state"]) @property + @sensor("Is On", icon="mdi:video") def is_on(self) -> bool: """True if device is currently on.""" return self.video_status == 1 @property + @setting("Motion Detection", setter_name="md_on", icon="mdi:motion-sensor") def md(self) -> bool: """Motion detection state.""" return bool(self.data["md_status"]) @property + @setting( + "Motion Detection Sensitivity", + setter_name="md_sensitivity", + icon="mdi:motion-sensor", + ) def md_sensitivity(self): """Motion detection sensitivity.""" return self.data["mdsensitivity"] @property + @setting("IR Mode", setter_name="ir_on", icon="mdi:remote") def ir(self): """IR mode.""" return bool(self.data["ir_status"]) @property + @setting("LED", setter_name="led_on", icon="mdi:led-on") def led(self): """LED status.""" return bool(self.data["led_status"]) @property + @setting("Flipped", setter_name="flip_on", icon="mdi:flip-vertical") def flipped(self) -> bool: """TODO: If camera is flipped?""" return self.data["flip_state"] @property + @sensor("Camera Offsets", icon="mdi:camera-control") def offsets(self) -> CameraOffset: """Camera offset information.""" return CameraOffset( @@ -124,26 +138,31 @@ def offsets(self) -> CameraOffset: ) @property + @sensor("Channel ID", icon="mdi:access-point-network") def channel_id(self) -> int: """TODO: Zigbee channel?""" return self.data["channel_id"] @property + @setting("Fullstop", setter_name="fullstop_on", icon="mdi:alarm-light") def fullstop(self) -> bool: """Is alarm triggered by MD.""" return self.data["fullstop"] != 0 @property + @sensor("P2P ID", icon="mdi:identifier") def p2p_id(self) -> str: """P2P ID for video and audio.""" return self.data["p2p_id"] @property + @sensor("AV ID", icon="mdi:identifier") def av_id(self) -> str: """TODO: What is this? ID for the cloud?""" return self.data["avID"] @property + @sensor("AV Password", icon="mdi:lock") 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 bad091f7d..25742b1a5 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,83 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Status", icon="mdi:curtains") def status(self) -> Status: """Device status.""" return Status(self.data["status"]) @property + @setting( + "Manual Enabled", setter_name="set_manual_enabled", icon="mdi:hand-back-left" + ) 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", + icon="mdi:swap-horizontal", + choices=Polarity, + ) def polarity(self) -> Polarity: """Motor rotation polarity.""" return Polarity(self.data["polarity"]) @property + @setting( + "Position Limited", + setter_name="set_position_limit", + icon="mdi:arrow-collapse-horizontal", + ) 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", icon="mdi:lightbulb-night" + ) def night_tip_light(self) -> bool: """Night tip light status.""" return bool(self.data["night_tip_light"]) @property + @sensor("Run Time", icon="mdi:timer-outline", device_class="duration", unit="s") def run_time(self) -> int: """Run time of the motor.""" return self.data["run_time"] @property + @sensor("Current Position", icon="mdi:curtains", unit="%") def current_position(self) -> int: """Current curtain position.""" return self.data["current_position"] @property + @setting( + "Target Position", + setter_name="set_target_position", + icon="mdi:curtains", + 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", + icon="mdi:tune", + 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 0e6cc6c56..df7bdaf4d 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="%", device_class="battery", icon="mdi:battery") def battery(self) -> int: """Battery Level.""" return self.data["battery"] @property + @sensor("Charge State", icon="mdi:battery-charging") def charge_state(self) -> G1ChargeState: """Charging State.""" return G1ChargeState(self.data["charge_state"]) @property + @sensor("Error Code", icon="mdi:alert-circle") def error_code(self) -> int: """Error code as returned by the device.""" return int(self.data["error_code"]) @property + @sensor("Error", icon="mdi:alert-circle") def error(self) -> str: """Human readable error description, see also :func:`error_code`.""" try: @@ -182,66 +187,81 @@ def error(self) -> str: return "Definition missing for error %s" % self.error_code @property + @sensor("State", icon="mdi:robot-vacuum") def state(self) -> G1State: """Vacuum Status.""" return G1State(self.data["state"]) @property + @setting( + "Fan Speed", setter_name="set_fan_speed", choices=G1FanSpeed, icon="mdi:fan" + ) def fan_speed(self) -> G1FanSpeed: """Fan Speed.""" return G1FanSpeed(self.data["fan_speed"]) @property + @sensor("Operating Mode", icon="mdi:robot-vacuum") def operating_mode(self) -> G1VacuumMode: """Operating Mode.""" return G1VacuumMode(self.data["operating_mode"]) @property + @sensor("Mop State", icon="mdi:robot-vacuum-variant") def mop_state(self) -> G1MopState: """Mop State.""" return G1MopState(self.data["mop_state"]) @property + @sensor("Water Level", icon="mdi:water") def water_level(self) -> G1WaterLevel: """Water Level.""" return G1WaterLevel(self.data["water_level"]) @property + @sensor("Main Brush Life Level", unit="%", icon="mdi:brush") 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", icon="mdi:brush") 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="%", icon="mdi:brush") 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", icon="mdi:brush") 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="%", icon="mdi:filter-outline") def filter_life_level(self) -> int: """Filter Life Level in %.""" return self.data["filter_life_level"] @property + @sensor("Filter Time Left", icon="mdi:filter-outline") def filter_time_left(self) -> timedelta: """Filter remaining time.""" return timedelta(minutes=self.data["filter_time_left"]) @property + @sensor("Clean Area", unit="cm²", icon="mdi:texture-box") def clean_area(self) -> int: """Clean Area in cm2.""" return self.data["clean_area"] @property + @sensor("Clean Time", icon="mdi:timer-outline") def clean_time(self) -> timedelta: """Clean time.""" return timedelta(minutes=self.data["clean_time"]) @@ -263,16 +283,19 @@ def __init__(self, data) -> None: self.data = data @property + @sensor("Total Clean Count", icon="mdi:counter") def total_clean_count(self) -> int: """Total Number of Cleanings.""" return self.data["total_clean_count"] @property + @sensor("Total Clean Area", unit="m²", icon="mdi:texture-box") def total_clean_area(self) -> int: """Total Area Cleaned in m2.""" return self.data["total_clean_area"] @property + @sensor("Total Clean Time", icon="mdi:timer-outline") 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..29c092b9f 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,36 +35,48 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Sponge Filter Left Days", icon="mdi:filter") def sponge_filter_left_days(self) -> timedelta: """Filter life time remaining in days.""" return timedelta(days=self.data["filter_left_time"]) @property + @setting(name="Power", setter_name="on", icon="mdi:power") def is_on(self) -> bool: """True if device is on.""" return self.data["on"] @property + @setting( + name="Mode", + setter_name="set_mode", + icon="mdi:water-pump", + choices=OperatingMode, + ) def mode(self) -> OperatingMode: """OperatingMode.""" return OperatingMode(self.data["mode"]) @property + @setting(name="LED", setter_name="set_led", icon="mdi:led-outline") def is_led_on(self) -> bool: """True if enabled.""" return self.data["indicator_light"] @property + @sensor(name="Cotton Left Days", icon="mdi:filter") 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", icon="mdi:broom") def before_cleaning_days(self) -> timedelta: """Days before cleaning.""" return timedelta(days=self.data["remain_clean_time"]) @property + @sensor(name="No Water", icon="mdi:water-off") def is_no_water(self) -> bool: """True if there is no water left.""" if self.data["no_water_flag"]: @@ -71,31 +84,43 @@ def is_no_water(self) -> bool: return True @property + @sensor(name="No Water Duration", icon="mdi:water-off") def no_water_minutes(self) -> timedelta: """Minutes without water.""" return timedelta(minutes=self.data["no_water_time"]) @property + @sensor(name="Pump Blocked", icon="mdi:water-pump-off") def is_pump_blocked(self) -> bool: """True if pump is blocked.""" return self.data["pump_block_flag"] @property + @sensor(name="Lid Up", icon="mdi:archive-arrow-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", + icon="mdi:map-clock", + 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", icon="mdi:map-marker") def location(self) -> str: """Device location string.""" return self.data["location"] @property + @sensor(name="Error Detected", icon="mdi:alert-circle") 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 d9db0e0ff..cfc3470a4 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,24 @@ def __init__(self, data: dict[str, Any], device_info: DeviceInfo) -> None: self.device_info = device_info @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["on_off"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if device is turned on.""" return self.power == "on" @property + @setting( + "Mode", + setter_name="set_mode", + choices=OperationMode, + icon="mdi:air-humidifier", + ) def mode(self) -> OperationMode: """Operation mode. @@ -81,6 +90,9 @@ def mode(self) -> OperationMode: return OperationMode(self.data["mode"]) @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> Optional[float]: """Current temperature, if available.""" if "temp" in self.data and self.data["temp"] is not None: @@ -88,26 +100,36 @@ def temperature(self) -> Optional[float]: return None @property + @sensor("Humidity", unit="%", device_class="humidity", icon="mdi:water-percent") def humidity(self) -> int: """Current humidity.""" return self.data["humidity"] @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] == "on" @property + @setting("LED", setter_name="set_led", icon="mdi:led-on") def led(self) -> bool: """LED brightness if available.""" return self.data["led"] == "on" @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi:lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] == "on" @property + @setting( + "Target Humidity", + setter_name="set_target_humidity", + unit="%", + icon="mdi:water-percent", + ) def target_humidity(self) -> Optional[int]: """Target humiditiy. @@ -118,6 +140,12 @@ def target_humidity(self) -> Optional[int]: return None @property + @setting( + "Fan Speed", + setter_name="set_fan_speed", + choices=FanSpeed, + icon="mdi:fan", + ) def fan_speed(self) -> Optional[FanSpeed]: """Current fan speed.""" if "fan_speed" in self.data and self.data["fan_speed"] is not None: @@ -125,26 +153,31 @@ def fan_speed(self) -> Optional[FanSpeed]: return None @property + @sensor("Tank Full", icon="mdi:cup-water") def tank_full(self) -> bool: """The remaining amount of water in percent.""" return self.data["tank_full"] == "on" @property + @sensor("Compressor Status", icon="mdi:air-conditioner") def compressor_status(self) -> bool: """Compressor status.""" return self.data["compressor_status"] == "on" @property + @sensor("Defrost Status", icon="mdi:snowflake-melt") def defrost_status(self) -> bool: """Defrost status.""" return self.data["defrost_status"] == "on" @property + @sensor("Fan St", icon="mdi:fan") def fan_st(self) -> int: """Fan st.""" return self.data["fan_st"] @property + @sensor("Alarm", icon="mdi:alarm-light") 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 ed401dab7..6744d8dc0 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,77 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @setting( + "Brightness", + setter_name="set_brightness", + icon="mdi:brightness-6", + 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", + icon="mdi:palette", + 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", + icon="mdi:timer", + device_class="duration", + 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", + icon="mdi:temperature-kelvin", + 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", icon="mdi:weather-night") def smart_night_light(self) -> bool: """Smart night mode state.""" return self.data["bl"] == 1 @property + @sensor("Automatic Color Temperature", icon="mdi:theme-light-dark") 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 66a38f12b..57aa4af82 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,14 +38,19 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: return self.data["power"] @property + @setting("Power", setter_name="on", icon="mdi:power") def is_on(self) -> bool: return self.power == "on" @property + @setting( + "Brightness", setter_name="set_brightness", unit="%", icon="mdi:brightness-6" + ) def brightness(self) -> Optional[int]: if "bright" in self.data: return self.data["bright"] @@ -53,18 +59,28 @@ def brightness(self) -> Optional[int]: return None @property + @setting( + "Color Temperature", setter_name="set_color_temperature", icon="mdi:palette" + ) def color_temperature(self) -> Optional[int]: if "cct" in self.data: return self.data["cct"] return None @property + @setting("Scene", setter_name="set_scene", icon="mdi:palette") def scene(self) -> Optional[int]: if "snm" in self.data: return self.data["snm"] return None @property + @sensor( + "Delay Off Countdown", + unit="s", + icon="mdi:timer-outline", + device_class="duration", + ) 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 1d34ca0e8..45595f6ba 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,51 +21,90 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") 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, + icon="mdi:brightness-6", + ) def brightness(self) -> int: """Current brightness of the primary light.""" return self.data["bright"] @property + @setting("Eye Fatigue Reminder", setter_name="reminder_on", icon="mdi:eye") def reminder(self) -> bool: """Indicates the eye fatigue notification is enabled or not.""" return self.data["notifystatus"] == "on" @property + @setting("Ambient Light", setter_name="ambient_on", icon="mdi:lightbulb-outline") def ambient(self) -> bool: """True if the ambient light (second light source) is on.""" return self.data["ambstatus"] == "on" @property + @setting( + "Ambient Brightness", + unit="%", + setter_name="set_ambient_brightness", + min_value=1, + max_value=100, + icon="mdi:brightness-6", + ) def ambient_brightness(self) -> int: """Brightness of the ambient light.""" return self.data["ambvalue"] @property + @setting("Eyecare Mode", setter_name="eyecare_on", icon="mdi:eye-check") def eyecare(self) -> bool: """True if the eyecare mode is on.""" return self.data["eyecare"] == "on" @property + @setting( + "Scene", + setter_name="set_scene", + min_value=1, + max_value=4, + icon="mdi:palette", + ) def scene(self) -> int: """Current fixed scene.""" return self.data["scene_num"] @property + @setting( + "Smart Night Light", + setter_name="smart_night_light_on", + icon="mdi:weather-night", + ) def smart_night_light(self) -> bool: """True if the smart night light mode is on.""" return self.data["bls"] == "on" @property + @sensor( + "Delay Off Countdown", + unit="min", + icon="mdi:timer-sand", + device_class="duration", + ) 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 d5e90bfcf..8d724cf00 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,31 +24,57 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Power", icon="mdi:power") def power(self) -> str: return self.data["pow"] @property + @setting(name="Power", setter_name="on", icon="mdi:power") def is_on(self) -> bool: return self.power == "on" @property + @setting( + name="Brightness", + setter_name="set_brightness", + unit="%", + icon="mdi:brightness-6", + min_value=1, + max_value=100, + ) def brightness(self) -> int: return self.data["bri"] @property + @setting( + name="Color Temperature", + setter_name="set_color_temperature", + icon="mdi:thermometer", + min_value=1, + max_value=100, + ) def color_temperature(self) -> int: return self.data["cct"] @property + @setting(name="RGB", setter_name="set_rgb", icon="mdi:palette") 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", + icon="mdi:palette-swatch", + min_value=1, + max_value=6, + ) def scene(self) -> int: return self.data["snm"] @property + @sensor(name="Sleep Assistant", icon="mdi:sleep") def sleep_assistant(self) -> int: """Example values: @@ -59,24 +86,29 @@ def sleep_assistant(self) -> int: return self.data["sta"] @property + @sensor(name="Sleep Off Time", unit="s", icon="mdi:timer-off") def sleep_off_time(self) -> int: return self.data["spr"] @property + @sensor(name="Total Assistant Sleep Time", unit="s", icon="mdi:timer") def total_assistant_sleep_time(self) -> int: return self.data["spt"] @property + @sensor(name="Brand Sleep", icon="mdi:sleep") def brand_sleep(self) -> bool: # sp_sleep_open? return self.data["ms"] == 1 @property + @sensor(name="Brand", icon="mdi:watch") def brand(self) -> bool: # sp_xm_bracelet? return self.data["mb"] == 1 @property + @sensor(name="Wake Up Time", icon="mdi:alarm") 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 5d3f5d4ca..86f29d249 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,77 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if the device is turned on.""" return self.power == "on" @property + @setting( + "Brightness", + setter_name="set_brightness", + icon="mdi:brightness-6", + 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", + icon="mdi:timer", + device_class="duration", + 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", + icon="mdi:palette", + 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", icon="mdi:motion-sensor" + ) 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", + icon="mdi:signal-cellular-2", + 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", icon="mdi: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 889cf3556..47b7e28da 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", icon="mdi:electric-switch") def relay_state(self) -> Optional[int]: """Current relay state.""" if "relay_status" in self.data: @@ -77,6 +79,7 @@ def relay_state(self) -> Optional[int]: return None @property + @sensor("Relay Names", icon="mdi:rename-box") 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", icon="mdi:counter") def on_count(self) -> Optional[int]: """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 b99f0bf5c..6cdac0d2a 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="%", device_class="battery", icon="mdi:battery") def battery(self) -> int: """Remaining battery in percentage.""" return self.data["battery_level"] @property + @sensor("Error Code", icon="mdi:alert-circle") def error_code(self) -> int: """Error code as returned by the device.""" return int(self.data["error_code"]) @property + @sensor("Error", icon="mdi:alert-circle") def error(self) -> str: """Human readable error description, see also :func:`error_code`.""" try: @@ -242,6 +246,7 @@ def error(self) -> str: return "Definition missing for error %s" % self.error_code @property + @sensor("Charging State", icon="mdi:battery-charging") 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", icon="mdi:robot-vacuum") 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, icon="mdi:fan") def fan_speed(self) -> FanSpeed: """Current fan speed.""" try: @@ -269,6 +276,12 @@ def fan_speed(self) -> FanSpeed: return FanSpeed.Unknown @property + @setting( + "Sweep Type", + setter_name="set_sweep_type", + choices=SweepType, + icon="mdi:robot-vacuum", + ) def sweep_type(self) -> SweepType: """Current sweep type sweep/mop/sweep&mop.""" try: @@ -278,6 +291,12 @@ def sweep_type(self) -> SweepType: return SweepType.Unknown @property + @setting( + "Path Mode", + setter_name="set_path_mode", + choices=PathMode, + icon="mdi:map-marker-path", + ) def path_mode(self) -> PathMode: """Current path-mode: normal/y-mopping etc.""" try: @@ -287,11 +306,20 @@ def path_mode(self) -> PathMode: return PathMode.Unknown @property + @sensor("Mop Attached", icon="mdi:robot-vacuum-variant") 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, + icon="mdi:delete-variant", + ) def dust_collection_frequency(self) -> int: """Frequency for emptying the dust bin. @@ -300,6 +328,7 @@ def dust_collection_frequency(self) -> int: return self.data["work_station_freq"] @property + @setting("Timing", setter_name="set_timing", icon="mdi:clock-outline") def timing(self) -> str: """Repeated cleaning. @@ -337,6 +366,7 @@ def timing(self) -> str: return self.data["timing"] @property + @setting("Carpet Mode", setter_name="set_carpet_mode", icon="mdi:rug") def carpet_mode(self) -> bool: """Auto boost on carpet.""" return self.data["auto_boost"] @@ -363,11 +393,18 @@ def _seconds_to_components(val): ) @property + @sensor("DND Status", icon="mdi:minus-circle") 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, + icon="mdi:water", + ) def water_level(self) -> WaterLevel: """Get current water level.""" try: @@ -377,32 +414,42 @@ def water_level(self) -> WaterLevel: return WaterLevel.Unknown @property + @setting("Double Clean", setter_name="set_double_clean", icon="mdi:refresh") def double_clean(self) -> bool: """Is double clean enabled.""" return self.data["double_clean"] @property + @setting("LED", setter_name="set_led", icon="mdi:led-on") 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", + icon="mdi:radar", + ) 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", icon="mdi:gesture-tap-button") 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", icon="mdi:led-on") def station_led(self) -> bool: """Return if station display is on.""" return self.data["station_led"] @property + @sensor("Current Audio", icon="mdi:account-voice") def current_audio(self) -> str: """Current voice setting. @@ -411,21 +458,25 @@ def current_audio(self) -> str: return self.data["current_audio"] @property + @sensor("Clean Time", icon="mdi:timer-outline") 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²", icon="mdi:texture-box") def clean_area(self) -> int: """Cleaned area in m2.""" return self.data["clean_area"] @property + @sensor("State Code", icon="mdi:robot-vacuum") def state_code(self) -> int: """State code as returned by the device.""" return int(self.data["state"]) @property + @sensor("State", icon="mdi:robot-vacuum") def state(self) -> RoidmiState: """Human readable state description, see also :func:`state_code`.""" try: @@ -435,26 +486,39 @@ def state(self) -> RoidmiState: return RoidmiState.Unknown @property + @setting( + "Volume", + setter_name="set_sound_volume", + unit="%", + min_value=0, + max_value=100, + step=1, + icon="mdi:volume-high", + ) def volume(self) -> int: """Return device sound volumen level.""" return self.data["volume"] @property + @setting("Muted", setter_name="set_sound_muted", icon="mdi:volume-off") def is_muted(self) -> bool: """True if device is muted.""" return bool(self.data["mute"]) @property + @sensor("Is Paused", icon="mdi:pause-circle") def is_paused(self) -> bool: """Return True if vacuum is paused.""" return self.state in [RoidmiState.Paused, RoidmiState.FindChargerPause] @property + @sensor("Is On", icon="mdi:robot-vacuum") def is_on(self) -> bool: """True if device is currently cleaning in any mode.""" return self.state == RoidmiState.Sweeping @property + @sensor("Got Error", icon="mdi:alert-circle") def got_error(self) -> bool: """True if an error has occurred.""" return self.error_code != 0 @@ -467,16 +531,19 @@ def __init__(self, data) -> None: self.data = data @property + @sensor("Total Duration", icon="mdi:timer-outline") def total_duration(self) -> timedelta: """Total cleaning duration.""" return timedelta(seconds=self.data["total_clean_time_sec"]) @property + @sensor("Total Area", unit="m²", icon="mdi:texture-box") def total_area(self) -> int: """Total cleaned area.""" return self.data["total_clean_areas"] @property + @sensor("Count", icon="mdi:counter") def count(self) -> int: """Number of cleaning runs.""" return self.data["clean_counts"] @@ -500,16 +567,19 @@ def _calcUsageTime( return original_total * (1 - remaning_fraction) @property + @sensor("Filter Usage", icon="mdi:filter-outline") def filter(self) -> timedelta: """Filter usage time.""" return self._calcUsageTime(self.filter_left, self.data["filter_life_level"]) @property + @sensor("Filter Left", icon="mdi:filter-outline") 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", icon="mdi:brush") def main_brush(self) -> timedelta: """Main brush usage time.""" return self._calcUsageTime( @@ -517,11 +587,13 @@ def main_brush(self) -> timedelta: ) @property + @sensor("Main Brush Left", icon="mdi:brush") 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", icon="mdi:brush") def side_brush(self) -> timedelta: """Main brush usage time.""" return self._calcUsageTime( @@ -529,11 +601,13 @@ def side_brush(self) -> timedelta: ) @property + @sensor("Side Brush Left", icon="mdi:brush") 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", icon="mdi:eye-off") def sensor_dirty(self) -> timedelta: """Return time since last sensor clean.""" return self._calcUsageTime( @@ -541,6 +615,7 @@ def sensor_dirty(self) -> timedelta: ) @property + @sensor("Sensor Dirty Left", icon="mdi:eye-off") 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 80ccb5a21..6a4cdd0d0 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,16 +60,24 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor(name="Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] == 1 else "off" @property + @setting(name="Power", setter_name="on", icon="mdi:power") def is_on(self) -> bool: """True if device is turned on.""" return self.power == "on" @property + @setting( + name="Mode", + setter_name="set_mode", + icon="mdi:fan", + choices=OperationMode, + ) def mode(self) -> OperationMode: """Operation mode. @@ -84,21 +93,30 @@ def mode(self) -> OperationMode: return mode @property + @sensor(name="Temperature", unit="C", device_class="temperature") def temperature(self) -> int: """Current temperature in degree celsius.""" return self.data["temperature"] @property + @sensor(name="Humidity", unit="%", device_class="humidity") def humidity(self) -> int: """Current humidity in percent.""" return self.data["humidity"] @property + @setting(name="Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") 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", + icon="mdi:brightness-6", + choices=LedBrightness, + ) def led_brightness(self) -> LedBrightness: """Buttons illumination Brightness level.""" try: @@ -110,26 +128,31 @@ def led_brightness(self) -> LedBrightness: return brightness @property + @setting(name="LED", setter_name="set_led", icon="mdi:led-outline") 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", icon="mdi:lock") def child_lock(self) -> bool: """Return True if child lock is on.""" return self.data["child_lock"] == 1 @property + @sensor(name="No Water", icon="mdi:water-off") def no_water(self) -> bool: """True if the water tank is empty.""" return self.data["no_water"] == 1 @property + @sensor(name="Lid Opened", icon="mdi:cup-water") 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", icon="mdi:timer") def use_time(self) -> Optional[int]: """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 2584feba9..74d94f7cd 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,46 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Work State", icon="mdi:state-machine") def work_state(self) -> int: """Device state code.""" return self.data["work_state"] @property + @sensor("Work Mode", icon="mdi:toilet") def work_mode(self) -> ToiletlidOperatingMode: """Device working mode.""" return ToiletlidOperatingMode((self.work_state - 1) // 16) @property + @sensor("Power", icon="mdi:power") def is_on(self) -> bool: return self.work_state != 1 @property + @sensor("Filter Life Remaining", icon="mdi:filter") 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", + icon="mdi:filter-outline", + device_class="duration", + ) 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", + icon="mdi:lightbulb-variant", + 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 8d28fb1d9..539ca5283 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", icon="mdi:lock") def child_lock(self) -> bool: """Returns the child lock status of the device.""" value = self.data["child_lock"] @@ -112,6 +114,12 @@ def child_lock(self) -> bool: raise DeviceException(f"{value} is not a valid child lock status.") @property + @setting( + name="Program", + setter_name="start", + icon="mdi:dishwasher", + choices=Program, + ) def program(self) -> Program: """Returns the current selected program of the device.""" program = self.data["program"] @@ -122,12 +130,14 @@ def program(self) -> Program: return Program.Unknown @property + @sensor(name="Door Open", icon="mdi: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", icon="mdi:information-outline") def system_status_raw(self) -> int: """Returns the raw status number of the device. @@ -139,12 +149,14 @@ def system_status_raw(self) -> int: return self.data["run_status"] @property + @sensor(name="Status", icon="mdi:dishwasher") def status(self) -> MachineStatus: """Returns the machine status of the device.""" return MachineStatus(self.data["wash_status"]) @property + @sensor(name="Temperature", unit="C", device_class="temperature") def temperature(self) -> int: """Returns the temperature in degree Celsius as determined by the NTC thermistor.""" @@ -152,6 +164,7 @@ def temperature(self) -> int: return self.data["wash_temp"] @property + @setting(name="Power", setter_name="on", icon="mdi:power") def power(self) -> bool: """Returns the power status of the device.""" @@ -162,6 +175,7 @@ def power(self) -> bool: raise DeviceException(f"{value} is not a valid power status.") @property + @sensor(name="Time Left", icon="mdi:timer") def time_left(self) -> timedelta: """Returns the timedelta in seconds of time left of the current program. @@ -174,6 +188,7 @@ def time_left(self) -> timedelta: raise DeviceException(f"{value} is not a valid integer for time_left.") @property + @sensor(name="Schedule", icon="mdi:calendar-clock") def schedule(self) -> Optional[datetime]: """Returns a datetime when the scheduled program should be finished. @@ -189,6 +204,7 @@ def schedule(self) -> Optional[datetime]: ) @property + @setting(name="Air Refresh Interval", setter_name="airrefresh", icon="mdi:fan") def air_refresh_interval(self) -> int: """Returns an integer on how often the air in the device should be refreshed. @@ -203,6 +219,7 @@ def air_refresh_interval(self) -> int: raise DeviceException(f"{value} is not a valid integer for freshdry_interval.") @property + @sensor(name="Program Progress", icon="mdi:progress-check") def program_progress(self) -> ProgramStatus: """Returns the program status of the running program.""" value = self.data["wash_process"] @@ -213,6 +230,7 @@ def program_progress(self) -> ProgramStatus: return ProgramStatus.Unknown @property + @sensor(name="Errors", icon="mdi:alert-circle") 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 6eaf34c42..3666c29e4 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", icon="mdi:broom") def cleaning(self) -> bool: return bool(self.status[0]) @property + @sensor("Progress", unit="%", icon="mdi:progress-check") def progress(self) -> int: return int(self.status[1]) @property + @sensor("Stage", icon="mdi:broom") 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", icon="mdi:cancel") 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", icon="mdi:timer") def enabled(self) -> bool: return bool(self.status[0]) @property + @sensor("Countdown", icon="mdi:timer-outline") def countdown(self) -> timedelta: return timedelta(minutes=self.status[1]) @property + @sensor("Power On", icon="mdi:power") def power_on(self) -> bool: return bool(self.status[2]) @property + @sensor("Time Left", icon="mdi:timer-outline") def time_left(self) -> timedelta: return timedelta(minutes=self.status[3]) @@ -186,91 +195,135 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if the device is turned on.""" return self.data["power"] @property + @sensor("Power", icon="mdi: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, + icon="mdi:air-conditioner", + ) 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, + device_class="temperature", + icon="mdi:thermometer", + ) def target_temperature(self) -> float: """Target temperature in Celsius.""" return self.data["target_temperature"] @property + @setting("ECO", setter_name="set_eco", icon="mdi:leaf") def eco(self) -> bool: """True if ECO mode is on.""" return self.data["eco"] @property + @setting("Heater", setter_name="set_heater", icon="mdi:radiator") def heater(self) -> bool: """True if aux heat mode is on.""" return self.data["heater"] @property + @setting("Dryer", setter_name="set_dryer", icon="mdi:water-off") def dryer(self) -> bool: """True if aux dryer mode is on.""" return self.data["dryer"] @property + @setting("Sleep Mode", setter_name="set_sleep_mode", icon="mdi:sleep") 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, icon="mdi:fan") def fan_speed(self) -> FanSpeed: """Current Fan speed.""" return FanSpeed(self.data["fan_speed"]) @property + @setting( + "Vertical Swing", setter_name="set_vertical_swing", icon="mdi:arrow-up-down" + ) def vertical_swing(self) -> bool: """True if vertical swing is on.""" return self.data["vertical_swing"] @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> float: """Current ambient temperature in Celsius.""" return self.data["temperature"] @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is on.""" return self.data["buzzer"] @property + @setting("LED", setter_name="set_led", icon="mdi:led-on") def led(self) -> bool: """True if LED is on.""" return self.data["led"] @property + @sensor("Electricity", unit="kWh", device_class="energy", icon="mdi:flash") def electricity(self) -> float: """Power consumption accumulation in kWh.""" return self.data["electricity"] @property + @sensor("Clean", icon="mdi:broom") def clean(self) -> CleaningStatus: """Auto clean mode indicator.""" return CleaningStatus(self.data["clean"]) @property + @sensor("Total Running Duration", icon="mdi:timer-outline") 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, + icon="mdi:fan", + ) def fan_speed_percent(self) -> int: """Current fan speed in percent.""" return self.data["fan_speed_percent"] @property + @sensor("Timer", icon="mdi: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 43dad1ffd..ba8346cae 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,17 +26,19 @@ def __init__(self, data): self.data = data @property + @sensor("Access Policy", icon="mdi:security") def access_policy(self) -> int: """Access policy of the associated stations.""" return self.data["sta"]["access_policy"] @property + @sensor("Associated Stations", icon="mdi:devices") def associated_stations(self) -> dict: """List of associated stations.""" return self.data["mat"] def __repr__(self) -> str: - s = "" % ( + s = "" % ( self.access_policy, len(self.associated_stations), ) @@ -51,14 +54,17 @@ def __init__(self, data): self.data = data @property + @sensor("SSID", icon="mdi:wifi") def ssid(self) -> str: return self.data["ssid"] @property + @sensor("Password", icon="mdi:lock") def password(self) -> str: return self.data["pwd"] @property + @sensor("SSID Hidden", icon="mdi:wifi-off") 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..d2aeda925 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", icon="mdi:speaker") def device_name(self) -> str: """Name of the device.""" return self.data["DeviceName"] @property + @sensor("Channel", icon="mdi:radio") def channel(self) -> str: """Name of the channel.""" return self.data["channel_title"] @property + @sensor("State", icon="mdi:play-circle") def state(self) -> PlayState: """State of the device, e.g. PLAYING.""" return PlayState(self.data["current_state"]) @property + @sensor("Hardware Version", icon="mdi:information-outline") def hardware_version(self) -> str: + """Hardware version.""" return self.data["hardware_version"] @property - def play_mode(self): + @sensor("Play Mode", icon="mdi:repeat") + 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", icon="mdi:account-music") def track_artist(self) -> str: """Artist of the current track.""" return self.data["track_artist"] @property + @sensor("Track Title", icon="mdi:music-note") def track_title(self) -> str: """Title of the current track.""" return self.data["track_title"] @property + @sensor("Track Duration", icon="mdi:timer-music", device_class="duration") def track_duration(self) -> str: """Total duration of the current track.""" return self.data["track_duration"] @property + @sensor("Transport Channel", icon="mdi:radio") 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 84e118745..056249383 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,81 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Switch 1 State", icon="mdi:toggle-switch") 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", + icon="mdi:toggle-switch-outline", + ) 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", + icon="mdi:timer", + device_class="duration", + 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", icon="mdi:toggle-switch") 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", + icon="mdi:toggle-switch-outline", + ) 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", + icon="mdi:timer", + device_class="duration", + 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", icon="mdi:lock-outline") def interlock(self) -> bool: """Interlock.""" return bool(self.data["interlock"]) @property + @setting("Flex Mode", setter_name="set_flex_mode", icon="mdi:shuffle-variant") def flex_mode(self) -> int: """Flex mode.""" return self.data["flex_mode"] @property + @sensor("RC List", icon="mdi:remote") 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 a6e435d8e..521a186ba 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,98 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: return self.power == "on" @property + @sensor("Mode", icon="mdi:water") def mode(self) -> str: """Current operation mode.""" return self.data["mode"] @property + @sensor("TDS", unit="ppm", icon="mdi:water-opacity") def tds(self) -> str: return self.data["tds"] @property + @sensor("Filter Life Remaining", unit="%", icon="mdi:filter") def filter_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["filter1_life"] @property + @sensor("Filter State", icon="mdi:filter") def filter_state(self) -> str: return self.data["filter1_state"] @property + @sensor("Filter 2 Life Remaining", unit="%", icon="mdi:filter") def filter2_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["filter_life"] @property + @sensor("Filter 2 State", icon="mdi:filter") def filter2_state(self) -> str: return self.data["filter_state"] @property + @sensor("Life", icon="mdi:clock-outline") def life(self) -> str: return self.data["life"] @property + @sensor("State", icon="mdi:information-outline") def state(self) -> str: return self.data["state"] @property + @sensor("Level", icon="mdi:water") def level(self) -> str: return self.data["level"] @property + @sensor("Volume", icon="mdi:cup-water") def volume(self) -> str: return self.data["volume"] @property + @sensor("Filter", icon="mdi:filter") def filter(self) -> str: return self.data["filter"] @property + @sensor("Usage", icon="mdi:chart-line") def usage(self) -> str: return self.data["usage"] @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> str: return self.data["temperature"] @property + @sensor("UV Filter Life Remaining", unit="%", icon="mdi:filter") def uv_filter_life_remaining(self) -> int: """Time until the filter should be changed.""" return self.data["uv_life"] @property + @sensor("UV Filter State", icon="mdi:filter") def uv_filter_state(self) -> str: return self.data["uv_state"] @property + @sensor("Valve", icon="mdi: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 8009c6300..52a712821 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", icon="mdi:alert-circle") def errors(self) -> list: return self.err_list @@ -119,121 +121,147 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Operation Status", icon="mdi:information-outline") def operation_status(self) -> OperationStatus: """Current operation status.""" return OperationStatus(self.data["run_status"]) @property + @sensor("Filter 1 Life Total", icon="mdi:filter-outline") 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", icon="mdi:filter-outline") def filter1_life_used(self) -> timedelta: """Filter1 used time in hours.""" return timedelta(hours=self.data["f1_usedtime"]) @property + @sensor("Filter 1 Life Remaining", icon="mdi:filter-outline") 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", icon="mdi:water") 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", icon="mdi:water") 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", icon="mdi:water") 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", icon="mdi:filter-outline") 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", icon="mdi:filter-outline") def filter2_life_used(self) -> timedelta: """Filter2 used time in hours.""" return timedelta(hours=self.data["f2_usedtime"]) @property + @sensor("Filter 2 Life Remaining", icon="mdi:filter-outline") 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", icon="mdi:water") 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", icon="mdi:water") 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", icon="mdi:water") 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", icon="mdi:filter-outline") 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", icon="mdi:filter-outline") def filter3_life_used(self) -> timedelta: """Filter3 used time in hours.""" return timedelta(hours=self.data["f3_usedtime"]) @property + @sensor("Filter 3 Life Remaining", icon="mdi:filter-outline") 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", icon="mdi:water") 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", icon="mdi:water") 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", icon="mdi:water") 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", icon="mdi:water-check") def tds_in(self) -> int: """TDS value of input water.""" return self.data["tds_in"] @property + @sensor("TDS Out", unit="ppm", icon="mdi:water-check") def tds_out(self) -> int: """TDS value of output water.""" return self.data["tds_out"] @property + @sensor("Rinse", icon="mdi:water-sync") def rinse(self) -> bool: """True if the device is rinsing.""" return self.data["rinse"] @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> int: """Current water temperature in Celsius.""" return self.data["temperature"] @property + @sensor("TDS Warning Threshold", unit="ppm", icon="mdi:water-alert") 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 f74bf5a69..189aeb362 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", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """Return True if device is on.""" return self.power == "on" @property + @sensor("AQI", unit="μg/m³", icon="mdi:air-filter") def aqi(self) -> int: """Air quality index.""" return self.data["aqi"] @property + @sensor("Average AQI", unit="μg/m³", icon="mdi:air-filter") def average_aqi(self) -> int: """Average of the air quality index.""" return self.data["average_aqi"] @property + @sensor("CO2", unit="ppm", icon="mdi:molecule-co2", device_class="carbon_dioxide") def co2(self) -> int: """Carbon dioxide.""" return self.data["co2"] @property + @sensor("Humidity", unit="%", device_class="humidity", icon="mdi:water-percent") def humidity(self) -> int: """Current humidity.""" return self.data["humidity"] @property + @setting("PTC", setter_name="set_ptc", icon="mdi:radiator") def ptc(self) -> Optional[bool]: """Return True if PTC is on.""" if self.data["ptc_state"] is not None: @@ -130,6 +138,9 @@ def ptc(self) -> Optional[bool]: return None @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> Optional[float]: """Current temperature, if available.""" if self.data["temp_dec"] is not None: @@ -141,6 +152,9 @@ def temperature(self) -> Optional[float]: return None @property + @sensor( + "NTC Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def ntc_temperature(self) -> Optional[float]: """Current ntc temperature, if available.""" if self.data["ntcT"] is not None: @@ -149,16 +163,24 @@ def ntc_temperature(self) -> Optional[float]: return None @property + @setting("Mode", setter_name="set_mode", choices=OperationMode, icon="mdi:fan") def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["mode"]) @property + @setting("LED", setter_name="set_led", icon="mdi:led-on") 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, + icon="mdi:brightness-6", + ) def led_brightness(self) -> Optional[LedBrightness]: """Brightness of the LED.""" if self.data["led_level"] is not None: @@ -173,6 +195,7 @@ def led_brightness(self) -> Optional[LedBrightness]: return None @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> Optional[bool]: """Return True if buzzer is on.""" if self.data["buzzer"] is not None: @@ -181,31 +204,37 @@ def buzzer(self) -> Optional[bool]: return None @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi: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="%", icon="mdi:filter-outline") 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", icon="mdi:filter-outline") 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", icon="mdi:timer-sand") 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", icon="mdi:fan") def motor_speed(self) -> int: """Speed of the motor.""" return self.data["motor1_speed"] @property + @setting("Extra Features", setter_name="set_extra_features", icon="mdi:star") def extra_features(self) -> Optional[int]: return self.data["app_extra"] diff --git a/miio/integrations/zhimi/airpurifier/airpurifier.py b/miio/integrations/zhimi/airpurifier/airpurifier.py index e30853dd4..2250bb279 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,39 @@ def __init__(self, data: dict[str, Any]) -> None: self.data = data @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """Return True if device is on.""" return self.power == "on" @property + @sensor("AQI", unit="μg/m³", icon="mdi:air-filter") def aqi(self) -> int: """Air quality index.""" return self.data["aqi"] @property + @sensor("Average AQI", unit="μg/m³", icon="mdi:air-filter") def average_aqi(self) -> int: """Average of the air quality index.""" return self.data["average_aqi"] @property + @sensor("Humidity", unit="%", device_class="humidity", icon="mdi:water-percent") def humidity(self) -> int: """Current humidity.""" return self.data["humidity"] @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) def temperature(self) -> Optional[float]: """Current temperature, if available.""" if self.data["temp_dec"] is not None: @@ -163,11 +172,15 @@ def temperature(self) -> Optional[float]: return None @property + @setting( + "Mode", setter_name="set_mode", choices=OperationMode, icon="mdi:air-purifier" + ) def mode(self) -> OperationMode: """Current operation mode.""" return OperationMode(self.data["mode"]) @property + @sensor("Sleep Mode", icon="mdi:sleep") def sleep_mode(self) -> Optional[SleepMode]: """Operation mode of the sleep state. @@ -179,11 +192,18 @@ def sleep_mode(self) -> Optional[SleepMode]: return None @property + @setting("LED", setter_name="set_led", icon="mdi:led-on") 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, + icon="mdi:brightness-6", + ) def led_brightness(self) -> Optional[LedBrightness]: """Brightness of the LED.""" if self.data["led_b"] is not None: @@ -195,6 +215,9 @@ def led_brightness(self) -> Optional[LedBrightness]: return None @property + @sensor( + "Illuminance", unit="lx", device_class="illuminance", icon="mdi:brightness-5" + ) def illuminance(self) -> Optional[int]: """Environment illuminance level in lux [0-200]. @@ -203,6 +226,7 @@ def illuminance(self) -> Optional[int]: return self.data["bright"] @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> Optional[bool]: """Return True if buzzer is on.""" if self.data["buzzer"] is not None: @@ -211,62 +235,89 @@ def buzzer(self) -> Optional[bool]: return None @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi: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, + icon="mdi:star", + ) 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="%", icon="mdi:filter-outline") 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", icon="mdi:filter-outline") 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", icon="mdi:timer-sand") 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³", icon="mdi:air-purifier") def purify_volume(self) -> int: """The volume of purified air in cubic meter.""" return self.data["purify_volume"] @property + @sensor("Motor Speed", unit="rpm", icon="mdi:fan") def motor_speed(self) -> int: """Speed of the motor.""" return self.data["motor1_speed"] @property + @sensor("Motor 2 Speed", unit="rpm", icon="mdi:fan") def motor2_speed(self) -> Optional[int]: """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, + icon="mdi:volume-high", + ) def volume(self) -> Optional[int]: """Volume of sound notifications [0-100].""" return self.data["volume"] @property + @sensor("Filter RFID Product ID", icon="mdi:barcode") def filter_rfid_product_id(self) -> Optional[str]: """RFID product ID of installed filter.""" return self.data["rfid_product_id"] @property + @sensor("Filter RFID Tag", icon="mdi:barcode") def filter_rfid_tag(self) -> Optional[str]: """RFID tag ID of installed filter.""" return self.data["rfid_tag"] @property + @sensor("Filter Type", icon="mdi:filter-outline") def filter_type(self) -> Optional[FilterType]: """Type of installed filter.""" return self.filter_type_util.determine_filter_type( @@ -274,23 +325,28 @@ def filter_type(self) -> Optional[FilterType]: ) @property + @setting("Learn Mode", setter_name="set_learn_mode", icon="mdi:school") def learn_mode(self) -> bool: """Return True if Learn Mode is enabled.""" return self.data["act_sleep"] == "single" @property + @sensor("Sleep Time", unit="s", icon="mdi:sleep") def sleep_time(self) -> Optional[int]: return self.data["sleep_time"] @property + @sensor("Sleep Mode Learn Count", icon="mdi:counter") def sleep_mode_learn_count(self) -> Optional[int]: return self.data["sleep_data_num"] @property + @setting("Extra Features", setter_name="set_extra_features", icon="mdi:star") def extra_features(self) -> Optional[int]: return self.data["app_extra"] @property + @sensor("Turbo Mode Supported", icon="mdi:rocket") def turbo_mode_supported(self) -> Optional[bool]: if self.data["app_extra"] is not None: return self.data["app_extra"] == 1 @@ -298,6 +354,7 @@ def turbo_mode_supported(self) -> Optional[bool]: return None @property + @setting("Auto Detect", setter_name="set_auto_detect", icon="mdi:eye") def auto_detect(self) -> Optional[bool]: """Return True if auto detect is enabled.""" if self.data["act_det"] is not None: @@ -306,6 +363,7 @@ def auto_detect(self) -> Optional[bool]: return None @property + @sensor("Button Pressed", icon="mdi:gesture-tap-button") def button_pressed(self) -> Optional[str]: """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 6f7f15749..1257d8096 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", icon="mdi:atom") def ionizer(self) -> bool: """True if negative ions generation is enabled.""" return self.data["anion"] @property + @sensor("Battery Supported", icon="mdi:battery") def battery_supported(self) -> bool: """True if battery is supported.""" return self.data["battery_supported"] @property + @sensor("Buttons Pressed", icon="mdi:gesture-tap-button") def buttons_pressed(self) -> str: """What buttons on the fan are pressed now.""" code = self.data["buttons_pressed"] @@ -104,16 +108,26 @@ def buttons_pressed(self) -> str: return "Unknown" @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") def buzzer(self) -> bool: """True if buzzer is turned on.""" return self.data["buzzer"] @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi: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, + icon="mdi:fan", + ) def fan_level(self) -> int: """Fan level (1-4).""" return self.data["fan_level"] @@ -125,61 +139,96 @@ def fan_speed(self) -> int: return self.speed @property + @setting( + "Speed", + setter_name="set_speed", + min_value=1, + max_value=100, + step=1, + unit="%", + icon="mdi:fan", + ) def speed(self) -> int: """Fan speed (1-100).""" return self.data["fan_speed"] @property + @sensor("Humidity", unit="%", device_class="humidity", icon="mdi:water-percent") 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="%", + icon="mdi:brightness-6", + ) def led_brightness(self) -> int: """LED brightness (1-100).""" return self.data["light"] @property + @setting("Mode", setter_name="set_mode", choices=OperationMode, icon="mdi:fan") def mode(self) -> OperationMode: """Operation mode (normal or nature).""" return OperationMode[OperationModeFanZA5(self.data["mode"]).name] @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.data["power"] else "off" @property + @sensor("Is On", icon="mdi:power") 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", + icon="mdi:timer-off-outline", + ) def delay_off_countdown(self) -> int: """Countdown until turning off in minutes.""" return self.data["power_off_time"] @property + @sensor("Power Supply Attached", icon="mdi:power-plug") def powersupply_attached(self) -> bool: """True is power supply is attached.""" return self.data["powersupply_attached"] @property + @sensor("Speed RPM", unit="rpm", icon="mdi:fan") def speed_rpm(self) -> int: """Fan rotations per minute.""" return self.data["speed_rpm"] @property + @setting("Oscillate", setter_name="set_oscillate", icon="mdi:arrow-left-right") def oscillate(self) -> bool: """True if oscillation is enabled.""" return self.data["swing_mode"] @property + @setting("Angle", setter_name="set_angle", icon="mdi:angle-acute") def angle(self) -> int: """Oscillation angle.""" return self.data["swing_mode_angle"] @property + @sensor( + "Temperature", unit="°C", device_class="temperature", icon="mdi:thermometer" + ) 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 e42ff6e93..4e5b40c1e 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", icon="mdi:power") def power(self) -> str: """Power state.""" return self.data["power"] @property + @sensor("Is On", icon="mdi:power") def is_on(self) -> bool: """True if device is currently on.""" return self.power == "on" @property + @sensor("Humidity", unit="%", icon="mdi:water-percent", device_class="humidity") def humidity(self) -> Optional[int]: """Current humidity.""" if ( @@ -78,36 +82,60 @@ def humidity(self) -> Optional[int]: return None @property + @sensor( + "Temperature", unit="°C", icon="mdi:thermometer", device_class="temperature" + ) 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, + device_class="temperature", + icon="mdi:thermometer", + ) def target_temperature(self) -> int: """Target temperature.""" return self.data["target_temperature"] @property + @setting( + "Brightness", + setter_name="set_brightness", + choices=Brightness, + icon="mdi:brightness-6", + ) def brightness(self) -> Brightness: """Display brightness.""" return Brightness(self.data["brightness"]) @property + @setting("Buzzer", setter_name="set_buzzer", icon="mdi:volume-high") 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", icon="mdi:lock") def child_lock(self) -> bool: """True if child lock is on.""" return self.data["child_lock"] == "on" @property + @sensor("Use Time", unit="s", icon="mdi:timer", device_class="duration") 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", icon="mdi:timer-sand", device_class="duration" + ) def delay_off_countdown(self) -> Optional[int]: """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 7d6104754..32b14a3f0 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,74 @@ def __init__(self, data: dict[str, Any], model: str) -> None: self.model = model @property + @sensor("Power", icon="mdi:power") def power(self) -> str: """Power state.""" return "on" if self.is_on else "off" @property + @sensor("Is On", icon="mdi:power") 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, + device_class="temperature", + icon="mdi:thermometer", + ) def target_temperature(self) -> int: """Target temperature.""" return self.data["target_temperature"] @property + @sensor( + "Delay Off Countdown", unit="s", icon="mdi:timer-sand", device_class="duration" + ) def delay_off_countdown(self) -> int: """Countdown until turning off in seconds.""" return self.data["countdown_time"] @property + @sensor( + "Temperature", unit="°C", icon="mdi:thermometer", device_class="temperature" + ) def temperature(self) -> float: """Current temperature.""" return self.data["temperature"] @property + @sensor( + "Relative Humidity", unit="%", icon="mdi:water-percent", device_class="humidity" + ) def relative_humidity(self) -> Optional[int]: """Current relative humidity.""" return self.data.get("relative_humidity") @property + @setting("Child Lock", setter_name="set_child_lock", icon="mdi: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", icon="mdi:volume-high") 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, + icon="mdi:brightness-6", + ) 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..004458ed9 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", icon="mdi:alarm") def enabled(self) -> bool: return self._enabled @property - def start(self): + @sensor("Start Time", icon="mdi:clock-start") + def start(self) -> str: return self._start @property - def end(self): + @sensor("End Time", icon="mdi:clock-end") + def end(self) -> str: return self._end