diff --git a/.cache/brands/integrations/midea_ac_lan/logo.png b/.cache/brands/integrations/midea_ac_lan/logo.png new file mode 100644 index 0000000..33adb36 Binary files /dev/null and b/.cache/brands/integrations/midea_ac_lan/logo.png differ diff --git a/.ha_run.lock b/.ha_run.lock index 2bffdf1..00f9d2e 100644 --- a/.ha_run.lock +++ b/.ha_run.lock @@ -1 +1 @@ -{"pid": 71, "version": 1, "ha_version": "2026.6.2", "start_ts": 1781272512.3211384} \ No newline at end of file +{"pid": 71, "version": 1, "ha_version": "2026.6.2", "start_ts": 1781274194.4665778} \ No newline at end of file diff --git a/automations.yaml b/automations.yaml index d3307f0..b4586ad 100644 --- a/automations.yaml +++ b/automations.yaml @@ -1,79 +1,78 @@ -- id: "1780072464609" - alias: "Camera: Send Motion Notifications" - description: - Sends a push notification to your primary mobile device when Nest cameras +- id: '1780072464609' + alias: 'Camera: Send Motion Notifications' + description: Sends a push notification to your primary mobile device when Nest cameras detect activity. triggers: - - entity_id: - - camera.entryway_camera - - camera.front_door - - camera.hallway_camera - - camera.living_room_camera - trigger: state + - entity_id: + - camera.entryway_camera + - camera.front_door + - camera.hallway_camera + - camera.living_room_camera + trigger: state conditions: [] actions: - - action: notify.notify - data: - title: Motion Detected! - message: Activity detected on one of your Nest security cameras. + - action: notify.notify + data: + title: Motion Detected! + message: Activity detected on one of your Nest security cameras. mode: parallel max: 10 -- id: "1780125759253" - alias: "Tablet: Dim Screen at Night" +- id: '1780125759253' + alias: 'Tablet: Dim Screen at Night' description: Drops tablet brightness to zero at night triggers: - - at: 01:00:00 - trigger: time + - at: 01:00:00 + trigger: time actions: - - action: notify.mobile_app_sm_t387w + - action: notify.mobile_app_sm_t387w + data: + message: command_screen_brightness_level data: - message: command_screen_brightness_level - data: - command: 0 -- id: "1780125803824" - alias: "Tablet: Restore Screen in Morning" + command: 0 +- id: '1780125803824' + alias: 'Tablet: Restore Screen in Morning' description: Restores tablet brightness to normal in the morning triggers: - - at: 07:00:00 - trigger: time + - at: 07:00:00 + trigger: time actions: - - action: notify.mobile_app_sm_t387w + - action: notify.mobile_app_sm_t387w + data: + message: command_screen_brightness_level data: - message: command_screen_brightness_level - data: - command: 180 -- id: "1780343863793" + command: 180 +- id: '1780343863793' alias: Front Door Notification - description: "" + description: '' triggers: - - trigger: state - entity_id: - - binary_sensor.front_door - from: - - "off" - to: - - "on" - - trigger: state - entity_id: - - binary_sensor.patio_door - from: - - "off" - to: - - "on" + - trigger: state + entity_id: + - binary_sensor.front_door + from: + - 'off' + to: + - 'on' + - trigger: state + entity_id: + - binary_sensor.patio_door + from: + - 'off' + to: + - 'on' conditions: [] actions: - - target: - entity_id: - - light.kitchen_home_kitchen - action: light.toggle - - delay: 00:00:15 - - target: - entity_id: - - light.kitchen_home_kitchen - action: light.toggle - - data: - message: There is someone at the front door - action: notify.notify + - target: + entity_id: + - light.kitchen_home_kitchen + action: light.toggle + - delay: 00:00:15 + - target: + entity_id: + - light.kitchen_home_kitchen + action: light.toggle + - data: + message: There is someone at the front door + action: notify.notify mode: single variables: _cafe_metadata: @@ -100,43 +99,42 @@ y: -555 graph_id: 7ad98446-4ce0-4b88-a400-430ac474a617 graph_version: 1 -- id: "1780441470361" +- id: '1780441470361' alias: Reload Hilo Integrations on Reboot - description: - Reloads problematic integrations 2 minutes after Home Assistant starts + description: Reloads problematic integrations 2 minutes after Home Assistant starts up. triggers: - - trigger: homeassistant - event: start + - trigger: homeassistant + event: start conditions: [] actions: - - delay: - hours: 0 - minutes: 3 - seconds: 0 - milliseconds: 0 - - action: homeassistant.reload_config_entry - metadata: {} - target: - device_id: - - 3e67afd88a4b4d17552c8f370aaf40b5 - - baad176ac2f415f3642e2d075bb6a977 - data: {} + - delay: + hours: 0 + minutes: 3 + seconds: 0 + milliseconds: 0 + - action: homeassistant.reload_config_entry + metadata: {} + target: + device_id: + - 3e67afd88a4b4d17552c8f370aaf40b5 + - baad176ac2f415f3642e2d075bb6a977 + data: {} mode: single -- id: "1780509027967" +- id: '1780509027967' alias: Notify When Ring Console Battery Low - description: "" + description: '' triggers: - - trigger: numeric_state - entity_id: - - sensor.entryway_keypad_battery - below: 25 + - trigger: numeric_state + entity_id: + - sensor.entryway_keypad_battery + below: 25 conditions: [] actions: - - data: - message: Keypad Battery Low. Please Charge ASAP - title: Ring Console Battery - action: notify.notify + - data: + message: Keypad Battery Low. Please Charge ASAP + title: Ring Console Battery + action: notify.notify mode: single variables: _cafe_metadata: @@ -151,20 +149,20 @@ y: 45 graph_id: 816af3b7-3eac-47c3-8da3-8797d1774c0c graph_version: 1 -- id: "1780516946807" +- id: '1780516946807' alias: Notify When Front Door Sensor Battery Low - description: "" + description: '' triggers: - - trigger: numeric_state - entity_id: - - sensor.front_door_battery - below: 25 + - trigger: numeric_state + entity_id: + - sensor.front_door_battery + below: 25 conditions: [] actions: - - data: - message: Front Door Sensor Battery Low. Please Change Batteries ASAP. - title: Ring Console Battery - action: notify.notify + - data: + message: Front Door Sensor Battery Low. Please Change Batteries ASAP. + title: Ring Console Battery + action: notify.notify mode: single variables: _cafe_metadata: @@ -179,54 +177,51 @@ y: 45 graph_id: 816af3b7-3eac-47c3-8da3-8797d1774c0c graph_version: 1 -- id: "1780813227139" - alias: "Alert: Fridge or Freezer Power Outage" - description: - Triggers an alert if the fridge or freezer smart plug power consumption +- id: '1780813227139' + alias: 'Alert: Fridge or Freezer Power Outage' + description: Triggers an alert if the fridge or freezer smart plug power consumption drops to 0W for too long. triggers: - - trigger: numeric_state - entity_id: - - sensor.freezer_power - for: - hours: 0 - minutes: 45 - seconds: 0 - below: 1 - id: freezer_down - - trigger: numeric_state - entity_id: - - sensor.fridge_power - for: - hours: 0 - minutes: 20 - seconds: 0 - below: 1 - id: fridge_down + - trigger: numeric_state + entity_id: + - sensor.freezer_power + for: + hours: 0 + minutes: 45 + seconds: 0 + below: 1 + id: freezer_down + - trigger: numeric_state + entity_id: + - sensor.fridge_power + for: + hours: 0 + minutes: 20 + seconds: 0 + below: 1 + id: fridge_down conditions: [] actions: + - if: + - condition: trigger + id: freezer_down + then: + - data: + title: "\U0001F6A8 URGENT: Freezer Power Failure" + message: The Freezer smart plug has drawn 0W of power for over 40 minutes! + Check the physical appliance immediately. + action: notify.notify + else: - if: - - condition: trigger - id: freezer_down + - condition: trigger + id: fridge_down then: - - data: - title: "\U0001F6A8 URGENT: Freezer Power Failure" - message: - The Freezer smart plug has drawn 0W of power for over 40 minutes! - Check the physical appliance immediately. - action: notify.notify - else: - - if: - - condition: trigger - id: fridge_down - then: - - data: - title: "\U0001F6A8 URGENT: Fridge Power Failure" - message: - The Fridge smart plug has drawn 0W of power for over 20 minutes! - Check the physical appliance immediately. - action: notify.notify - else: [] + - data: + title: "\U0001F6A8 URGENT: Fridge Power Failure" + message: The Fridge smart plug has drawn 0W of power for over 20 minutes! + Check the physical appliance immediately. + action: notify.notify + else: [] mode: single variables: _cafe_metadata: @@ -253,134 +248,172 @@ y: 102 graph_id: 5948b900-b45d-4c2e-ae73-c880acc3a1bf graph_version: 1 -- id: "1780989340205" - alias: "Kitchen: Coffee Maker Active Notification" - description: - Sends a mobile application push notification when the coffee maker +- id: '1780989340205' + alias: 'Kitchen: Coffee Maker Active Notification' + description: Sends a mobile application push notification when the coffee maker power exceeds 1000W for 5 seconds. triggers: - - entity_id: sensor.coffee_maker_power - above: 1000 - for: - hours: 0 - minutes: 0 - seconds: 5 - trigger: numeric_state + - entity_id: sensor.coffee_maker_power + above: 1000 + for: + hours: 0 + minutes: 0 + seconds: 5 + trigger: numeric_state conditions: [] actions: - - data: - title: ☕ Coffee Time - message: Someone is Making Coffee - action: notify.notify - - action: notify.persistent_notification - metadata: {} - data: - title: ☕ Coffee Time - message: Someone is Making Coffee - - delay: - hours: 0 - minutes: 4 - seconds: 0 - milliseconds: 0 + - data: + title: ☕ Coffee Time + message: Someone is Making Coffee + action: notify.notify + - action: notify.persistent_notification + metadata: {} + data: + title: ☕ Coffee Time + message: Someone is Making Coffee + - delay: + hours: 0 + minutes: 4 + seconds: 0 + milliseconds: 0 mode: single -- id: "1781024962776" - alias: "Office: Turn On Lights on Motion then Door Open" - description: - Triggers when office door opens, but only if living room motion happened +- id: '1781024962776' + alias: 'Office: Turn On Lights on Motion then Door Open' + description: Triggers when office door opens, but only if living room motion happened in the last 30 seconds and light is off. triggers: - - trigger: state - entity_id: binary_sensor.office_door_contact - to: "on" + - trigger: state + entity_id: binary_sensor.office_door_contact + to: 'on' conditions: - - condition: time - after: "10:00:00" - before: "21:00:00" - - condition: state - entity_id: light.playroom_light - state: "off" - - condition: template - value_template: - "{{ (now() - states.binary_sensor.basement.last_changed).total_seconds() - < 30 }}" + - condition: time + after: '10:00:00' + before: '21:00:00' + - condition: state + entity_id: light.playroom_light + state: 'off' + - condition: template + value_template: '{{ (now() - states.binary_sensor.basement.last_changed).total_seconds() + < 30 }}' actions: - - action: light.turn_on - target: - entity_id: light.playroom_light - data: {} + - action: light.turn_on + target: + entity_id: light.playroom_light + data: {} mode: single -- id: "1781034724187" - alias: "Office: Turn Off Lights on Exit" - description: - Turns off office lights 2 minutes after the door closes, but only if living +- id: '1781034724187' + alias: 'Office: Turn Off Lights on Exit' + description: Turns off office lights 2 minutes after the door closes, but only if living room motion was detected after the door shut. triggers: - - trigger: state - entity_id: binary_sensor.office_door_contact - to: "off" - for: - minutes: 2 + - trigger: state + entity_id: binary_sensor.office_door_contact + to: 'off' + for: + minutes: 2 conditions: - - condition: state - entity_id: light.playroom_light - state: "on" - - condition: template - value_template: - "{{ states.binary_sensor.basement.last_changed > states.binary_sensor.office_door_contact.last_changed - }}" + - condition: state + entity_id: light.playroom_light + state: 'on' + - condition: template + value_template: '{{ states.binary_sensor.basement.last_changed > states.binary_sensor.office_door_contact.last_changed + }}' actions: - - action: light.turn_off - target: - entity_id: light.playroom_light - data: {} + - action: light.turn_off + target: + entity_id: light.playroom_light + data: {} mode: single -- id: "1781102033733" +- id: '1781102033733' alias: Water Leak Detected - description: "" + description: '' triggers: - - type: moist - device_id: 59bc102f385d7a3524c8f21a5c53592b - entity_id: c268ff8842e905515de2274d09c5ab9e - domain: binary_sensor - metadata: - secondary: false - trigger: device + - type: moist + device_id: 59bc102f385d7a3524c8f21a5c53592b + entity_id: c268ff8842e905515de2274d09c5ab9e + domain: binary_sensor + metadata: + secondary: false + trigger: device conditions: [] actions: - - action: notify.notify - metadata: {} - data: - title: "\U0001F6A8 URGENT: Water Leak Detected" - message: Water Leak Detected in Basement. Check Water Heater for Leaks. - - action: persistent_notification.create - metadata: {} - data: - title: "\U0001F6A8 URGENT: Water Leak Detected" - message: Water Leak Detected in Basement. Check Water Heater for Leaks. + - action: notify.notify + metadata: {} + data: + title: "\U0001F6A8 URGENT: Water Leak Detected" + message: Water Leak Detected in Basement. Check Water Heater for Leaks. + - action: persistent_notification.create + metadata: {} + data: + title: "\U0001F6A8 URGENT: Water Leak Detected" + message: Water Leak Detected in Basement. Check Water Heater for Leaks. mode: single -- id: "1781112110968" - alias: "Freezer Safety: Alert if Plug Turns Off" - description: - Triggers an immediate notification if the freezer smart plug is turned +- id: '1781112110968' + alias: 'Freezer Safety: Alert if Plug Turns Off' + description: Triggers an immediate notification if the freezer smart plug is turned off. triggers: - - trigger: state - entity_id: switch.freezer - to: "off" - for: - minutes: 1 + - trigger: state + entity_id: switch.freezer + to: 'off' + for: + minutes: 1 actions: - - action: notify.notify - data: - title: "\U0001F6A8 FREEZER PLUG TRIPPED!" - message: - The freezer smart plug turned off. Check it immediately to prevent - food spoilage. - - action: notify.persistent_notification - metadata: {} - data: - title: "\U0001F6A8 FREEZER PLUG TRIPPED!" - message: - The freezer smart plug turned off. Check it immediately to prevent - food spoilage. + - action: notify.notify + data: + title: "\U0001F6A8 FREEZER PLUG TRIPPED!" + message: The freezer smart plug turned off. Check it immediately to prevent + food spoilage. + - action: notify.persistent_notification + metadata: {} + data: + title: "\U0001F6A8 FREEZER PLUG TRIPPED!" + message: The freezer smart plug turned off. Check it immediately to prevent + food spoilage. + mode: single +- id: '1781275767970' + alias: 'Dishwasher: Cycle Finished Notification' + description: Alerts when the dishwasher finishes washing + triggers: + - entity_id: sensor.150633095332665_progress + from: Running + to: Idle + trigger: state + actions: + - action: notify.notify + data: + title: Dishwasher Done + message: The dishwasher cycle has completed successfully! + mode: single +- id: '1781275801564' + alias: 'Dishwasher: Rinse Aid Low Warning' + description: Alerts when rinse aid needs a refill + triggers: + - entity_id: binary_sensor.150633095332665_rinse_aid + to: 'on' + trigger: state + actions: + - action: notify.notify + data: + title: Dishwasher Maintenance + message: The dishwasher is low on rinse aid. Please refill it before the next + cycle! + mode: single +- id: '1781278655292' + alias: 'Dishwasher: Dishwasher Started Notification' + description: Alerts when the dishwasher starts washing + triggers: + - entity_id: + - sensor.150633095332665_progress + from: + - idle + to: + - running + trigger: state + conditions: [] + actions: + - action: notify.notify + data: + title: Dishwasher Started + message: The dishwasher cycle has started! mode: single diff --git a/custom_components/midea_ac_lan/__init__.py b/custom_components/midea_ac_lan/__init__.py new file mode 100644 index 0000000..d3b3b54 --- /dev/null +++ b/custom_components/midea_ac_lan/__init__.py @@ -0,0 +1,346 @@ +"""Init for Midea LAN. + +integration load process: +1. component setup: `async_setup` + 1.1 use `hass.services.async_register` to register service +2. config entry setup: `async_setup_entry` + 2.1 forward the Config Entry to the platform `async_forward_entry_setups` + 2.2 register listener `update_listener` +3. unloading a config entry: `async_unload_entry` +""" + +import logging +from typing import Any, cast + +import homeassistant.helpers.config_validation as cv +import homeassistant.helpers.device_registry as dr +import voluptuous as vol +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_CUSTOMIZE, + CONF_DEVICE_ID, + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PORT, + CONF_PROTOCOL, + CONF_TOKEN, + CONF_TYPE, + MAJOR_VERSION, + MINOR_VERSION, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.typing import ConfigType +from midealocal.device import DeviceType, MideaDevice, ProtocolVersion +from midealocal.devices import device_selector + +from .const import ( + ALL_PLATFORM, + CONF_ACCOUNT, + CONF_KEY, + CONF_MODEL, + CONF_REFRESH_INTERVAL, + CONF_SUBTYPE, + DEVICES, + DOMAIN, + EXTRA_SWITCH, +) +from .midea_devices import MIDEA_DEVICES + +_LOGGER = logging.getLogger(__name__) + + +async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Option flow signal update. + + register update listener for config entry that will be called when entry is updated. + A listener is registered by adding the following to the `async_setup_entry`: + `config_entry.async_on_unload(config_entry.add_update_listener(update_listener))` + means the Listener is attached when the entry is loaded and detached at unload + """ + # Forward the unloading of an entry to platforms. + await hass.config_entries.async_unload_platforms(config_entry, ALL_PLATFORM) + # forward the Config Entry to the platforms + hass.async_create_task( + hass.config_entries.async_forward_entry_setups(config_entry, ALL_PLATFORM), + ) + device_id: int = cast("int", config_entry.data.get(CONF_DEVICE_ID)) + customize = config_entry.options.get(CONF_CUSTOMIZE, "") + ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) + refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL, None) + dev: MideaDevice = hass.data[DOMAIN][DEVICES].get(device_id) + if dev: + dev.set_customize(customize) + if ip_address is not None: + dev.set_ip_address(ip_address) + if refresh_interval is not None: + dev.set_refresh_interval(refresh_interval) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa: ARG001 + """Set up midea_lan component when load this integration. + + Returns + ------- + True if entry is configured. + + """ + hass.data.setdefault(DOMAIN, {}) + attributes = [] + for device_entities in MIDEA_DEVICES.values(): + for attribute_name, attribute in cast( + "dict", + device_entities["entities"], + ).items(): + if ( + attribute.get("type") in EXTRA_SWITCH + and attribute_name.value not in attributes + ): + attributes.append(attribute_name.value) + + def service_set_attribute(service: Any) -> None: # noqa: ANN401 + """Set service attribute func.""" + device_id: int = service.data["device_id"] + attr = service.data["attribute"] + value = service.data["value"] + dev: MideaDevice = hass.data[DOMAIN][DEVICES].get(device_id) + if dev: + if attr == "fan_speed" and value == "auto": + value = 102 + item = None + if dev_ := MIDEA_DEVICES.get(dev.device_type): + item = cast("dict", dev_["entities"]).get(attr) + if ( + item + and (item.get("type") in EXTRA_SWITCH) + or ( + dev.device_type == DeviceType.AC + and attr == "fan_speed" + and value in range(103) + ) + ): + dev.set_attribute(attr=attr, value=value) + else: + _LOGGER.error( + "Appliance [%s] has no attribute %s or value is invalid", + device_id, + attr, + ) + + def service_send_command(service: Any) -> None: # noqa: ANN401 + """Send command to service func.""" + device_id = service.data.get("device_id") + cmd_type = service.data.get("cmd_type") + cmd_body = service.data.get("cmd_body") + try: + cmd_body = bytearray.fromhex(cmd_body) + except ValueError: + _LOGGER.exception( + "Appliance [%s] invalid cmd_body, a hexadecimal string required", + device_id, + ) + return + dev = hass.data[DOMAIN][DEVICES].get(device_id) + if dev: + dev.send_command(cmd_type, cmd_body) + + # register service func calls: + # `service_set_attribute`, service.yaml key: `set_attribute` + hass.services.async_register( + DOMAIN, + "set_attribute", + service_set_attribute, + schema=vol.Schema( + { + vol.Required("device_id"): vol.Coerce(int), + vol.Required("attribute"): vol.In(attributes), + vol.Required("value"): vol.Any(int, cv.boolean, str), + }, + ), + ) + + # register service func calls: + # `service_send_command`, service.yaml key: `send_command` + hass.services.async_register( + DOMAIN, + "send_command", + service_send_command, + schema=vol.Schema( + { + vol.Required("device_id"): vol.Coerce(int), + vol.Required("cmd_type"): vol.In([2, 3]), + vol.Required("cmd_body"): str, + }, + ), + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up platform for current integration. + + Returns + ------- + True if entry is configured. + + """ + device_type = config_entry.data.get(CONF_TYPE) + if device_type == CONF_ACCOUNT: + return True + name = config_entry.data.get(CONF_NAME) + device_id: int = config_entry.data[CONF_DEVICE_ID] + if name is None: + name = f"{device_id}" + if device_type is None: + device_type = 0xAC + token: str = config_entry.data.get(CONF_TOKEN) or "" + key: str = config_entry.data.get(CONF_KEY) or "" + ip_address = config_entry.options.get(CONF_IP_ADDRESS, None) + if ip_address is None: + ip_address = config_entry.data.get(CONF_IP_ADDRESS) + refresh_interval = config_entry.options.get(CONF_REFRESH_INTERVAL) + port: int = config_entry.data[CONF_PORT] + model: str = config_entry.data[CONF_MODEL] + subtype = config_entry.data.get(CONF_SUBTYPE, 0) + protocol: ProtocolVersion = ProtocolVersion(config_entry.data[CONF_PROTOCOL]) + customize: str = config_entry.options.get(CONF_CUSTOMIZE, "") + if protocol == ProtocolVersion.V3 and (key == "" or token == ""): + _LOGGER.error("For V3 devices, the key and the token is required") + return False + # device_selector in `midealocal/devices/__init__.py` + # hass core version >= 2024.3 + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 3): + device = await hass.async_add_import_executor_job( + device_selector, + name, + device_id, + device_type, + ip_address, + port, + token, + key, + protocol, + model, + subtype, + customize, + ) + # hass core version < 2024.3 + else: + device = device_selector( + name=name, + device_id=device_id, + device_type=device_type, + ip_address=ip_address, + port=port, + token=token, + key=key, + device_protocol=protocol, + model=model, + subtype=subtype, + customize=customize, + ) + if device: + if refresh_interval is not None: + device.set_refresh_interval(refresh_interval) + device.open() + if DOMAIN not in hass.data: + hass.data[DOMAIN] = {} + if DEVICES not in hass.data[DOMAIN]: + hass.data[DOMAIN][DEVICES] = {} + hass.data[DOMAIN][DEVICES][device_id] = device + # Forward the setup of an entry to all platforms + await hass.config_entries.async_forward_entry_setups(config_entry, ALL_PLATFORM) + # Listener `update_listener` is + # attached when the entry is loaded + # and detached when it's unloaded + config_entry.async_on_unload(config_entry.add_update_listener(update_listener)) + return True + return False + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Clean up entities, unsubscribe event listener and close all connections. + + Returns + ------- + True if entry is unloaded. + + """ + device_type = config_entry.data.get(CONF_TYPE) + if device_type == CONF_ACCOUNT: + return True + device_id = config_entry.data.get(CONF_DEVICE_ID) + if device_id is not None: + dm = hass.data[DOMAIN][DEVICES].get(device_id) + if dm is not None: + try: + dm.close() + except (OSError, ConnectionError, AttributeError) as e: + _LOGGER.warning("Failed to close Midea socket cleanly: %s", e) + hass.data[DOMAIN][DEVICES].pop(device_id) + # Forward the unloading of an entry to platforms + await hass.config_entries.async_unload_platforms(config_entry, ALL_PLATFORM) + return True + + +async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Migrate old entry. + + Returns + ------- + True if entry is migrated. + + """ + # 1 -> 2: convert device identifiers from int to str + if config_entry.version == 1: + _LOGGER.debug("Migrating configuration from version 1") + + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 3): + hass.config_entries.async_update_entry(config_entry, version=2) + else: + config_entry.version = 2 + hass.config_entries.async_update_entry(config_entry) + + # Migrate device. + await _async_migrate_device_identifiers(hass, config_entry) + + _LOGGER.debug("Migration to configuration version 2 successful") + + return True + + +async def _async_migrate_device_identifiers( + hass: HomeAssistant, + config_entry: ConfigEntry, +) -> None: + """Migrate the device identifiers to the new format.""" + device_registry = dr.async_get(hass) + device_entry_found = False + for device_entry in dr.async_entries_for_config_entry( + device_registry, + config_entry.entry_id, + ): + for identifier in device_entry.identifiers: + # Device found in registry. Update identifiers. + new_identifiers = { + ( + DOMAIN, + str(identifier[1]), + ), + } + _LOGGER.debug( + "Migrating device identifiers from %s to %s", + device_entry.identifiers, + new_identifiers, + ) + device_registry.async_update_device( + device_id=device_entry.id, + new_identifiers=new_identifiers, + ) + # Device entry found. Leave inner for loop. + device_entry_found = True + break + + # Leave outer for loop if device entry is already found. + if device_entry_found: + break diff --git a/custom_components/midea_ac_lan/__pycache__/__init__.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..14716b6 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/__init__.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/binary_sensor.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/binary_sensor.cpython-314.pyc new file mode 100644 index 0000000..a112162 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/binary_sensor.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/climate.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/climate.cpython-314.pyc new file mode 100644 index 0000000..f6e75f0 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/climate.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/config_flow.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/config_flow.cpython-314.pyc new file mode 100644 index 0000000..0c9cf19 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/config_flow.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/const.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/const.cpython-314.pyc new file mode 100644 index 0000000..f38fcfa Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/const.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/diagnostics.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/diagnostics.cpython-314.pyc new file mode 100644 index 0000000..0c4470c Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/diagnostics.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/fan.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/fan.cpython-314.pyc new file mode 100644 index 0000000..e067291 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/fan.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/humidifier.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/humidifier.cpython-314.pyc new file mode 100644 index 0000000..1191972 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/humidifier.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/light.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/light.cpython-314.pyc new file mode 100644 index 0000000..74809ae Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/light.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/lock.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/lock.cpython-314.pyc new file mode 100644 index 0000000..a9965eb Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/lock.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/midea_devices.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/midea_devices.cpython-314.pyc new file mode 100644 index 0000000..a63400e Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/midea_devices.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/midea_entity.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/midea_entity.cpython-314.pyc new file mode 100644 index 0000000..a113e15 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/midea_entity.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/number.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/number.cpython-314.pyc new file mode 100644 index 0000000..8b8dc7e Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/number.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/select.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/select.cpython-314.pyc new file mode 100644 index 0000000..47df762 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/select.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/sensor.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/sensor.cpython-314.pyc new file mode 100644 index 0000000..76f3c80 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/sensor.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/switch.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/switch.cpython-314.pyc new file mode 100644 index 0000000..22b0869 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/switch.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/__pycache__/water_heater.cpython-314.pyc b/custom_components/midea_ac_lan/__pycache__/water_heater.cpython-314.pyc new file mode 100644 index 0000000..73b9443 Binary files /dev/null and b/custom_components/midea_ac_lan/__pycache__/water_heater.cpython-314.pyc differ diff --git a/custom_components/midea_ac_lan/binary_sensor.py b/custom_components/midea_ac_lan/binary_sensor.py new file mode 100644 index 0000000..f956d4d --- /dev/null +++ b/custom_components/midea_ac_lan/binary_sensor.py @@ -0,0 +1,50 @@ +"""Binary sensor for Midea Lan.""" + +from typing import cast + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up sensors for device.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_sensors = config_entry.options.get(CONF_SENSORS, []) + binary_sensors = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.BINARY_SENSOR and entity_key in extra_sensors: + sensor = MideaBinarySensor(device, entity_key) + binary_sensors.append(sensor) + async_add_entities(binary_sensors) + + +class MideaBinarySensor(MideaEntity, BinarySensorEntity): + """Represent a Midea binary sensor.""" + + @property + def device_class(self) -> BinarySensorDeviceClass | None: + """Return device class.""" + return cast("BinarySensorDeviceClass", self._config.get("device_class")) + + @property + def is_on(self) -> bool: + """Return true if sensor state is on.""" + return cast("bool", self._device.get_attribute(self._entity_key)) diff --git a/custom_components/midea_ac_lan/climate.py b/custom_components/midea_ac_lan/climate.py new file mode 100644 index 0000000..f0f281a --- /dev/null +++ b/custom_components/midea_ac_lan/climate.py @@ -0,0 +1,707 @@ +"""Midea Climate entries.""" + +import logging +from typing import Any, ClassVar, TypeAlias, cast + +from homeassistant.components.climate import ( + ATTR_HVAC_MODE, + FAN_AUTO, + FAN_HIGH, + FAN_LOW, + FAN_MEDIUM, + PRESET_AWAY, + PRESET_BOOST, + PRESET_COMFORT, + PRESET_ECO, + PRESET_NONE, + PRESET_SLEEP, + SWING_BOTH, + SWING_HORIZONTAL, + SWING_OFF, + SWING_ON, + SWING_VERTICAL, + ClimateEntity, + ClimateEntityFeature, + HVACMode, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_DEVICE_ID, + CONF_SWITCHES, + MAJOR_VERSION, + MINOR_VERSION, + PRECISION_HALVES, + PRECISION_WHOLE, + Platform, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from midealocal.device import DeviceType +from midealocal.devices.ac import DeviceAttributes as ACAttributes +from midealocal.devices.ac import MideaACDevice +from midealocal.devices.c3 import DeviceAttributes as C3Attributes +from midealocal.devices.c3 import MideaC3Device +from midealocal.devices.cc import DeviceAttributes as CCAttributes +from midealocal.devices.cc import MideaCCDevice +from midealocal.devices.cf import DeviceAttributes as CFAttributes +from midealocal.devices.cf import MideaCFDevice +from midealocal.devices.fb import DeviceAttributes as FBAttributes +from midealocal.devices.fb import MideaFBDevice + +from .const import DEVICES, DOMAIN, FanSpeed +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + + +TEMPERATURE_MAX = 30 +TEMPERATURE_MIN = 16 + +FAN_SILENT = "silent" +FAN_FULL_SPEED = "full" + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up climate entries.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs: list[ + MideaACClimate + | MideaCCClimate + | MideaCFClimate + | MideaC3Climate + | MideaFBClimate + ] = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.CLIMATE and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == DeviceType.AC: + # add config_entry args to fix indoor_humidity error bug + devs.append(MideaACClimate(device, entity_key, config_entry)) + elif device.device_type == DeviceType.CC: + devs.append(MideaCCClimate(device, entity_key)) + elif device.device_type == DeviceType.CF: + devs.append(MideaCFClimate(device, entity_key)) + elif device.device_type == DeviceType.C3: + devs.append(MideaC3Climate(device, entity_key, config["zone"])) + elif device.device_type == DeviceType.FB: + devs.append(MideaFBClimate(device, entity_key)) + async_add_entities(devs) + + +MideaClimateDevice: TypeAlias = ( + MideaACDevice | MideaCCDevice | MideaCFDevice | MideaC3Device | MideaFBDevice +) + + +class MideaClimate(MideaEntity, ClimateEntity): + """Midea Climate Entries Base Class.""" + + # https://developers.home-assistant.io/blog/2024/01/24/climate-climateentityfeatures-expanded + _enable_turn_on_off_backwards_compatibility: bool = ( + False # maybe remove after 2025.1 + ) + + _device: MideaClimateDevice + + _attr_max_temp: float = TEMPERATURE_MAX + _attr_min_temp: float = TEMPERATURE_MIN + _attr_target_temperature_high: float | None = TEMPERATURE_MAX + _attr_target_temperature_low: float | None = TEMPERATURE_MIN + _attr_temperature_unit: str = UnitOfTemperature.CELSIUS + + def __init__(self, device: MideaClimateDevice, entity_key: str) -> None: + """Midea Climate entity init.""" + super().__init__(device, entity_key) + + @property + def supported_features(self) -> ClimateEntityFeature: + """Midea Climate supported features.""" + features = ( + ClimateEntityFeature.TARGET_TEMPERATURE + | ClimateEntityFeature.FAN_MODE + | ClimateEntityFeature.PRESET_MODE + | ClimateEntityFeature.SWING_MODE + ) + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def hvac_mode(self) -> HVACMode: + """Midea Climate hvac mode.""" + if self._device.get_attribute("power"): + mode = cast("int", self._device.get_attribute("mode")) + return self.hvac_modes[mode] + return HVACMode.OFF + + @property + def target_temperature(self) -> float: + """Midea Climate target temperature.""" + return cast("float", self._device.get_attribute("target_temperature")) + + @property + def current_temperature(self) -> float | None: + """Midea Climate current temperature.""" + return cast("float | None", self._device.get_attribute("indoor_temperature")) + + @property + def preset_mode(self) -> str: + """Midea Climate preset mode.""" + if self._device.get_attribute("comfort_mode"): + mode = PRESET_COMFORT + elif self._device.get_attribute("eco_mode"): + mode = PRESET_ECO + elif self._device.get_attribute("boost_mode"): + mode = PRESET_BOOST + elif self._device.get_attribute("sleep_mode"): + mode = PRESET_SLEEP + elif self._device.get_attribute("frost_protect"): + mode = PRESET_AWAY + else: + mode = PRESET_NONE + return str(mode) + + @property + def extra_state_attributes(self) -> dict: + """Midea Climate extra state attributes.""" + return cast("dict", self._device.attributes) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Climate turn on.""" + self._device.set_attribute(attr="power", value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Climate turn off.""" + self._device.set_attribute(attr="power", value=False) + + def set_temperature(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea Climate set temperature.""" + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(int((float(kwargs[ATTR_TEMPERATURE]) * 2) + 0.5)) / 2 + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + try: + mode = self.hvac_modes.index(hvac_mode.lower()) if hvac_mode else None + self._device.set_target_temperature( + target_temperature=temperature, + mode=mode, + zone=None, + ) + except ValueError: + _LOGGER.exception("Error setting temperature with: %s", kwargs) + + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Midea Climate set hvac mode.""" + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self._device.set_attribute( + attr="mode", + value=self.hvac_modes.index(hvac_mode), + ) + + def set_preset_mode(self, preset_mode: str) -> None: + """Midea Climate set preset mode.""" + old_mode = self.preset_mode + preset_mode = preset_mode.lower() + if preset_mode == PRESET_AWAY: + self._device.set_attribute(attr="frost_protect", value=True) + elif preset_mode == PRESET_COMFORT: + self._device.set_attribute(attr="comfort_mode", value=True) + elif preset_mode == PRESET_SLEEP: + self._device.set_attribute(attr="sleep_mode", value=True) + elif preset_mode == PRESET_ECO: + self._device.set_attribute(attr="eco_mode", value=True) + elif preset_mode == PRESET_BOOST: + self._device.set_attribute(attr="boost_mode", value=True) + elif old_mode == PRESET_AWAY: + self._device.set_attribute(attr="frost_protect", value=False) + elif old_mode == PRESET_COMFORT: + self._device.set_attribute(attr="comfort_mode", value=False) + elif old_mode == PRESET_SLEEP: + self._device.set_attribute(attr="sleep_mode", value=False) + elif old_mode == PRESET_ECO: + self._device.set_attribute(attr="eco_mode", value=False) + elif old_mode == PRESET_BOOST: + self._device.set_attribute(attr="boost_mode", value=False) + + def update_state(self, status: Any) -> None: # noqa: ANN401, ARG002 + """Midea Climate update state.""" + if not self.hass: + _LOGGER.warning( + "Climate update_state skipped for %s [%s]: HASS is None", + self.name, + type(self), + ) + return + self.schedule_update_ha_state() + + +class MideaACClimate(MideaClimate): + """Midea AC Climate Entries.""" + + _device: MideaACDevice + + def __init__( + self, + device: MideaACDevice, + entity_key: str, + config_entry: ConfigEntry, + ) -> None: + """Midea AC Climate entity init.""" + super().__init__(device, entity_key) + self._attr_hvac_modes = [ + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.COOL, + HVACMode.DRY, + HVACMode.HEAT, + HVACMode.FAN_ONLY, + ] + self._fan_speeds: dict[str, int] = { + FAN_SILENT: 20, + FAN_LOW: 40, + FAN_MEDIUM: 60, + FAN_HIGH: 80, + FAN_FULL_SPEED: 100, + FAN_AUTO: 102, + } + self._attr_swing_modes: list[str] = [ + SWING_OFF, + SWING_VERTICAL, + SWING_HORIZONTAL, + SWING_BOTH, + ] + self._attr_preset_modes = [ + PRESET_NONE, + PRESET_COMFORT, + PRESET_ECO, + PRESET_BOOST, + PRESET_SLEEP, + PRESET_AWAY, + ] + self._attr_fan_modes = list(self._fan_speeds.keys()) + # fix error humidity value, disable indoor_humidity + # add config_entry args to fix indoor_humidity error bug + self._indoor_humidity_enabled = ( + "sensors" in config_entry.options + and "indoor_humidity" in config_entry.options["sensors"] + ) + + @property + def fan_mode(self) -> str: + """Midea AC Climate fan mode.""" + fan_speed = cast("int", self._device.get_attribute(ACAttributes.fan_speed)) + if fan_speed > FanSpeed.AUTO: + return str(FAN_AUTO) + if fan_speed > FanSpeed.FULL_SPEED: + return str(FAN_FULL_SPEED) + if fan_speed > FanSpeed.HIGH: + return str(FAN_HIGH) + if fan_speed > FanSpeed.MEDIUM: + return str(FAN_MEDIUM) + if fan_speed > FanSpeed.LOW: + return str(FAN_LOW) + return str(FAN_SILENT) + + @property + def target_temperature_step(self) -> float: + """Midea AC Climate target temperature step.""" + return float( + PRECISION_WHOLE if self._device.temperature_step == 1 else PRECISION_HALVES, + ) + + @property + def swing_mode(self) -> str: + """Midea AC Climate swing mode.""" + swing_mode = ( + 1 if self._device.get_attribute(ACAttributes.swing_vertical) else 0 + ) + (2 if self._device.get_attribute(ACAttributes.swing_horizontal) else 0) + return self._attr_swing_modes[swing_mode] + + @property + def current_humidity(self) -> float | None: + """Return the current indoor humidity, or None if unavailable.""" + # fix error humidity, disable indoor_humidity in web UI + # https://github.com/wuwentao/midea_ac_lan/pull/641 + if not self._indoor_humidity_enabled: + return None + raw = self._device.get_attribute("indoor_humidity") + if isinstance(raw, (int, float)) and raw not in {0, 0xFF}: + return float(raw) + # indoor_humidity is 0 or 255, return None + # https://github.com/wuwentao/midea_ac_lan/pull/614 + return None + + @property + def outdoor_temperature(self) -> float: + """Midea AC Climate outdoor temperature.""" + return cast( + "float", + self._device.get_attribute(ACAttributes.outdoor_temperature), + ) + + def set_fan_mode(self, fan_mode: str) -> None: + """Midea AC Climate set fan mode.""" + fan_speed = self._fan_speeds.get(fan_mode) + if fan_speed: + self._device.set_attribute(attr=ACAttributes.fan_speed, value=fan_speed) + + def set_swing_mode(self, swing_mode: str) -> None: + """Midea AC Climate set swing mode.""" + swing = self._attr_swing_modes.index(swing_mode) + swing_vertical = swing & 1 > 0 + swing_horizontal = swing & 2 > 0 + self._device.set_swing( + swing_vertical=swing_vertical, + swing_horizontal=swing_horizontal, + ) + + +class MideaCCClimate(MideaClimate): + """Midea CC Climate Entries Base Class.""" + + _device: MideaCCDevice + + def __init__(self, device: MideaCCDevice, entity_key: str) -> None: + """Midea CC Climate entity init.""" + super().__init__(device, entity_key) + self._attr_hvac_modes = [ + HVACMode.OFF, + HVACMode.FAN_ONLY, + HVACMode.DRY, + HVACMode.HEAT, + HVACMode.COOL, + HVACMode.AUTO, + ] + self._attr_swing_modes = [SWING_OFF, SWING_ON] + self._attr_preset_modes = [PRESET_NONE, PRESET_SLEEP, PRESET_ECO] + + @property + def fan_modes(self) -> list[str] | None: + """Midea CC Climate fan modes.""" + return cast("list", self._device.fan_modes) + + @property + def fan_mode(self) -> str: + """Midea CC Climate fan mode.""" + return cast("str", self._device.get_attribute(CCAttributes.fan_speed)) + + @property + def target_temperature_step(self) -> float: + """Midea CC Climate target temperature step.""" + return cast( + "float", + self._device.get_attribute(CCAttributes.temperature_precision), + ) + + @property + def swing_mode(self) -> str: + """Midea CC Climate swing mode.""" + return str( + SWING_ON if self._device.get_attribute(CCAttributes.swing) else SWING_OFF, + ) + + def set_fan_mode(self, fan_mode: str) -> None: + """Midea CC Climate set fan mode.""" + self._device.set_attribute(attr=CCAttributes.fan_speed, value=fan_mode) + + def set_swing_mode(self, swing_mode: str) -> None: + """Midea CC Climate set swing mode.""" + self._device.set_attribute( + attr=CCAttributes.swing, + value=swing_mode == SWING_ON, + ) + + +class MideaCFClimate(MideaClimate): + """Midea CF Climate Entries.""" + + _device: MideaCFDevice + + _attr_target_temperature_step: float | None = PRECISION_WHOLE + + def __init__(self, device: MideaCFDevice, entity_key: str) -> None: + """Midea CF Climate entity init.""" + super().__init__(device, entity_key) + self._attr_hvac_modes = [ + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.COOL, + HVACMode.HEAT, + ] + + @property + def supported_features(self) -> ClimateEntityFeature: + """Midea CF Climate supported features.""" + features = ClimateEntityFeature.TARGET_TEMPERATURE + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def min_temp(self) -> float: + """Midea CF Climate min temperature.""" + return cast("float", self._device.get_attribute(CFAttributes.min_temperature)) + + @property + def max_temp(self) -> float: + """Midea CF Climate max temperature.""" + return cast("float", self._device.get_attribute(CFAttributes.max_temperature)) + + @property + def target_temperature_low(self) -> float: + """Midea CF Climate target temperature.""" + return cast("float", self._device.get_attribute(CFAttributes.min_temperature)) + + @property + def target_temperature_high(self) -> float: + """Midea CF Climate target temperature high.""" + return cast("float", self._device.get_attribute(CFAttributes.max_temperature)) + + @property + def current_temperature(self) -> float: + """Midea CF Climate current temperature.""" + return cast( + "float", + self._device.get_attribute(CFAttributes.current_temperature), + ) + + +class MideaC3Climate(MideaClimate): + """Midea C3 Climate Entries.""" + + _device: MideaC3Device + + _powers: ClassVar[list[C3Attributes]] = [ + C3Attributes.zone1_power, + C3Attributes.zone2_power, + ] + + def __init__(self, device: MideaC3Device, entity_key: str, zone: int) -> None: + """Midea C3 Climate entity init.""" + super().__init__(device, entity_key) + self._zone = zone + self._attr_hvac_modes = [ + HVACMode.OFF, + HVACMode.AUTO, + HVACMode.COOL, + HVACMode.HEAT, + ] + self._power_attr = MideaC3Climate._powers[zone] + + def _temperature(self, minimum: bool) -> list[str]: + """Midea C3 Climate temperature. + + Returns + ------- + List of temperatures + + """ + # fmt: off + value = (C3Attributes.temperature_min + if minimum + else C3Attributes.temperature_max) + # fmt: on + return cast("list[str]", self._device.get_attribute(value)) + + @property + def supported_features(self) -> ClimateEntityFeature: + """Midea C3 Climate supported features.""" + features = ClimateEntityFeature.TARGET_TEMPERATURE + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def target_temperature_step(self) -> float: + """Midea C3 Climate target temperature step.""" + zone_temp_type = cast( + "list[str]", + self._device.get_attribute(C3Attributes.zone_temp_type), + ) + return float( + PRECISION_WHOLE if zone_temp_type[self._zone] else PRECISION_HALVES, + ) + + @property + def min_temp(self) -> float: + """Midea C3 Climate min temperature.""" + return cast( + "float", + self._temperature(True)[self._zone], + ) + + @property + def max_temp(self) -> float: + """Midea C3 Climate max temperature.""" + return cast( + "float", + self._temperature(False)[self._zone], + ) + + @property + def target_temperature_low(self) -> float: + """Midea C3 Climate target temperature low.""" + return cast( + "float", + self._temperature(True)[self._zone], + ) + + @property + def target_temperature_high(self) -> float: + """Midea C3 Climate target temperature high.""" + return cast( + "float", + self._temperature(False)[self._zone], + ) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea C3 Climate turn on.""" + self._device.set_attribute(attr=self._power_attr, value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea C3 Climate turn off.""" + self._device.set_attribute(attr=self._power_attr, value=False) + + @property + def hvac_mode(self) -> HVACMode: + """Midea C3 Climate hvac mode.""" + mode = self._device.get_attribute(C3Attributes.mode) + if self._device.get_attribute(self._power_attr) and isinstance(mode, int): + return self.hvac_modes[mode] + return HVACMode.OFF + + @property + def target_temperature(self) -> float: + """Midea C3 Climate target temperature.""" + target_temperature = cast( + "list[str]", + self._device.get_attribute(C3Attributes.target_temperature), + ) + return cast( + "float", + target_temperature[self._zone], + ) + + @property + def current_temperature(self) -> float | None: + """Midea C3 Climate current temperature.""" + return None + + def set_temperature(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea C3 Climate set temperature.""" + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(int((float(kwargs[ATTR_TEMPERATURE]) * 2) + 0.5)) / 2 + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + try: + mode = self.hvac_modes.index(hvac_mode.lower()) if hvac_mode else None + self._device.set_target_temperature( + target_temperature=temperature, + mode=mode, + zone=self._zone, + ) + except ValueError: + _LOGGER.exception("Error setting temperature with: %s", kwargs) + + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Midea C3 Climate set hvac mode.""" + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self._device.set_mode(self._zone, self.hvac_modes.index(hvac_mode)) + + +class MideaFBClimate(MideaClimate): + """Midea FB Climate Entries.""" + + _device: MideaFBDevice + + _attr_max_temp: float = 35 + _attr_min_temp: float = 5 + _attr_target_temperature_high: float | None = 35 + _attr_target_temperature_low: float | None = 5 + _attr_target_temperature_step: float | None = PRECISION_WHOLE + + def __init__(self, device: MideaFBDevice, entity_key: str) -> None: + """Midea FB Climate entity init.""" + super().__init__(device, entity_key) + self._attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT] + self._attr_preset_modes: list[str] = self._device.modes + + @property + def supported_features(self) -> ClimateEntityFeature: + """Midea FB Climate supported features.""" + features = ( + ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE + ) + if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 2): + features |= ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON + return features + + @property + def preset_mode(self) -> str: + """Midea FB Climate preset mode.""" + return cast("str", self._device.get_attribute(attr=FBAttributes.mode)) + + @property + def hvac_mode(self) -> HVACMode: + """Midea FB Climate hvac mode.""" + return ( + HVACMode.HEAT + if self._device.get_attribute(attr=FBAttributes.power) + else HVACMode.OFF + ) + + @property + def current_temperature(self) -> float: + """Midea FB Climate current temperature.""" + return cast( + "float", + self._device.get_attribute(FBAttributes.current_temperature), + ) + + def set_temperature(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea FB Climate set temperature.""" + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(int((float(kwargs[ATTR_TEMPERATURE]) * 2) + 0.5)) / 2 + hvac_mode = kwargs.get(ATTR_HVAC_MODE) + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self._device.set_target_temperature( + target_temperature=temperature, + mode=None, + ) + + def set_hvac_mode(self, hvac_mode: HVACMode) -> None: + """Midea FB Climate set hvac mode.""" + if hvac_mode == HVACMode.OFF: + self.turn_off() + else: + self.turn_on() + + def set_preset_mode(self, preset_mode: str) -> None: + """Midea FB Climate set preset mode.""" + self._device.set_attribute(attr=FBAttributes.mode, value=preset_mode) diff --git a/custom_components/midea_ac_lan/config_flow.py b/custom_components/midea_ac_lan/config_flow.py new file mode 100644 index 0000000..456f641 --- /dev/null +++ b/custom_components/midea_ac_lan/config_flow.py @@ -0,0 +1,998 @@ +"""Config flow for Midea LAN. + +Setup current integration and add device entry via the web UI +enable by adding `config_flow: true` in `manifest.json` + +`MideaLanConfigFlow`: add device entry +`MideaLanOptionsFlowHandler`: update the options of a config entry + +job process: +1. run `async_step_user` when select `Add Device` from web UI +2. default auto discovery action run `async_step_discovery` +3. device available, run `async_step_auto` to show available device list in web UI + 3.1 check local device json with `_load_device_config` + 3.1.1 device exist with `_check_storage_device`, + send json data to `async_step_manually` + 3.1.2 device NOT exist, get device data from cloud, + send to `async_step_manually` + - check login with `async_step_login` +4. add selected device detail with `async_step_manually` +5. run `_save_device_config` and `async_create_entry` +""" + +import logging +from pathlib import Path +from typing import TYPE_CHECKING, Any, cast + +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow +from homeassistant.const import ( + CONF_CUSTOMIZE, + CONF_DEVICE, + CONF_DEVICE_ID, + CONF_IP_ADDRESS, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_PROTOCOL, + CONF_SENSORS, + CONF_SWITCHES, + CONF_TOKEN, + CONF_TYPE, + MAJOR_VERSION, + MINOR_VERSION, +) +from homeassistant.core import callback +from homeassistant.helpers.aiohttp_client import async_create_clientsession +from homeassistant.helpers.json import save_json +from homeassistant.util.json import load_json +from midealocal.cloud import ( + PRESET_ACCOUNT_DATA, + SUPPORTED_CLOUDS, + MideaCloud, + get_midea_cloud, +) +from midealocal.device import AuthException, MideaDevice, ProtocolVersion +from midealocal.discover import discover +from midealocal.exceptions import SocketException + +if TYPE_CHECKING: + from aiohttp import ClientSession + +if (MAJOR_VERSION, MINOR_VERSION) >= (2024, 4): + from homeassistant.config_entries import ConfigFlowResult # pylint: disable=E0611 +else: + from homeassistant.data_entry_flow import ( # type: ignore[assignment] + FlowResult as ConfigFlowResult, + ) + +from .const import ( + CONF_ACCOUNT, + CONF_KEY, + CONF_MODEL, + CONF_REFRESH_INTERVAL, + CONF_SERVER, + CONF_SUBTYPE, + DOMAIN, + EXTRA_CONTROL, + EXTRA_SENSOR, +) +from .midea_devices import MIDEA_DEVICES + +_LOGGER = logging.getLogger(__name__) + +ADD_WAY = { + "discovery": "Discover automatically", + "manually": "Configure manually", + "list": "List all appliances only", + "cache": "Remove login cache", +} + +# Select DEFAULT_CLOUD from the list of supported cloud +DEFAULT_CLOUD: str = list(SUPPORTED_CLOUDS)[3] + +STORAGE_PATH = f".storage/{DOMAIN}" + +SKIP_LOGIN = "Skip Login (input any user/password)" + + +class MideaLanConfigFlow(ConfigFlow, domain=DOMAIN): # type: ignore[call-arg] + """Define current integration setup steps. + + Use ConfigFlow handle to support config entries + ConfigFlow will manage the creation of entries from user input, discovery + """ + + VERSION = 2 + MINOR_VERSION = 1 + + def __init__(self) -> None: + """MideaLanConfigFlow class.""" + self.available_device: dict = {} + self.devices: dict = {} + self.found_device: dict[str, Any] = {} + self.supports: dict = {} + self.unsorted: dict[int, Any] = {} + self.account: dict = {} + self.cloud: MideaCloud | None = None + self.session: ClientSession | None = None + for device_type, device_info in MIDEA_DEVICES.items(): + self.unsorted[device_type] = device_info["name"] + + sorted_device_names = sorted(self.unsorted.items(), key=lambda x: x[1]) + for item in sorted_device_names: + self.supports[item[0]] = item[1] + # preset account + self.preset_account: str = bytes.fromhex( + format((PRESET_ACCOUNT_DATA[0] ^ PRESET_ACCOUNT_DATA[1]), "X"), + ).decode("utf-8", errors="ignore") + # preset password + self.preset_password: str = bytes.fromhex( + format((PRESET_ACCOUNT_DATA[0] ^ PRESET_ACCOUNT_DATA[2]), "X"), + ).decode("utf-8", errors="ignore") + self.preset_cloud_name: str = DEFAULT_CLOUD + + def _save_device_config(self, data: dict[str, Any]) -> None: + """Save device config to json file with device id.""" + storage_path = Path(self.hass.config.path(STORAGE_PATH)) + storage_path.mkdir(parents=True, exist_ok=True) + record_file = storage_path.joinpath(f"{data[CONF_DEVICE_ID]!s}.json") + save_json(str(record_file), data) + + def _load_device_config(self, device_id: str) -> Any: # noqa: ANN401 + """Load device config from json file with device id. + + Returns + ------- + Device configuration (json) + + """ + record_file = Path( + self.hass.config.path(f"{STORAGE_PATH}", f"{device_id}.json"), + ) + if record_file.exists(): + with record_file.open(encoding="utf-8") as f: + return load_json(f.name, default={}) + return {} + + @staticmethod + def _check_storage_device(device: dict, storage_device: dict) -> bool: + """Check input device with storage_device. + + Returns + ------- + True if storage device exist + + """ + if storage_device.get(CONF_SUBTYPE) is None: + return False + return not ( + device.get(CONF_PROTOCOL) == ProtocolVersion.V3 + and ( + storage_device.get(CONF_TOKEN) is None + or storage_device.get(CONF_KEY) is None + ) + ) + + def _already_configured(self, device_id: str, ip_address: str) -> bool: + """Check device from json with device_id or ip address. + + Returns + ------- + True if device is already configured + + """ + for entry in self._async_current_entries(): + if device_id == entry.data.get( + CONF_DEVICE_ID, + ) or ip_address == entry.data.get(CONF_IP_ADDRESS): + return True + return False + + async def async_step_user( + self, + user_input: dict[str, Any] | None = None, + error: str | None = None, + ) -> ConfigFlowResult: + """Define config flow steps. + + Using `async_step_` and `async_step_user` will be the first step, + then select discovery mode + + Returns + ------- + Config flow result + + """ + # user select a device discovery mode + if user_input is not None: + # default is auto discovery mode + if user_input["action"] == "discovery": + return await self.async_step_discovery() + # manual input device detail + if user_input["action"] == "manually": + self.found_device = {} + return await self.async_step_manually() + # remove cached login data and input new one + if user_input["action"] == "cache": + return await self.async_step_cache() + # only list all devices + return await self.async_step_list() + # user not input, show device discovery select form in UI + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + {vol.Required("action", default="discovery"): vol.In(ADD_WAY)}, + ), + errors={"base": error} if error else None, + ) + + async def async_step_cache( + self, + user_input: dict[str, Any] | None = None, + error: str | None = None, + ) -> ConfigFlowResult: + """Remove cached login data and can input a new one. + + Returns + ------- + Config flow result + + """ + # user input data exist + if user_input is not None: + # key is not None + if self.hass.data.get(DOMAIN): + self.hass.data[DOMAIN].pop("login_data", None) + self.hass.data[DOMAIN].pop("login_mode", None) + return await self.async_step_user() + # show cache info form in UI + return self.async_show_form( + step_id="cache", + data_schema=vol.Schema( + { + vol.Required("action", default="remove"): vol.In( + {"action": "remove"}, + ), + }, + ), + errors={"base": error} if error else None, + ) + + async def async_step_login( + self, + user_input: dict[str, Any] | None = None, + error: str | None = None, + ) -> ConfigFlowResult: + """User login steps. + + Returns + ------- + Config flow result + + """ + # get cloud servers configs + cloud_servers = await MideaCloud.get_cloud_servers() + default_keys = await MideaCloud.get_default_keys() + # add skip login option to web UI with key 99 + cloud_servers[next(iter(default_keys))] = SKIP_LOGIN + # user input data exist + if user_input is not None: + if not self.hass.data.get(DOMAIN): + self.hass.data[DOMAIN] = {} + # check skip login option + if user_input[CONF_SERVER] == next(iter(default_keys)): + # use preset account and DEFAULT_CLOUD cloud + _LOGGER.debug("skip login matched, cloud_servers: %s", cloud_servers) + # get DEFAULT_CLOUD key from dict + key = next( + key + for key, value in cloud_servers.items() + if value == DEFAULT_CLOUD + ) + cloud_server = cloud_servers[key] + account = self.preset_account + password = self.preset_password + # set a login_mode flag + self.hass.data[DOMAIN]["login_mode"] = "preset" + # use input data + else: + _LOGGER.debug("user input login matched") + cloud_server = cloud_servers[user_input[CONF_SERVER]] + account = user_input[CONF_ACCOUNT] + password = user_input[CONF_PASSWORD] + # set a login_mode flag + self.hass.data[DOMAIN]["login_mode"] = "input" + + # cloud login MUST pass with user input or perset account + if await self._check_cloud_login( + cloud_name=cloud_server, + account=account, + password=password, + force_login=True, + ): + # save passed account to cache, available before HA reboot + self.hass.data[DOMAIN]["login_data"] = { + CONF_ACCOUNT: account, + CONF_PASSWORD: password, + CONF_SERVER: cloud_server, + } + # return to next step after login pass + return await self.async_step_auto() + # return error with login failed + _LOGGER.debug( + "ERROR: Failed to login with %s account in %s server", + self.hass.data[DOMAIN]["login_mode"], + cloud_server, + ) + return await self.async_step_login(error="login_failed") + # user not login, show login form in UI + return self.async_show_form( + step_id="login", + data_schema=vol.Schema( + { + vol.Required(CONF_ACCOUNT): str, + vol.Required(CONF_PASSWORD): str, + vol.Required(CONF_SERVER, default=1): vol.In(cloud_servers), + }, + ), + errors={"base": error} if error else None, + ) + + async def async_step_list( + self, + error: str | None = None, + ) -> ConfigFlowResult: + """List all devices and show device info in web UI. + + Returns + ------- + Config flow result + + """ + # get all devices list + all_devices = discover() + # available devices exist + if len(all_devices) > 0: + table = ( + "Appliance code|Type|IP address|SN|Supported\n:--:|:--:|:--:|:--:|:--:" + ) + green = "YES" + red = "NO" + for device_id, device in all_devices.items(): + supported = device.get(CONF_TYPE) in self.supports + table += ( + f"\n{device_id}|{f'{device.get(CONF_TYPE):02X}'}|" + f"{device.get(CONF_IP_ADDRESS)}|" + f"{device.get('sn')}|" + f"{green if supported else red}" + ) + # no available device + else: + table = "Not found" + # show devices list result in UI + return self.async_show_form( + step_id="list", + description_placeholders={"table": table}, + errors={"base": error} if error else None, + ) + + async def async_step_discovery( + self, + discovery_info: dict[str, Any] | None = None, + error: str | None = None, + ) -> ConfigFlowResult: + """Discovery device with auto mode or ip address. + + Returns + ------- + Config flow result + + """ + # input is not None, using ip_address to discovery device + if discovery_info is not None: + # auto mode, ip_address is None + if discovery_info[CONF_IP_ADDRESS].lower() == "auto": + ip_address = None + # ip exist + else: + ip_address = discovery_info[CONF_IP_ADDRESS] + # use midea-local discover() to get devices list with ip_address + self.devices = discover(list(self.supports.keys()), ip_address=ip_address) + self.available_device = {} + for device_id, device in self.devices.items(): + # remove exist devices and only return new devices + if not self._already_configured( + str(device_id), + device[CONF_IP_ADDRESS], + ): + # fmt: off + self.available_device[device_id] = ( + f"{device_id} ({self.supports.get(device.get(CONF_TYPE))})" + ) + # fmt: on + if len(self.available_device) > 0: + return await self.async_step_auto() + return await self.async_step_discovery(error="no_devices") + # show discovery device input form with auto or ip address in web UI + return self.async_show_form( + step_id="discovery", + data_schema=vol.Schema( + {vol.Required(CONF_IP_ADDRESS, default="auto"): str}, + ), + errors={"base": error} if error else None, + ) + + async def _check_cloud_login( + self, + cloud_name: str | None = None, + account: str | None = None, + password: str | None = None, + force_login: bool = False, + ) -> bool: + """Check cloud login. + + Returns + ------- + True if cloud login succeeded + + """ + # set default args with perset account + if cloud_name is None or account is None or password is None: + cloud_name = self.preset_cloud_name + account = self.preset_account + password = self.preset_password + + if self.session is None: + self.session = async_create_clientsession(self.hass) + + # init cloud object or force reinit with new one + if self.cloud is None or force_login: + self.cloud = get_midea_cloud( + cloud_name, + self.session, + account, + password, + ) + # check cloud login after self.cloud exist + if await self.cloud.login(): + _LOGGER.debug( + "Using account %s login to %s cloud pass", + account, + cloud_name, + ) + return True + _LOGGER.debug( + "ERROR: unable to use account %s login to %s cloud", + account, + cloud_name, + ) + return False + + async def _check_key_from_cloud( + self, + appliance_id: int, + default_key: bool = True, + ) -> dict[str, Any]: + """Use perset DEFAULT_CLOUD account to get v3 device token and key. + + Returns + ------- + Dictionary of keys + + """ + device = self.devices[appliance_id] + + if self.cloud is None: + return {"error": "cloud_none"} + + # get device token/key from cloud + keys = await self.cloud.get_cloud_keys(appliance_id) + default_keys = await MideaCloud.get_default_keys() + # use token/key to connect device and confirm token result + for k, value in keys.items(): + # skip default_key + if not default_key and k == next(iter(default_keys)): + continue + dm = MideaDevice( + name="", + device_id=appliance_id, + device_type=device.get(CONF_TYPE), + ip_address=device.get(CONF_IP_ADDRESS), + port=device.get(CONF_PORT), + token=value["token"], + key=value["key"], + device_protocol=ProtocolVersion.V3, + model=device.get(CONF_MODEL), + subtype=0, + attributes={}, + ) + if dm.connect(): + try: + dm.authenticate() + except AuthException: + _LOGGER.debug("Unable to authenticate.") + dm.close_socket() + except SocketException: + _LOGGER.debug("Socket closed.") + else: + dm.close_socket() + return value + # return debug log with failed key + _LOGGER.debug( + "connect device using method %s token/key failed", + k, + ) + _LOGGER.debug( + "Unable to connect device with all the token/key", + ) + return {"error": "connect_error"} + + async def async_step_auto( + self, + user_input: dict[str, Any] | None = None, + error: str | None = None, + ) -> ConfigFlowResult: + """Discovery device detail info. + + Returns + ------- + Config flow result + + """ + # input device exist + if user_input is not None: + device_id = user_input[CONF_DEVICE] + device = self.devices[device_id] + # set device args with protocol decode data + # then get subtype from cloud, get v3 device token/key from cloud + self.found_device = { + CONF_DEVICE_ID: device_id, + CONF_TYPE: device.get(CONF_TYPE), + CONF_PROTOCOL: device.get(CONF_PROTOCOL), + CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), + CONF_PORT: device.get(CONF_PORT), + CONF_MODEL: device.get(CONF_MODEL), + } + storage_device = self._load_device_config(device_id) + # device config already exist, load from local json without cloud + if self._check_storage_device(device, storage_device): + self.found_device = { + CONF_DEVICE_ID: device_id, + CONF_TYPE: device.get(CONF_TYPE), + CONF_PROTOCOL: device.get(CONF_PROTOCOL), + CONF_IP_ADDRESS: device.get(CONF_IP_ADDRESS), + CONF_PORT: device.get(CONF_PORT), + CONF_MODEL: device.get(CONF_MODEL), + CONF_NAME: storage_device.get(CONF_NAME), + CONF_SUBTYPE: storage_device.get(CONF_SUBTYPE), + CONF_TOKEN: storage_device.get(CONF_TOKEN), + CONF_KEY: storage_device.get(CONF_KEY), + } + _LOGGER.debug( + "Loaded configuration for device %s from storage", + device_id, + ) + return await self.async_step_manually() + # device config not exist in local + # check login cache, show login web if no cache + if not self.hass.data.get(DOMAIN) or not self.hass.data[DOMAIN].get( + "login_data", + ): + return await self.async_step_login() + # login cached exist, cloud is None, reinit and login + if self.cloud is None and not await self._check_cloud_login( + cloud_name=self.hass.data[DOMAIN]["login_data"][CONF_SERVER], + account=self.hass.data[DOMAIN]["login_data"][CONF_ACCOUNT], + password=self.hass.data[DOMAIN]["login_data"][CONF_PASSWORD], + ): + # print error in debug log and show login web + _LOGGER.debug( + "Login with cached %s account %s failed in %s server", + self.hass.data[DOMAIN].get("login_mode"), + self.hass.data[DOMAIN]["login_data"][CONF_ACCOUNT], + self.hass.data[DOMAIN]["login_data"][CONF_SERVER], + ) + # remove error cache and relogin + self.hass.data[DOMAIN].pop("login_data", None) + self.hass.data[DOMAIN].pop("login_mode", None) + return await self.async_step_login() + + # get subtype from cloud + if self.cloud is not None and ( + device_info := await self.cloud.get_device_info(device_id) + ): + # set subtype with model_number + self.found_device[CONF_NAME] = device_info.get("name") + self.found_device[CONF_SUBTYPE] = device_info.get("model_number") + + # MUST get a auth passed token/key for v3 device, disable add before pass + if device.get(CONF_PROTOCOL) == ProtocolVersion.V3: + # phase 1, try with user input login data + keys = await self._check_key_from_cloud(device_id) + + # no available key, continue the phase 2 + if not keys.get("token") or not keys.get("key"): + _LOGGER.debug( + "Can't get valid token with %s account in %s server", + self.hass.data[DOMAIN]["login_mode"], + self.hass.data[DOMAIN]["login_data"][CONF_SERVER], + ) + + # exclude: user selected not login and phase 1 is preset account + if self.hass.data[DOMAIN]["login_mode"] == "preset": + return await self.async_step_auto( + error="can't get valid token from Midea server", + ) + + # get key phase 2: reinit cloud with preset account + if not await self._check_cloud_login(force_login=True): + return await self.async_step_auto( + error="Perset account login failed!", + ) + # try to get a passed key, without default_key + keys = await self._check_key_from_cloud( + device_id, + default_key=False, + ) + + # phase 2 got no available token/key, disable device add + if not keys.get("token") or not keys.get("key"): + _LOGGER.debug( + "Can't get available token from Midea server for device %s", + device_id, + ) + return await self.async_step_auto( + error=( + f"Can't get available token from Midea server" + f" for device {device_id}" + ), + ) + # get key pass + self.found_device[CONF_TOKEN] = keys["token"] + self.found_device[CONF_KEY] = keys["key"] + return await self.async_step_manually() + # v1/v2 device add without token/key + return await self.async_step_manually() + # show available device list in UI + return self.async_show_form( + step_id="auto", + data_schema=vol.Schema( + { + vol.Required( + CONF_DEVICE, + default=next(iter(self.available_device.keys())), + ): vol.In(self.available_device), + }, + ), + errors={"base": error} if error else None, + ) + + async def async_step_manually( + self, + user_input: dict[str, Any] | None = None, + error: str | None = None, + ) -> ConfigFlowResult: + """Add device with device detail info. + + Returns + ------- + Config flow result + + """ + if user_input is not None: + try: + bytearray.fromhex(user_input[CONF_TOKEN]) + bytearray.fromhex(user_input[CONF_KEY]) + except ValueError: + return await self.async_step_manually(error="invalid_token") + + device_id = user_input[CONF_DEVICE_ID] + # check device, discover already done or only manual add + if len(self.devices) < 1: + ip = user_input[CONF_IP_ADDRESS] + # discover device + self.devices = discover( + list(self.supports.keys()), + ip_address=ip, + ) + # discover result MUST exist + if len(self.devices) != 1: + return await self.async_step_manually(error="invalid_device_ip") + # check all the input, disable error add + device_id = next(iter(self.devices.keys())) + + # check if device_id is correctly set for that IP + if user_input[CONF_DEVICE_ID] != device_id: + return await self.async_step_manually( + error=f"For ip {ip} the device_id MUST be {device_id}", + ) + + device = self.devices[device_id] + if user_input[CONF_IP_ADDRESS] != device.get(CONF_IP_ADDRESS): + return await self.async_step_manually( + error=f"ip_address MUST be {device.get(CONF_IP_ADDRESS)}", + ) + if user_input[CONF_PROTOCOL] != device.get(CONF_PROTOCOL): + return await self.async_step_manually( + error=f"protocol MUST be {device.get(CONF_PROTOCOL)}", + ) + + # try to get token/key with preset account + if user_input[CONF_PROTOCOL] == ProtocolVersion.V3 and ( + len(user_input[CONF_TOKEN]) == 0 or len(user_input[CONF_KEY]) == 0 + ): + # init cloud with preset account + result = await self._check_cloud_login() + if not result: + return await self.async_step_manually( + error="Perset account login failed!", + ) + # try to get a passed key + keys = await self._check_key_from_cloud(int(user_input[CONF_DEVICE_ID])) + + # no available token/key, disable device add + if not keys.get("token") or not keys.get("key"): + _LOGGER.debug( + "Can't get a valid token from Midea server for device %s", + user_input[CONF_DEVICE_ID], + ) + return await self.async_step_manually( + error=( + f"Can't get a valid token from Midea server" + f" for device {user_input[CONF_DEVICE_ID]}" + ), + ) + + # set token/key from preset account + user_input[CONF_KEY] = keys["key"] + user_input[CONF_TOKEN] = keys["token"] + + self.found_device = { + CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], + CONF_TYPE: user_input[CONF_TYPE], + CONF_PROTOCOL: user_input[CONF_PROTOCOL], + CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], + CONF_PORT: user_input[CONF_PORT], + CONF_MODEL: user_input[CONF_MODEL], + CONF_TOKEN: user_input[CONF_TOKEN], + CONF_KEY: user_input[CONF_KEY], + } + + # check device connection with all the input + dm = MideaDevice( + name="", + device_id=user_input[CONF_DEVICE_ID], + device_type=user_input[CONF_TYPE], + ip_address=user_input[CONF_IP_ADDRESS], + port=user_input[CONF_PORT], + token=user_input[CONF_TOKEN], + key=user_input[CONF_KEY], + device_protocol=user_input[CONF_PROTOCOL], + model=user_input[CONF_MODEL], + subtype=0, + attributes={}, + ) + if dm.connect(): + try: + if user_input[CONF_PROTOCOL] == ProtocolVersion.V3: + dm.authenticate() + except SocketException: + _LOGGER.exception("Socket closed.") + except AuthException: + _LOGGER.exception( + "Unable to authenticate with provided key and token.", + ) + dm.close_socket() + else: + dm.close_socket() + data = { + CONF_NAME: user_input[CONF_NAME], + CONF_DEVICE_ID: user_input[CONF_DEVICE_ID], + CONF_TYPE: user_input[CONF_TYPE], + CONF_PROTOCOL: user_input[CONF_PROTOCOL], + CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS], + CONF_PORT: user_input[CONF_PORT], + CONF_MODEL: user_input[CONF_MODEL], + CONF_SUBTYPE: user_input[CONF_SUBTYPE], + CONF_TOKEN: user_input[CONF_TOKEN], + CONF_KEY: user_input[CONF_KEY], + } + # save device json config when adding new device + self._save_device_config(data) + # finish add device entry + return self.async_create_entry( + title=f"{user_input[CONF_NAME]}", + data=data, + ) + return await self.async_step_manually( + error="Device auth failed with input config", + ) + protocol = self.found_device.get(CONF_PROTOCOL) + return self.async_show_form( + step_id="manually", + data_schema=vol.Schema( + { + vol.Required( + CONF_NAME, + default=( + self.found_device.get(CONF_NAME) + if self.found_device.get(CONF_NAME) + else self.supports.get(self.found_device.get(CONF_TYPE)) + ), + ): str, + vol.Required( + CONF_DEVICE_ID, + default=self.found_device.get(CONF_DEVICE_ID), + ): int, + vol.Required( + CONF_TYPE, + default=( + self.found_device.get(CONF_TYPE) + if self.found_device.get(CONF_TYPE) + else 0xAC + ), + ): vol.In(self.supports), + vol.Required( + CONF_IP_ADDRESS, + default=self.found_device.get(CONF_IP_ADDRESS), + ): str, + vol.Required( + CONF_PORT, + default=( + self.found_device.get(CONF_PORT) + if self.found_device.get(CONF_PORT) + else 6444 + ), + ): int, + vol.Required( + CONF_PROTOCOL, + default=protocol or ProtocolVersion.V3, + ): vol.In( + [protocol] if protocol else ProtocolVersion, + ), + vol.Required( + CONF_MODEL, + default=( + self.found_device.get(CONF_MODEL) + if self.found_device.get(CONF_MODEL) + else "Unknown" + ), + ): str, + vol.Required( + CONF_SUBTYPE, + default=( + self.found_device.get(CONF_SUBTYPE) + if self.found_device.get(CONF_SUBTYPE) + else 0 + ), + ): int, + vol.Optional( + CONF_TOKEN, + default=( + self.found_device.get(CONF_TOKEN) + if self.found_device.get(CONF_TOKEN) + else "" + ), + ): str, + vol.Optional( + CONF_KEY, + default=( + self.found_device.get(CONF_KEY) + if self.found_device.get(CONF_KEY) + else "" + ), + ): str, + }, + ), + errors={"base": error} if error else None, + ) + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: + """Create the options flow with MideaLanOptionsFlowHandler. + + Returns + ------- + Config flow options handler + + """ + return MideaLanOptionsFlowHandler(config_entry) + + +class MideaLanOptionsFlowHandler(OptionsFlow): + """define an Options Flow Handler to update the options of a config entry.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self._config_entry = config_entry + self._device_type = config_entry.data.get(CONF_TYPE) + if self._device_type is None: + self._device_type = 0xAC + if CONF_SENSORS in self._config_entry.options: + for key in self._config_entry.options[CONF_SENSORS]: + if key not in MIDEA_DEVICES[self._device_type]["entities"]: + self._config_entry.options[CONF_SENSORS].remove(key) + if CONF_SWITCHES in self._config_entry.options: + for key in self._config_entry.options[CONF_SWITCHES]: + if key not in MIDEA_DEVICES[self._device_type]["entities"]: + self._config_entry.options[CONF_SWITCHES].remove(key) + + async def async_step_init( + self, + user_input: dict[str, Any] | None = None, + ) -> ConfigFlowResult: + """Manage the options. + + Returns + ------- + Config flow result + + """ + if self._device_type == CONF_ACCOUNT: + return self.async_abort(reason="account_option") + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + sensors = {} + switches = {} + for attribute, attribute_config in cast( + "dict", + MIDEA_DEVICES[cast("int", self._device_type)]["entities"], + ).items(): + attribute_name = ( + attribute if isinstance(attribute, str) else attribute.value + ) + if attribute_config.get("type") in EXTRA_SENSOR: + sensors[attribute_name] = attribute_config.get("name") + elif attribute_config.get( + "type", + ) in EXTRA_CONTROL and not attribute_config.get("default"): + switches[attribute_name] = attribute_config.get("name") + ip_address = self._config_entry.options.get(CONF_IP_ADDRESS, None) + if ip_address is None: + ip_address = self._config_entry.data.get(CONF_IP_ADDRESS, None) + refresh_interval = self._config_entry.options.get(CONF_REFRESH_INTERVAL, 30) + extra_sensors = list( + set(sensors.keys()) & set(self._config_entry.options.get(CONF_SENSORS, [])), + ) + extra_switches = list( + set(switches.keys()) + & set(self._config_entry.options.get(CONF_SWITCHES, [])), + ) + customize = self._config_entry.options.get(CONF_CUSTOMIZE, "") + data_schema = vol.Schema( + { + vol.Required(CONF_IP_ADDRESS, default=ip_address): str, + vol.Required(CONF_REFRESH_INTERVAL, default=refresh_interval): int, + }, + ) + if len(sensors) > 0: + data_schema = data_schema.extend( + { + vol.Required( + CONF_SENSORS, + default=extra_sensors, + ): cv.multi_select(sensors), + }, + ) + if len(switches) > 0: + data_schema = data_schema.extend( + { + vol.Required( + CONF_SWITCHES, + default=extra_switches, + ): cv.multi_select(switches), + }, + ) + data_schema = data_schema.extend( + { + vol.Optional( + CONF_CUSTOMIZE, + default=customize, + ): str, + }, + ) + + return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/custom_components/midea_ac_lan/const.py b/custom_components/midea_ac_lan/const.py new file mode 100644 index 0000000..9a21b78 --- /dev/null +++ b/custom_components/midea_ac_lan/const.py @@ -0,0 +1,38 @@ +"""Const for Midea Lan.""" + +from enum import IntEnum + +from homeassistant.const import Platform + +DOMAIN = "midea_ac_lan" +COMPONENT = "component" +DEVICES = "devices" + +CONF_KEY = "key" +CONF_MODEL = "model" +CONF_SUBTYPE = "subtype" +CONF_ACCOUNT = "account" +CONF_SERVER = "server" +CONF_REFRESH_INTERVAL = "refresh_interval" + +EXTRA_SENSOR = [Platform.SENSOR, Platform.BINARY_SENSOR] +EXTRA_SWITCH = [Platform.SWITCH, Platform.LOCK, Platform.SELECT, Platform.NUMBER] +EXTRA_CONTROL = [ + Platform.CLIMATE, + Platform.WATER_HEATER, + Platform.FAN, + Platform.HUMIDIFIER, + Platform.LIGHT, + *EXTRA_SWITCH, +] +ALL_PLATFORM = EXTRA_SENSOR + EXTRA_CONTROL + + +class FanSpeed(IntEnum): + """FanSpeed reference values.""" + + LOW = 20 + MEDIUM = 40 + HIGH = 60 + FULL_SPEED = 80 + AUTO = 100 diff --git a/custom_components/midea_ac_lan/diagnostics.py b/custom_components/midea_ac_lan/diagnostics.py new file mode 100644 index 0000000..3ecd6f3 --- /dev/null +++ b/custom_components/midea_ac_lan/diagnostics.py @@ -0,0 +1,32 @@ +"""Diagnostics support for Midea AC LAN.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from homeassistant.components.diagnostics import async_redact_data +from homeassistant.const import ( + CONF_TOKEN, +) + +if TYPE_CHECKING: + from homeassistant.config_entries import ConfigEntry + from homeassistant.core import HomeAssistant + +from .const import CONF_KEY + +TO_REDACT = {CONF_TOKEN, CONF_KEY} + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, # noqa: ARG001 + entry: ConfigEntry, +) -> dict[str, Any]: + """Return diagnostics for a config entry. + + Returns + ------- + Dictionary of config + + """ + return {"entry": async_redact_data(entry.as_dict(), TO_REDACT)} diff --git a/custom_components/midea_ac_lan/fan.py b/custom_components/midea_ac_lan/fan.py new file mode 100644 index 0000000..5f6bde3 --- /dev/null +++ b/custom_components/midea_ac_lan/fan.py @@ -0,0 +1,320 @@ +"""Midea Fan entries.""" + +import logging +from typing import Any, cast + +from homeassistant.components.fan import FanEntity, FanEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_DEVICE_ID, + CONF_SWITCHES, + Platform, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from midealocal.device import DeviceType +from midealocal.devices.ac import DeviceAttributes as ACAttributes +from midealocal.devices.ac import MideaACDevice +from midealocal.devices.b6 import MideaB6Device +from midealocal.devices.ce import DeviceAttributes as CEAttributes +from midealocal.devices.ce import MideaCEDevice +from midealocal.devices.fa import MideaFADevice +from midealocal.devices.x40 import DeviceAttributes as X40Attributes +from midealocal.devices.x40 import MideaX40Device + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up fan entries.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs: list[ + MideaFAFan | MideaB6Fan | MideaACFreshAirFan | MideaCEFan | MideaX40Fan + ] = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.FAN and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == DeviceType.FA: + devs.append(MideaFAFan(device, entity_key)) + elif device.device_type == DeviceType.B6: + devs.append(MideaB6Fan(device, entity_key)) + elif device.device_type == DeviceType.AC: + devs.append(MideaACFreshAirFan(device, entity_key)) + elif device.device_type == DeviceType.CE: + devs.append(MideaCEFan(device, entity_key)) + elif device.device_type == DeviceType.X40: + devs.append(MideaX40Fan(device, entity_key)) + async_add_entities(devs) + + +# HA version >= 2024,8 support TURN_ON | TURN_OFF,for future changes, ref PR #285 +try: + FAN_FEATURE_TURN_ON_OFF = FanEntityFeature["TURN_ON"] | FanEntityFeature["TURN_OFF"] +except KeyError: + FAN_FEATURE_TURN_ON_OFF = FanEntityFeature(0) + + +class MideaFan(MideaEntity, FanEntity): + """Midea Fan Entries Base Class.""" + + _enable_turn_on_off_backwards_compatibility = False # 2024.8~2025.1 + + @property + def preset_modes(self) -> list[str] | None: + """Midea Fan preset modes.""" + return ( + self._device.preset_modes if hasattr(self._device, "preset_modes") else None + ) + + @property + def is_on(self) -> bool: + """Midea Fan is on.""" + return cast("bool", self._device.get_attribute("power")) + + @property + def oscillating(self) -> bool: + """Midea Fan oscillating.""" + return cast("bool", self._device.get_attribute("oscillate")) + + @property + def preset_mode(self) -> str | None: + """Midea Fan preset mode.""" + return cast("str", self._device.get_attribute("mode")) + + @property + def fan_speed(self) -> int | None: + """Midea Fan fan speed.""" + return cast("int", self._device.get_attribute("fan_speed")) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Fan turn off.""" + self._device.set_attribute(attr="power", value=False) + + def oscillate(self, oscillating: bool) -> None: + """Midea Fan oscillate.""" + self._device.set_attribute(attr="oscillate", value=oscillating) + + def set_preset_mode(self, preset_mode: str) -> None: + """Midea Fan set preset mode.""" + self._device.set_attribute(attr="mode", value=preset_mode.capitalize()) + + @property + def percentage(self) -> int | None: + """Midea Fan percentage.""" + if not self.fan_speed: + return None + return int(round(self.fan_speed * self.percentage_step)) # noqa: RUF046 + + def set_percentage(self, percentage: int) -> None: + """Midea Fan set percentage.""" + fan_speed = round(percentage / self.percentage_step) + self._device.set_attribute(attr="fan_speed", value=fan_speed) + + async def async_set_percentage(self, percentage: int) -> None: + """Midea Fan async set percentage.""" + if percentage == 0: + await self.async_turn_off() + else: + await self.hass.async_add_executor_job(self.set_percentage, percentage) + + def update_state(self, status: Any) -> None: # noqa: ANN401, ARG002 + """Midea Fan update state.""" + if not self.hass: + _LOGGER.warning( + "Fan update_state skipped for %s [%s]: HASS is None", + self.name, + type(self), + ) + return + self.schedule_update_ha_state() + + +class MideaFAFan(MideaFan): + """Midea FA Fan Entries.""" + + _device: MideaFADevice + + def __init__(self, device: MideaFADevice, entity_key: str) -> None: + """Midea FA Fan entity init.""" + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.OSCILLATE + | FanEntityFeature.PRESET_MODE + | FAN_FEATURE_TURN_ON_OFF + ) + self._attr_speed_count = self._device.speed_count + + def turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, # noqa: ANN401, ARG002 + ) -> None: + """Midea FA Fan turn on.""" + fan_speed = int(percentage / self.percentage_step + 0.5) if percentage else None + self._device.turn_on(fan_speed=fan_speed, mode=preset_mode) + + +class MideaB6Fan(MideaFan): + """Midea B6 Fan Entries.""" + + _device: MideaB6Device + + def __init__(self, device: MideaB6Device, entity_key: str) -> None: + """Midea B6 Fan entity init.""" + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.PRESET_MODE + | FAN_FEATURE_TURN_ON_OFF + ) + self._attr_speed_count = self._device.speed_count + + def turn_on( + self, + percentage: int | None = None, + preset_mode: str | None = None, + **kwargs: Any, # noqa: ANN401, ARG002 + ) -> None: + """Midea B6 Fan turn on.""" + fan_speed = int(percentage / self.percentage_step + 0.5) if percentage else None + self._device.turn_on(fan_speed=fan_speed, mode=preset_mode) + + +class MideaACFreshAirFan(MideaFan): + """Midea AC Fresh Air Fan Entries.""" + + _device: MideaACDevice + + def __init__(self, device: MideaACDevice, entity_key: str) -> None: + """Midea AC Fresh Air Fan entity init.""" + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.PRESET_MODE + | FAN_FEATURE_TURN_ON_OFF + ) + self._attr_speed_count = 100 + + @property + def preset_modes(self) -> list[str] | None: + """Midea AC Fan preset modes.""" + return cast("list", self._device.fresh_air_fan_speeds) + + @property + def is_on(self) -> bool: + """Midea AC Fan is on.""" + return cast("bool", self._device.get_attribute(ACAttributes.fresh_air_power)) + + @property + def fan_speed(self) -> int: + """Midea AC Fan fan speed.""" + return cast("int", self._device.get_attribute(ACAttributes.fresh_air_fan_speed)) + + def turn_on( + self, + percentage: int | None = None, # noqa: ARG002 + preset_mode: str | None = None, # noqa: ARG002 + **kwargs: Any, # noqa: ANN401, ARG002 + ) -> None: + """Midea AC Fan tun on.""" + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea AC Fan turn off.""" + self._device.set_attribute(attr=ACAttributes.fresh_air_power, value=False) + + def set_percentage(self, percentage: int) -> None: + """Midea AC Fan set percentage.""" + fan_speed = int(percentage / self.percentage_step + 0.5) + self._device.set_attribute( + attr=ACAttributes.fresh_air_fan_speed, + value=fan_speed, + ) + + def set_preset_mode(self, preset_mode: str) -> None: + """Midea AC Fan set preset mode.""" + self._device.set_attribute(attr=ACAttributes.fresh_air_mode, value=preset_mode) + + @property + def preset_mode(self) -> str | None: + """Midea AC Fan preset mode.""" + return cast("str", self._device.get_attribute(attr=ACAttributes.fresh_air_mode)) + + +class MideaCEFan(MideaFan): + """Midea CE Fan Entries.""" + + _device: MideaCEDevice + + def __init__(self, device: MideaCEDevice, entity_key: str) -> None: + """Midea CE Fan entity init.""" + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.PRESET_MODE + | FAN_FEATURE_TURN_ON_OFF + ) + self._attr_speed_count = self._device.speed_count + + def turn_on( + self, + percentage: int | None = None, # noqa: ARG002 + preset_mode: str | None = None, # noqa: ARG002 + **kwargs: Any, # noqa: ANN401, ARG002 + ) -> None: + """Midea CE Fan turn on.""" + self._device.set_attribute(attr=CEAttributes.power, value=True) + + async def async_set_percentage(self, percentage: int) -> None: + """Midea CE Fan async set percentage.""" + await self.hass.async_add_executor_job(self.set_percentage, percentage) + + +class MideaX40Fan(MideaFan): + """Midea X40 Fan Entries.""" + + _device: MideaX40Device + + def __init__(self, device: MideaX40Device, entity_key: str) -> None: + """Midea X40 Fan entity init.""" + super().__init__(device, entity_key) + self._attr_supported_features = ( + FanEntityFeature.SET_SPEED + | FanEntityFeature.OSCILLATE + | FAN_FEATURE_TURN_ON_OFF + ) + self._attr_speed_count = 2 + + @property + def is_on(self) -> bool: + """Midea X40 Fan is on.""" + return cast("int", self._device.get_attribute(attr=X40Attributes.fan_speed)) > 0 + + def turn_on( + self, + percentage: int | None = None, # noqa: ARG002 + preset_mode: str | None = None, # noqa: ARG002 + **kwargs: Any, # noqa: ANN401, ARG002 + ) -> None: + """Midea X40 Fan turn on.""" + self._device.set_attribute(attr=X40Attributes.fan_speed, value=1) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea X40 Fan turn off.""" + self._device.set_attribute(attr=X40Attributes.fan_speed, value=0) diff --git a/custom_components/midea_ac_lan/humidifier.py b/custom_components/midea_ac_lan/humidifier.py new file mode 100644 index 0000000..c186a00 --- /dev/null +++ b/custom_components/midea_ac_lan/humidifier.py @@ -0,0 +1,140 @@ +"""Midea Humidifier entries.""" + +import logging +from typing import Any, TypeAlias, cast + +from homeassistant.components.humidifier import ( + HumidifierDeviceClass, + HumidifierEntity, + HumidifierEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from midealocal.device import DeviceType +from midealocal.devices.a1 import MideaA1Device +from midealocal.devices.fd import MideaFDDevice + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up humidifier entries.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs: list[MideaA1Humidifier | MideaFDHumidifier] = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.HUMIDIFIER and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == DeviceType.A1: + devs.append(MideaA1Humidifier(device, entity_key)) + if device.device_type == DeviceType.FD: + devs.append(MideaFDHumidifier(device, entity_key)) + async_add_entities(devs) + + +MideaHumidifierDevice: TypeAlias = MideaFDDevice | MideaA1Device + + +class MideaHumidifier(MideaEntity, HumidifierEntity): + """Midea Humidifier Entries Base Class.""" + + _device: MideaHumidifierDevice + + def __init__(self, device: MideaHumidifierDevice, entity_key: str) -> None: + """Midea Humidifier entity init.""" + super().__init__(device, entity_key) + + @property + def current_humidity(self) -> float | None: + """Midea Humidifier current humidity.""" + return cast("float", self._device.get_attribute("current_humidity")) + + @property + def target_humidity(self) -> float: + """Midea Humidifier target humidity.""" + return cast("float", self._device.get_attribute("target_humidity")) + + @property + def mode(self) -> str: + """Midea Humidifier mode.""" + return cast("str", self._device.get_attribute("mode")) + + @property + def available_modes(self) -> list[str] | None: + """Midea Humidifier available modes.""" + return cast("list", self._device.modes) + + def set_humidity(self, humidity: int) -> None: + """Midea Humidifier set humidity.""" + self._device.set_attribute("target_humidity", humidity) + + def set_mode(self, mode: str) -> None: + """Midea Humidifier set mode.""" + self._device.set_attribute("mode", mode) + + @property + def is_on(self) -> bool: + """Midea Humidifier is on.""" + return cast("bool", self._device.get_attribute(attr="power")) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Humidifier turn on.""" + self._device.set_attribute(attr="power", value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Humidifier turn off.""" + self._device.set_attribute(attr="power", value=False) + + def update_state(self, status: Any) -> None: # noqa: ANN401, ARG002 + """Midea Humidifier update state.""" + if not self.hass: + _LOGGER.warning( + "Humidifier update_state skipped for %s [%s]: HASS is None", + self.name, + type(self), + ) + return + self.schedule_update_ha_state() + + +class MideaA1Humidifier(MideaHumidifier): + """Midea A1 Humidifier Entries.""" + + _device: MideaA1Device + + def __init__(self, device: MideaA1Device, entity_key: str) -> None: + """Midea A1 Humidifier entity init.""" + super().__init__(device, entity_key) + self._attr_min_humidity: float = 35 + self._attr_max_humidity: float = 85 + self._attr_device_class = HumidifierDeviceClass.DEHUMIDIFIER + self._attr_supported_features = HumidifierEntityFeature.MODES + + +class MideaFDHumidifier(MideaHumidifier): + """Midea FD Humidifier Entries.""" + + _device: MideaFDDevice + + def __init__(self, device: MideaFDDevice, entity_key: str) -> None: + """Midea FD Humidifier entity init.""" + super().__init__(device, entity_key) + self._attr_min_humidity: float = 35 + self._attr_max_humidity: float = 85 + self._attr_device_class = HumidifierDeviceClass.HUMIDIFIER + self._attr_supported_features = HumidifierEntityFeature.MODES diff --git a/custom_components/midea_ac_lan/light.py b/custom_components/midea_ac_lan/light.py new file mode 100644 index 0000000..00f27a6 --- /dev/null +++ b/custom_components/midea_ac_lan/light.py @@ -0,0 +1,184 @@ +"""Midea Light entries.""" + +import logging +from typing import Any, cast + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_TEMP_KELVIN, + ATTR_EFFECT, + ColorMode, + LightEntity, + LightEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from midealocal.devices.x13 import DeviceAttributes as X13Attributes +from midealocal.devices.x13 import Midea13Device + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up light entries.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.LIGHT and ( + config.get("default") or entity_key in extra_switches + ): + devs.append(MideaLight(device, entity_key)) + async_add_entities(devs) + + +def _calc_supported_features(device: Midea13Device) -> LightEntityFeature: + supported_features = LightEntityFeature(0) + if device.get_attribute(X13Attributes.effect): + supported_features |= LightEntityFeature.EFFECT + return supported_features + + +def _calc_supported_color_modes(device: Midea13Device) -> set[ColorMode]: + # https://github.com/home-assistant/core/blob/c34731185164aaf44419977c4086e9a7dd6c0a7f/homeassistant/components/light/__init__.py#L1278 + supported = set[ColorMode]() + + if device.get_attribute(X13Attributes.color_temperature) is not None: + supported.add(ColorMode.COLOR_TEMP) + if device.get_attribute(X13Attributes.rgb_color) is not None: + supported.add(ColorMode.HS) + if not supported and device.get_attribute(X13Attributes.brightness) is not None: + supported = {ColorMode.BRIGHTNESS} + + if not supported: + supported = {ColorMode.ONOFF} + + return supported + + +class MideaLight(MideaEntity, LightEntity): + """Midea Light Entries.""" + + _attr_color_mode: ColorMode | str | None = None + _attr_supported_color_modes: set[ColorMode] | set[str] | None = None + _attr_supported_features: LightEntityFeature = LightEntityFeature(0) + + _device: Midea13Device + + def __init__(self, device: Midea13Device, entity_key: str) -> None: + """Midea Light entity init.""" + super().__init__(device, entity_key) + self._attr_supported_features = _calc_supported_features(device) + self._attr_supported_color_modes = _calc_supported_color_modes(device) + self._attr_color_mode = self._calc_color_mode(self._attr_supported_color_modes) + + def _calc_color_mode(self, supported: set[ColorMode]) -> ColorMode: + """Midea Light calculate color mode. + + Returns + ------- + Calculated color mode + + """ + # https://github.com/home-assistant/core/blob/c34731185164aaf44419977c4086e9a7dd6c0a7f/homeassistant/components/light/__init__.py#L925 + if ColorMode.HS in supported and self.hs_color is not None: + return ColorMode.HS + if ColorMode.COLOR_TEMP in supported and self.color_temp_kelvin is not None: + return ColorMode.COLOR_TEMP + if ColorMode.BRIGHTNESS in supported and self.brightness is not None: + return ColorMode.BRIGHTNESS + if ColorMode.ONOFF in supported: + return ColorMode.ONOFF + return ColorMode.UNKNOWN + + @property + def is_on(self) -> bool: + """Midea Light is on.""" + return cast("bool", self._device.get_attribute(X13Attributes.power)) + + @property + def brightness(self) -> int | None: + """Midea Light brightness.""" + return cast("int", self._device.get_attribute(X13Attributes.brightness)) + + @property + def rgb_color(self) -> tuple[int, int, int] | None: + """Midea Light rgb color.""" + return cast("tuple", self._device.get_attribute(X13Attributes.rgb_color)) + + @property + def color_temp(self) -> int | None: + """Midea Light color temperature.""" + if not self.color_temp_kelvin: + return None + return round(1000000 / self.color_temp_kelvin) + + # https://developers.home-assistant.io/blog/2024/12/14/kelvin-preferred-color-temperature-unit + @property + def color_temp_kelvin(self) -> int | None: + """Midea Light color temperature kelvin.""" + return cast("int", self._device.get_attribute(X13Attributes.color_temperature)) + + @property + def min_color_temp_kelvin(self) -> int: + """Midea Light min color temperature kelvin.""" + return self._device.color_temp_range[0] + + @property + def max_color_temp_kelvin(self) -> int: + """Midea Light max color temperature kelvin.""" + return self._device.color_temp_range[1] + + @property + def effect_list(self) -> list[str] | None: + """Midea Light effect list.""" + return cast("list", self._device.effects) + + @property + def effect(self) -> str | None: + """Midea Light effect.""" + return cast("str", self._device.get_attribute(X13Attributes.effect)) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea Light turn on.""" + if not self.is_on: + self._device.set_attribute(attr=X13Attributes.power, value=True) + for key, value in kwargs.items(): + if key == ATTR_BRIGHTNESS: + self._device.set_attribute(attr=X13Attributes.brightness, value=value) + if key == ATTR_COLOR_TEMP_KELVIN: + self._device.set_attribute( + attr=X13Attributes.color_temperature, + value=value, + ) + if key == ATTR_EFFECT: + self._device.set_attribute(attr=X13Attributes.effect, value=value) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Light turn off.""" + self._device.set_attribute(attr=X13Attributes.power, value=False) + + def update_state(self, status: Any) -> None: # noqa: ANN401,ARG002 + """Midea Light update state.""" + if not self.hass: + _LOGGER.warning( + "Light update_state skipped for %s [%s]: HASS is None", + self.name, + type(self), + ) + return + self.schedule_update_ha_state() diff --git a/custom_components/midea_ac_lan/lock.py b/custom_components/midea_ac_lan/lock.py new file mode 100644 index 0000000..e66f974 --- /dev/null +++ b/custom_components/midea_ac_lan/lock.py @@ -0,0 +1,54 @@ +"""Lock entities for Midea Lan.""" + +from typing import Any, cast + +from homeassistant.components.lock import LockEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up entities for device.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + locks = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.LOCK and entity_key in extra_switches: + dev = MideaLock(device, entity_key) + locks.append(dev) + async_add_entities(locks) + + +class MideaLock(MideaEntity, LockEntity): + """Represent a Midea lock entity.""" + + @property + def is_locked(self) -> bool: + """Return true if state is locked.""" + return cast("bool", self._device.get_attribute(self._entity_key)) + + def lock(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Lock the lock.""" + self._device.set_attribute(attr=self._entity_key, value=True) + + def unlock(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Unlock the lock.""" + self._device.set_attribute(attr=self._entity_key, value=False) + + def open(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Open the lock.""" + self.unlock() diff --git a/custom_components/midea_ac_lan/manifest.json b/custom_components/midea_ac_lan/manifest.json new file mode 100644 index 0000000..265543d --- /dev/null +++ b/custom_components/midea_ac_lan/manifest.json @@ -0,0 +1,14 @@ +{ + "domain": "midea_ac_lan", + "name": "Midea AC LAN", + "codeowners": ["@wuwentao", "@rokam", "@chemelli74", "@Necroneco"], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/wuwentao/midea_ac_lan#readme", + "integration_type": "device", + "iot_class": "local_push", + "issue_tracker": "https://github.com/wuwentao/midea_ac_lan/issues", + "loggers": ["midealocal"], + "requirements": ["midea-local==6.6.0"], + "version": "v0.6.11" +} diff --git a/custom_components/midea_ac_lan/midea_devices.py b/custom_components/midea_ac_lan/midea_devices.py new file mode 100644 index 0000000..0805dc1 --- /dev/null +++ b/custom_components/midea_ac_lan/midea_devices.py @@ -0,0 +1,3060 @@ +"""Devices configuration for Midea Lan.""" + +from typing import Any + +from homeassistant.components.binary_sensor import BinarySensorDeviceClass +from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass +from homeassistant.const import ( + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + CONCENTRATION_PARTS_PER_MILLION, + PERCENTAGE, + Platform, + UnitOfEnergy, + UnitOfPower, + UnitOfTemperature, + UnitOfTime, + UnitOfVolume, +) +from midealocal.devices.a1 import DeviceAttributes as A1Attributes +from midealocal.devices.ac import DeviceAttributes as ACAttributes +from midealocal.devices.ad import DeviceAttributes as ADAttributes +from midealocal.devices.b0 import DeviceAttributes as B0Attributes +from midealocal.devices.b1 import DeviceAttributes as B1Attributes +from midealocal.devices.b3 import DeviceAttributes as B3Attributes +from midealocal.devices.b4 import DeviceAttributes as B4Attributes +from midealocal.devices.b6 import DeviceAttributes as B6Attributes +from midealocal.devices.bf import DeviceAttributes as BFAttributes +from midealocal.devices.c2 import DeviceAttributes as C2Attributes +from midealocal.devices.c3 import DeviceAttributes as C3Attributes +from midealocal.devices.ca import DeviceAttributes as CAAttributes +from midealocal.devices.cc import DeviceAttributes as CCAttributes +from midealocal.devices.cd import DeviceAttributes as CDAttributes +from midealocal.devices.ce import DeviceAttributes as CEAttributes +from midealocal.devices.cf import DeviceAttributes as CFAttributes +from midealocal.devices.da import DeviceAttributes as DAAttributes +from midealocal.devices.db import DeviceAttributes as DBAttributes +from midealocal.devices.dc import DeviceAttributes as DCAttributes +from midealocal.devices.e1 import DeviceAttributes as E1Attributes +from midealocal.devices.e2 import DeviceAttributes as E2Attributes +from midealocal.devices.e3 import DeviceAttributes as E3Attributes +from midealocal.devices.e6 import DeviceAttributes as E6Attributes +from midealocal.devices.e8 import DeviceAttributes as E8Attributes +from midealocal.devices.ea import DeviceAttributes as EAAttributes +from midealocal.devices.ec import DeviceAttributes as ECAttributes +from midealocal.devices.ed import DeviceAttributes as EDAttributes +from midealocal.devices.fa import DeviceAttributes as FAAttributes +from midealocal.devices.fb import DeviceAttributes as FBAttributes +from midealocal.devices.fc import DeviceAttributes as FCAttributes +from midealocal.devices.fd import DeviceAttributes as FDAttributes +from midealocal.devices.x26 import DeviceAttributes as X26Attributes +from midealocal.devices.x34 import DeviceAttributes as X34Attributes +from midealocal.devices.x40 import DeviceAttributes as X40Attributes + +""" +Entity Naming Rule: + +1. `name` used in web UI enable/disable extra sensor/control setting +2. entity `_attr_name` exist will ignore `translation_key` +3. no `_attr_name` and no `translation_key` will try `device_class` +4. refer to `midea_entity.py` comments for translation order + +- for entity translation: + - 1. set "translation_key" + - 2. add translation to `translations/{language}.json` +""" + +MIDEA_DEVICES: dict[int, dict[str, dict[str, Any] | str]] = { + 0x13: { + "name": "Light", + "entities": { + "light": { + "type": Platform.LIGHT, + "icon": "mdi:lightbulb", + "default": True, + }, + }, + }, + 0x26: { + "name": "Bathroom Master", + "entities": { + X26Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X26Attributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + X26Attributes.current_radar: { + "type": Platform.BINARY_SENSOR, + "name": "Occupancy Status", + "device_class": BinarySensorDeviceClass.MOTION, + }, + X26Attributes.main_light: { + "type": Platform.SWITCH, + "translation_key": "main_light", + "name": "Main Light", + "icon": "mdi:lightbulb", + }, + X26Attributes.night_light: { + "type": Platform.SWITCH, + "translation_key": "night_light", + "name": "Night Light", + "icon": "mdi:lightbulb", + }, + X26Attributes.mode: { + "type": Platform.SELECT, + "translation_key": "mode", + "name": "Mode", + "options": "preset_modes", + "icon": "mdi:fan", + }, + X26Attributes.direction: { + "type": Platform.SELECT, + "translation_key": "direction", + "name": "Direction", + "options": "directions", + "icon": "mdi:arrow-split-vertical", + }, + }, + }, + 0x34: { + "name": "Sink Dishwasher", + "entities": { + X34Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + X34Attributes.rinse_aid: { + "type": Platform.BINARY_SENSOR, + "translation_key": "rinse_aid", + "name": "Rinse Aid Shortage", + "icon": "mdi:bottle-tonic", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + X34Attributes.salt: { + "type": Platform.BINARY_SENSOR, + "translation_key": "salt", + "name": "Salt Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + X34Attributes.humidity: { + "type": Platform.SENSOR, + "name": "Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.progress: { + "type": Platform.SENSOR, + "translation_key": "progress", + "name": "Progress", + "icon": "mdi:rotate-360", + }, + X34Attributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:information", + }, + X34Attributes.storage_remaining: { + "type": Platform.SENSOR, + "translation_key": "storage_remaining", + "name": "Storage Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.HOURS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.temperature: { + "type": Platform.SENSOR, + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + X34Attributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + X34Attributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + X34Attributes.storage: { + "type": Platform.SWITCH, + "translation_key": "storage", + "name": "Storage", + "icon": "mdi:repeat-variant", + }, + X34Attributes.mode: { + "type": Platform.SENSOR, + "translation_key": "mode", + "name": "Working Mode", + "icon": "mdi:dishwasher", + }, + X34Attributes.error_code: { + "type": Platform.SENSOR, + "translation_key": "error_code", + "name": "Error Code", + "icon": "mdi:alert-box", + }, + X34Attributes.softwater: { + "type": Platform.SENSOR, + "translation_key": "softwater", + "name": "Softwater Level", + "icon": "mdi:shaker-outline", + }, + X34Attributes.bright: { + "type": Platform.SENSOR, + "translation_key": "bright", + "name": "Bright Level", + "icon": "mdi:star-four-points", + }, + }, + }, + 0x40: { + "name": "Integrated Ceiling Fan", + "entities": { + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True, + }, + X40Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + X40Attributes.light: { + "type": Platform.SWITCH, + "translation_key": "light", + "name": "Light", + "icon": "mdi:lightbulb", + }, + X40Attributes.ventilation: { + "type": Platform.SWITCH, + "translation_key": "ventilation", + "name": "Ventilation", + "icon": "mdi:air-filter", + }, + X40Attributes.smelly_sensor: { + "type": Platform.SWITCH, + "translation_key": "smelly_sensor", + "name": "Smelly Sensor", + "icon": "mdi:scent", + }, + X40Attributes.direction: { + "type": Platform.SELECT, + "translation_key": "direction", + "name": "Direction", + "options": "directions", + "icon": "mdi:arrow-split-vertical", + }, + }, + }, + 0xA1: { + "name": "Dehumidifier", + "entities": { + "humidifier": { + "type": Platform.HUMIDIFIER, + "icon": "mdi:air-humidifier", + "default": True, + }, + A1Attributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + A1Attributes.anion: { + "type": Platform.SWITCH, + "translation_key": "anion", + "name": "Anion", + "icon": "mdi:vanish", + }, + A1Attributes.prompt_tone: { + "type": Platform.SWITCH, + "translation_key": "prompt_tone", + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + A1Attributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + A1Attributes.swing: { + "type": Platform.SWITCH, + "translation_key": "swing", + "name": "Swing", + "icon": "mdi:pan-horizontal", + }, + A1Attributes.fan_speed: { + "type": Platform.SELECT, + "translation_key": "fan_speed", + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + A1Attributes.water_level_set: { + "type": Platform.SELECT, + "translation_key": "water_level_set", + "name": "Water Level Setting", + "options": "water_level_sets", + "icon": "mdi:cup-water", + }, + A1Attributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.tank: { + "type": Platform.SENSOR, + "translation_key": "tank", + "name": "Tank", + "icon": "mdi:cup-water", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + A1Attributes.tank_full: { + "type": Platform.BINARY_SENSOR, + "translation_key": "tank_full", + "name": "Tank status", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + A1Attributes.filter_cleaning_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "filter_cleaning_reminder", + "name": "Filter Cleaning Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + }, + }, + 0xAC: { + "name": "Air Conditioner", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "translation_key": "climate_key", + "icon": "mdi:air-conditioner", + "default": True, + }, + "fresh_air": { + "type": Platform.FAN, + "translation_key": "fresh_air", + "name": "Fresh Air", + "icon": "mdi:fan", + }, + ACAttributes.aux_heating: { + "type": Platform.SWITCH, + "translation_key": "aux_heating", + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + ACAttributes.boost_mode: { + "type": Platform.SWITCH, + "translation_key": "boost_mode", + "name": "Boost Mode", + "icon": "mdi:turbine", + }, + ACAttributes.breezeless: { + "type": Platform.SWITCH, + "translation_key": "breezeless", + "name": "Breezeless", + "icon": "mdi:tailwind", + }, + ACAttributes.comfort_mode: { + "type": Platform.SWITCH, + "translation_key": "comfort_mode", + "name": "Comfort Mode", + "icon": "mdi:alpha-c-circle", + }, + ACAttributes.dry: { + "type": Platform.SWITCH, + "translation_key": "dry", + "name": "Dry", + "icon": "mdi:air-filter", + }, + ACAttributes.eco_mode: { + "type": Platform.SWITCH, + "translation_key": "eco_mode", + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + ACAttributes.frost_protect: { + "type": Platform.SWITCH, + "translation_key": "frost_protect", + "name": "Frost Protect", + "icon": "mdi:snowflake-alert", + }, + ACAttributes.indirect_wind: { + "type": Platform.SWITCH, + "translation_key": "indirect_wind", + "name": "Indirect Wind", + "icon": "mdi:tailwind", + }, + ACAttributes.natural_wind: { + "type": Platform.SWITCH, + "translation_key": "natural_wind", + "name": "Natural Wind", + "icon": "mdi:tailwind", + }, + ACAttributes.prompt_tone: { + "type": Platform.SWITCH, + "translation_key": "prompt_tone", + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + ACAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + ACAttributes.screen_display: { + "type": Platform.SWITCH, + "translation_key": "screen_display", + "name": "Screen Display", + "icon": "mdi:television-ambient-light", + }, + ACAttributes.screen_display_alternate: { + "type": Platform.SWITCH, + "translation_key": "screen_display_alternate", + "name": "Screen Display Alternate", + "icon": "mdi:television-ambient-light", + }, + ACAttributes.sleep_mode: { + "type": Platform.SWITCH, + "translation_key": "sleep_mode", + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + ACAttributes.smart_eye: { + "type": Platform.SWITCH, + "translation_key": "smart_eye", + "name": "Smart Eye", + "icon": "mdi:eye", + }, + ACAttributes.swing_horizontal: { + "type": Platform.SWITCH, + "translation_key": "swing_horizontal", + "name": "Swing Horizontal", + "icon": "mdi:arrow-split-vertical", + }, + ACAttributes.swing_vertical: { + "type": Platform.SWITCH, + "translation_key": "swing_vertical", + "name": "Swing Vertical", + "icon": "mdi:arrow-split-horizontal", + }, + ACAttributes.full_dust: { + "type": Platform.BINARY_SENSOR, + "translation_key": "full_dust", + "name": "Full of Dust", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + ACAttributes.indoor_humidity: { + "type": Platform.SENSOR, + "translation_key": "indoor_humidity", + "name": "Indoor Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.indoor_temperature: { + "type": Platform.SENSOR, + "translation_key": "indoor_temperature", + "name": "Indoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.outdoor_temperature: { + "type": Platform.SENSOR, + "translation_key": "outdoor_temperature", + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.total_energy_consumption: { + "type": Platform.SENSOR, + "translation_key": "total_energy_consumption", + "name": "Total Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + ACAttributes.current_energy_consumption: { + "type": Platform.SENSOR, + "translation_key": "current_energy_consumption", + "name": "Current Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + ACAttributes.realtime_power: { + "type": Platform.SENSOR, + "translation_key": "realtime_power", + "name": "Realtime Power", + "device_class": SensorDeviceClass.POWER, + "unit": UnitOfPower.WATT, + "state_class": SensorStateClass.MEASUREMENT, + }, + ACAttributes.wind_lr_angle: { + "type": Platform.SELECT, + "translation_key": "wind_lr_angle", + "name": "Airflow Horizontal", + "options": "wind_lr_angles", + "icon": "mdi:pan-horizontal", + }, + ACAttributes.wind_ud_angle: { + "type": Platform.SELECT, + "translation_key": "wind_ud_angle", + "name": "Airflow Vertical", + "options": "wind_ud_angles", + "icon": "mdi:pan-vertical", + }, + ACAttributes.fan_speed: { + "type": Platform.NUMBER, + "translation_key": "fan_speed_percent", + "name": "Fan Speed Percent", + "icon": "mdi:fan", + "max": 100, + "min": 1, + "step": 1, + }, + }, + }, + 0xAD: { + "name": "Air Detector", + "entities": { + ADAttributes.temperature: { + "type": Platform.SENSOR, + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.humidity: { + "type": Platform.SENSOR, + "name": "Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.temperature_raw: { + "type": Platform.SENSOR, + "name": "Temperature Raw", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.humidity_raw: { + "type": Platform.SENSOR, + "name": "Humidity Raw", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.temperature_compensate: { + "type": Platform.SENSOR, + "name": "Temperature Compensate", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.humidity_compensate: { + "type": Platform.SENSOR, + "name": "Humidity Compensate", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.tvoc: { + "type": Platform.SENSOR, + "name": "Tvoc", + "icon": "mdi:heat-wave", + "device_class": SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS, + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.co2: { + "type": Platform.SENSOR, + "name": "Carbon Dioxide", + "device_class": SensorDeviceClass.CO2, + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.pm25: { + "type": Platform.SENSOR, + "name": "PM 2.5", + "device_class": SensorDeviceClass.PM25, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.hcho: { + "type": Platform.SENSOR, + "name": "Methanal", + "icon": "mdi:molecule", + "device_class": SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.presets_function: { + "type": Platform.BINARY_SENSOR, + "name": "Presets Function", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ADAttributes.fall_asleep_status: { + "type": Platform.BINARY_SENSOR, + "name": "Asleep Status", + "icon": "mdi:sleep", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ADAttributes.screen_extinction_timeout: { + "type": Platform.SENSOR, + "name": "Screen Extinction Timeout", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + ADAttributes.portable_sense: { + "type": Platform.BINARY_SENSOR, + "name": "Portable Sense", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ADAttributes.night_mode: { + "type": Platform.BINARY_SENSOR, + "name": "Night Mode", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ADAttributes.screen_status: { + "type": Platform.BINARY_SENSOR, + "name": "Screen Status", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ADAttributes.led_status: { + "type": Platform.BINARY_SENSOR, + "name": "Ambient Lighting Status", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ADAttributes.arofene_link: { + "type": Platform.BINARY_SENSOR, + "name": "Methanal Status", + "device_class": BinarySensorDeviceClass.PLUG, + }, + ADAttributes.header_exist: { + "type": Platform.BINARY_SENSOR, + "name": "Header Status", + "device_class": BinarySensorDeviceClass.PLUG, + }, + ADAttributes.radar_exist: { + "type": Platform.BINARY_SENSOR, + "name": "Radar Status", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ADAttributes.header_led_status: { + "type": Platform.BINARY_SENSOR, + "name": "Breathing Light", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + }, + }, + 0xB0: { + "name": "Microwave Oven", + "entities": { + B0Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B0Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "translation_key": "tank_ejected", + "name": "Tank Ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_change_reminder", + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_shortage", + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B0Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B0Attributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:information", + }, + B0Attributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB1: { + "name": "Electric Oven", + "entities": { + B1Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B1Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "translation_key": "tank_ejected", + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_change_reminder", + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_shortage", + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B1Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B1Attributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:information", + }, + B1Attributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB3: { + "name": "Dish Sterilizer", + "entities": { + B3Attributes.top_compartment_door: { + "type": Platform.BINARY_SENSOR, + "translation_key": "top_compartment_door", + "name": "Top Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.top_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "translation_key": "top_compartment_preheating", + "name": "Top Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.top_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "translation_key": "top_compartment_cooling", + "name": "Top Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.middle_compartment_door: { + "type": Platform.BINARY_SENSOR, + "translation_key": "middle_compartment_door", + "name": "Middle Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.middle_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "translation_key": "middle_compartment_preheating", + "name": "Middle Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.middle_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "translation_key": "middle_compartment_cooling", + "name": "Middle Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.bottom_compartment_door: { + "type": Platform.BINARY_SENSOR, + "translation_key": "bottom_compartment_door", + "name": "Bottom Compartment Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B3Attributes.bottom_compartment_preheating: { + "type": Platform.BINARY_SENSOR, + "translation_key": "bottom_compartment_preheating", + "name": "Bottom Compartment Preheating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.bottom_compartment_cooling: { + "type": Platform.BINARY_SENSOR, + "translation_key": "bottom_compartment_cooling", + "name": "Bottom Compartment Cooling", + "icon": "snowflake-variant", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + B3Attributes.top_compartment_status: { + "type": Platform.SENSOR, + "translation_key": "top_compartment_status", + "name": "Top Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.top_compartment_temperature: { + "type": Platform.SENSOR, + "translation_key": "top_compartment_temperature", + "name": "Top Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.top_compartment_remaining: { + "type": Platform.SENSOR, + "translation_key": "top_compartment_remaining", + "name": "Top Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.middle_compartment_status: { + "type": Platform.SENSOR, + "translation_key": "middle_compartment_status", + "name": "Middle Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.middle_compartment_temperature: { + "type": Platform.SENSOR, + "translation_key": "middle_compartment_temperature", + "name": "Middle Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.middle_compartment_remaining: { + "type": Platform.SENSOR, + "translation_key": "middle_compartment_remaining", + "name": "Middle Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.bottom_compartment_status: { + "type": Platform.SENSOR, + "translation_key": "bottom_compartment_status", + "name": "Bottom Compartment Status", + "icon": "mdi:information", + }, + B3Attributes.bottom_compartment_temperature: { + "type": Platform.SENSOR, + "translation_key": "bottom_compartment_temperature", + "name": "Bottom Compartment Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B3Attributes.bottom_compartment_remaining: { + "type": Platform.SENSOR, + "translation_key": "bottom_compartment_remaining", + "name": "Bottom Compartment Remaining", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB4: { + "name": "Toaster", + "entities": { + B4Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + B4Attributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "translation_key": "tank_ejected", + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_change_reminder", + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_shortage", + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B4Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + B4Attributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:information", + }, + B4Attributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xB6: { + "name": "Range Hood", + "entities": { + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True, + }, + B6Attributes.light: { + "type": Platform.SWITCH, + "translation_key": "light", + "name": "Light", + "icon": "mdi:lightbulb", + }, + B6Attributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + B6Attributes.cleaning_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "cleaning_reminder", + "name": "Cleaning Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B6Attributes.oilcup_full: { + "type": Platform.BINARY_SENSOR, + "translation_key": "oilcup_full", + "name": "Oil-cup Full", + "icon": "mdi:cup", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + B6Attributes.fan_level: { + "type": Platform.SENSOR, + "translation_key": "fan_level", + "name": "Fan level", + "icon": "mdi:fan", + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xBF: { + "name": "Microwave Steam Oven", + "entities": { + BFAttributes.tank_ejected: { + "type": Platform.BINARY_SENSOR, + "translation_key": "tank_ejected", + "name": "Tank ejected", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.water_change_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_change_reminder", + "name": "Water Change Reminder", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + BFAttributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_shortage", + "name": "Water Shortage", + "icon": "mdi:cup-water", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + BFAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + BFAttributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:information", + }, + BFAttributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xC2: { + "name": "Toilet", + "entities": { + C2Attributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + C2Attributes.sensor_light: { + "type": Platform.SWITCH, + "translation_key": "sensor_light", + "name": "Sensor Light", + "icon": "mdi:lightbulb", + }, + C2Attributes.foam_shield: { + "type": Platform.SWITCH, + "translation_key": "foam_shield", + "name": "Foam Shield", + "icon": "mdi:chart-bubble", + }, + C2Attributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + C2Attributes.seat_status: { + "type": Platform.BINARY_SENSOR, + "translation_key": "seat_status", + "name": "Seat Status", + "icon": "mdi:seat-legroom-normal", + }, + C2Attributes.lid_status: { + "type": Platform.BINARY_SENSOR, + "translation_key": "lid_status", + "name": "Lid Status", + "icon": "mdi:toilet", + }, + C2Attributes.light_status: { + "type": Platform.BINARY_SENSOR, + "name": "Light Status", + "icon": "mdi:lightbulb", + "device_class": BinarySensorDeviceClass.LIGHT, + }, + C2Attributes.water_temperature: { + "type": Platform.SENSOR, + "translation_key": "water_temperature", + "name": "Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.seat_temperature: { + "type": Platform.SENSOR, + "translation_key": "seat_temperature", + "name": "Seat Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.filter_life: { + "type": Platform.SENSOR, + "translation_key": "filter_life", + "name": "Filter Life", + "icon": "mdi:toilet", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + C2Attributes.dry_level: { + "type": Platform.NUMBER, + "translation_key": "dry_level", + "name": "Dry Level", + "icon": "mdi:fire", + "max": "max_dry_level", + "min": 0, + "step": 1, + }, + C2Attributes.water_temp_level: { + "type": Platform.NUMBER, + "translation_key": "water_temp_level", + "name": "Water Temperature Level", + "icon": "mdi:fire", + "max": "max_water_temp_level", + "min": 0, + "step": 1, + }, + C2Attributes.seat_temp_level: { + "type": Platform.NUMBER, + "translation_key": "seat_temp_level", + "name": "Seat Temperature Level", + "icon": "mdi:fire", + "max": "max_seat_temp_level", + "min": 0, + "step": 1, + }, + }, + }, + 0xC3: { + "name": "Heat Pump Wi-Fi Controller", + "entities": { + "climate_zone1": { + "type": Platform.CLIMATE, + "translation_key": "climate_zone1", + "name": "Zone1 Thermostat", + "icon": "mdi:air-conditioner", + "zone": 0, + "default": True, + }, + "climate_zone2": { + "type": Platform.CLIMATE, + "translation_key": "climate_zone2", + "name": "Zone2 Thermostat", + "icon": "mdi:air-conditioner", + "zone": 1, + "default": False, + }, + "water_heater": { + "type": Platform.WATER_HEATER, + "translation_key": "domestic_hot_water", + "name": "Domestic hot water", + "icon": "mdi:heat-pump", + "default": True, + }, + C3Attributes.disinfect: { + "type": Platform.SWITCH, + "translation_key": "disinfect", + "name": "Disinfect", + "icon": "mdi:water-plus-outline", + }, + C3Attributes.dhw_power: { + "type": Platform.SWITCH, + "translation_key": "dhw_power", + "name": "DHW Power", + "icon": "mdi:power", + }, + C3Attributes.eco_mode: { + "type": Platform.SWITCH, + "translation_key": "eco_mode", + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + C3Attributes.fast_dhw: { + "type": Platform.SWITCH, + "translation_key": "fast_dhw", + "name": "Fast DHW", + "icon": "mdi:rotate-orbit", + }, + C3Attributes.silent_mode: { + "type": Platform.SWITCH, + "translation_key": "silent_mode", + "name": "Silent Mode", + "icon": "mdi:fan-remove", + }, + C3Attributes.SILENT_LEVEL: { + "type": Platform.SELECT, + "translation_key": "silent_level", + "name": "Silent Level", + "icon": "mdi:fan-remove", + "options": "silent_modes", + }, + C3Attributes.tbh: { + "type": Platform.SWITCH, + "translation_key": "tbh", + "name": "TBH", + "icon": "mdi:water-boiler", + }, + C3Attributes.zone1_curve: { + "type": Platform.SWITCH, + "translation_key": "zone1_curve", + "name": "Zone1 Curve", + "icon": "mdi:chart-bell-curve-cumulative", + }, + C3Attributes.zone2_curve: { + "type": Platform.SWITCH, + "translation_key": "zone2_curve", + "name": "Zone2 Curve", + "icon": "mdi:chart-bell-curve-cumulative", + }, + C3Attributes.zone1_power: { + "type": Platform.SWITCH, + "translation_key": "zone1_power", + "name": "Zone1 Power", + "icon": "mdi:power", + }, + C3Attributes.zone2_power: { + "type": Platform.SWITCH, + "translation_key": "zone2_power", + "name": "Zone2 Power", + "icon": "mdi:power", + }, + C3Attributes.zone1_water_temp_mode: { + "type": Platform.BINARY_SENSOR, + "translation_key": "zone1_water_temp_mode", + "name": "Zone1 Water-temperature Mode", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone2_water_temp_mode: { + "type": Platform.BINARY_SENSOR, + "translation_key": "zone2_water_temp_mode", + "name": "Zone2 Water-temperature Mode", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone1_room_temp_mode: { + "type": Platform.BINARY_SENSOR, + "translation_key": "zone1_room_temp_mode", + "name": "Zone1 Room-temperature Mode", + "icon": "mdi:home-thermometer-outline", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.zone2_room_temp_mode: { + "type": Platform.BINARY_SENSOR, + "translation_key": "zone2_room_temp_mode", + "name": "Zone2 Room-temperature Mode", + "icon": "mdi:home-thermometer-outline", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.error_code: { + "type": Platform.SENSOR, + "translation_key": "error_code", + "name": "Error Code", + "icon": "mdi:alpha-e-circle", + }, + C3Attributes.tank_actual_temperature: { + "type": Platform.SENSOR, + "translation_key": "tank_actual_temperature", + "name": "Tank Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C3Attributes.status_dhw: { + "type": Platform.BINARY_SENSOR, + "translation_key": "status_dhw", + "name": "DHW status", + "icon": "mdi:heat-pump", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_tbh: { + "type": Platform.BINARY_SENSOR, + "translation_key": "status_tbh", + "name": "TBH status", + "icon": "mdi:water-boiler", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_ibh: { + "type": Platform.BINARY_SENSOR, + "translation_key": "status_ibh", + "name": "IBH status", + "icon": "mdi:coolant-temperature", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.status_heating: { + "type": Platform.BINARY_SENSOR, + "translation_key": "status_heating", + "name": "Heating status", + "icon": "mdi:heat-pump", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + C3Attributes.total_energy_consumption: { + "type": Platform.SENSOR, + "translation_key": "total_energy_consumption", + "name": "Total energy consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + C3Attributes.total_produced_energy: { + "type": Platform.SENSOR, + "translation_key": "total_produced_energy", + "name": "Total produced energy", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + C3Attributes.outdoor_temperature: { + "type": Platform.SENSOR, + "translation_key": "outdoor_temperature", + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C3Attributes.temp_tw_in: { + "type": Platform.SENSOR, + "name": "Water Inlet Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C3Attributes.temp_tw_out: { + "type": Platform.SENSOR, + "name": "Water Outlet Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + C3Attributes.instant_power0: { + "type": Platform.SENSOR, + "name": "Current Power", + "device_class": SensorDeviceClass.POWER, + "unit": UnitOfPower.WATT, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCA: { + "name": "Refrigerator", + "entities": { + CAAttributes.bar_door: { + "type": Platform.BINARY_SENSOR, + "translation_key": "bar_door", + "name": "Bar Door", + "icon": "mdi:door", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.bar_door_overtime: { + "type": Platform.BINARY_SENSOR, + "translation_key": "bar_door_overtime", + "name": "Bar Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.flex_zone_door: { + "type": Platform.BINARY_SENSOR, + "translation_key": "flex_zone_door", + "name": "Flex Door", + "icon": "mdi:door", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.flex_zone_door_overtime: { + "type": Platform.BINARY_SENSOR, + "translation_key": "flex_zone_door_overtime", + "name": "Flex Zone Door", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.freezer_door: { + "type": Platform.BINARY_SENSOR, + "translation_key": "freezer_door", + "name": "Freezer Door", + "icon": "mdi:door", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.freezer_door_overtime: { + "type": Platform.BINARY_SENSOR, + "translation_key": "freezer_door_overtime", + "name": "Freezer Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.refrigerator_door: { + "type": Platform.BINARY_SENSOR, + "translation_key": "refrigerator_door", + "name": "Refrigerator Door", + "icon": "mdi:door", + "device_class": BinarySensorDeviceClass.DOOR, + }, + CAAttributes.refrigerator_door_overtime: { + "type": Platform.BINARY_SENSOR, + "translation_key": "refrigerator_door_overtime", + "name": "Refrigerator Door Overtime", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CAAttributes.flex_zone_actual_temp: { + "type": Platform.SENSOR, + "translation_key": "flex_zone_actual_temp", + "name": "Flex Zone Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.flex_zone_setting_temp: { + "type": Platform.SENSOR, + "translation_key": "flex_zone_setting_temp", + "name": "Flex Zone Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.freezer_actual_temp: { + "type": Platform.SENSOR, + "translation_key": "freezer_actual_temp", + "name": "Freezer Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.freezer_setting_temp: { + "type": Platform.SENSOR, + "translation_key": "freezer_setting_temp", + "name": "Freezer Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.energy_consumption: { + "type": Platform.SENSOR, + "translation_key": "energy_consumption", + "name": "Energy Consumption", + "device_class": SensorDeviceClass.ENERGY, + "unit": UnitOfEnergy.KILO_WATT_HOUR, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + CAAttributes.refrigerator_actual_temp: { + "type": Platform.SENSOR, + "translation_key": "refrigerator_actual_temp", + "name": "Refrigerator Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.refrigerator_setting_temp: { + "type": Platform.SENSOR, + "translation_key": "refrigerator_setting_temp", + "name": "Refrigerator Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.right_flex_zone_actual_temp: { + "type": Platform.SENSOR, + "translation_key": "right_flex_zone_actual_temp", + "name": "Right Flex Zone Actual Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.right_flex_zone_setting_temp: { + "type": Platform.SENSOR, + "translation_key": "right_flex_zone_setting_temp", + "name": "Right Flex Zone Setting Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CAAttributes.microcrystal_fresh: { + "type": Platform.BINARY_SENSOR, + "translation_key": "microcrystal_fresh", + "name": "Microcrystal Fresh", + "icon": "mdi:origin", + }, + CAAttributes.electronic_smell: { + "type": Platform.BINARY_SENSOR, + "translation_key": "electronic_smell", + "name": "Deodorizing sterilizing", + "icon": "mdi:air-filter", + }, + CAAttributes.humidity: { + "type": Platform.SENSOR, + "translation_key": "humidity", + "name": "Humidity", + "icon": "mdi:water-opacity", + }, + CAAttributes.variable_mode: { + "type": Platform.SENSOR, + "translation_key": "variable_mode", + "name": "Variable Mode", + "icon": "mdi:nut", + }, + }, + }, + 0xCC: { + "name": "MDV Wi-Fi Controller", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "hass:air-conditioner", + "default": True, + }, + CCAttributes.aux_heating: { + "type": Platform.SWITCH, + "translation_key": "aux_heating", + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CCAttributes.eco_mode: { + "type": Platform.SWITCH, + "translation_key": "eco_mode", + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + CCAttributes.night_light: { + "type": Platform.SWITCH, + "translation_key": "night_light", + "name": "Night Light", + "icon": "mdi:lightbulb", + }, + CCAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + CCAttributes.sleep_mode: { + "type": Platform.SWITCH, + "translation_key": "sleep_mode", + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + CCAttributes.swing: { + "type": Platform.SWITCH, + "translation_key": "swing", + "name": "Swing", + "icon": "mdi:arrow-split-horizontal", + }, + CCAttributes.indoor_temperature: { + "type": Platform.SENSOR, + "translation_key": "indoor_temperature", + "name": "Indoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xCD: { + "name": "Heat Pump Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:water-boiler", + "default": True, + }, + CDAttributes.compressor_status: { + "type": Platform.BINARY_SENSOR, + "translation_key": "compressor_status", + "name": "Compressor Status", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + CDAttributes.compressor_temperature: { + "type": Platform.SENSOR, + "translation_key": "compressor_temperature", + "name": "Compressor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.condenser_temperature: { + "type": Platform.SENSOR, + "translation_key": "condenser_temperature", + "name": "Condenser Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.outdoor_temperature: { + "type": Platform.SENSOR, + "translation_key": "outdoor_temperature", + "name": "Outdoor Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.water_level: { + "type": Platform.SENSOR, + "translation_key": "water_level", + "name": "Water Level", + "icon": "mdi:cup-water", + }, + CDAttributes.disinfect: { + "type": Platform.SWITCH, + "translation_key": "disinfect", + "name": "Disinfect", + "icon": "mdi:bacteria", + }, + CDAttributes.elec_heat: { + "type": Platform.SENSOR, + "translation_key": "elec_heat", + "name": "Electric Heat", + "icon": "mdi:heat-wave", + }, + CDAttributes.top_elec_heat: { + "type": Platform.BINARY_SENSOR, + "translation_key": "top_elec_heat", + "name": "Top Electric Heat", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + CDAttributes.bottom_elec_heat: { + "type": Platform.BINARY_SENSOR, + "translation_key": "bottom_elec_heat", + "name": "Bottom Electric Heat", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + CDAttributes.water_pump: { + "type": Platform.SENSOR, + "translation_key": "water_pump", + "name": "Water Pump", + "icon": "mdi:water-pump", + }, + CDAttributes.four_way: { + "type": Platform.SENSOR, + "translation_key": "four_way", + "name": "Four Way Valve", + "icon": "mdi:pipe-valve", + }, + CDAttributes.back_water: { + "type": Platform.SENSOR, + "translation_key": "back_water", + "name": "Back Water", + "icon": "mdi:water", + }, + CDAttributes.sterilize: { + "type": Platform.SENSOR, + "translation_key": "sterilize", + "name": "Sterilize", + "icon": "mdi:bacteria", + }, + CDAttributes.top_temperature: { + "type": Platform.SENSOR, + "translation_key": "top_temperature", + "name": "Top Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.bottom_temperature: { + "type": Platform.SENSOR, + "translation_key": "bottom_temperature", + "name": "Bottom Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CDAttributes.wind: { + "type": Platform.SENSOR, + "translation_key": "wind", + "name": "Wind", + "icon": "mdi:weather-windy", + }, + CDAttributes.smart_grid: { + "type": Platform.BINARY_SENSOR, + "translation_key": "smart_grid", + "name": "Smart Grid", + "icon": "mdi:transmission-tower", + }, + CDAttributes.multi_terminal: { + "type": Platform.BINARY_SENSOR, + "translation_key": "multi_terminal", + "name": "Multi Terminal", + "icon": "mdi:tablet-cellphone", + }, + CDAttributes.mute_effect: { + "type": Platform.BINARY_SENSOR, + "translation_key": "mute_effect", + "name": "Mute Effect", + "icon": "mdi:volume-off", + }, + CDAttributes.mute_status: { + "type": Platform.BINARY_SENSOR, + "translation_key": "mute_status", + "name": "Mute Status", + "icon": "mdi:volume-mute", + }, + CDAttributes.error_code: { + "type": Platform.SENSOR, + "translation_key": "error_code", + "name": "Error Code", + "icon": "mdi:alert-circle-outline", + }, + CDAttributes.typeinfo: { + "type": Platform.SENSOR, + "translation_key": "typeinfo", + "name": "Type Info", + "icon": "mdi:information-outline", + }, + CDAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + }, + }, + 0xCE: { + "name": "Fresh Air Appliance", + "entities": { + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True, + }, + CEAttributes.filter_cleaning_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "filter_cleaning_reminder", + "name": "Filter Cleaning Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CEAttributes.filter_change_reminder: { + "type": Platform.BINARY_SENSOR, + "translation_key": "filter_change_reminder", + "name": "Filter Change Reminder", + "icon": "mdi:alert-circle", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + CEAttributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.co2: { + "type": Platform.SENSOR, + "name": "Carbon Dioxide", + "device_class": SensorDeviceClass.CO2, + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.hcho: { + "type": Platform.SENSOR, + "translation_key": "hcho", + "name": "Methanal", + "icon": "mdi:molecule", + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.pm25: { + "type": Platform.SENSOR, + "name": "PM 2.5", + "device_class": SensorDeviceClass.PM25, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + CEAttributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + CEAttributes.aux_heating: { + "type": Platform.SWITCH, + "translation_key": "aux_heating", + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CEAttributes.eco_mode: { + "type": Platform.SWITCH, + "translation_key": "eco_mode", + "name": "ECO Mode", + "icon": "mdi:leaf-circle", + }, + CEAttributes.link_to_ac: { + "type": Platform.SWITCH, + "translation_key": "link_to_ac", + "name": "Link to AC", + "icon": "mdi:link", + }, + CEAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + CEAttributes.powerful_purify: { + "type": Platform.SWITCH, + "translation_key": "powerful_purify", + "name": "Powerful Purification", + "icon": "mdi:turbine", + }, + CEAttributes.sleep_mode: { + "type": Platform.SWITCH, + "translation_key": "sleep_mode", + "name": "Sleep Mode", + "icon": "mdi:power-sleep", + }, + }, + }, + 0xCF: { + "name": "Heat Pump", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "hass:air-conditioner", + "default": True, + }, + CFAttributes.aux_heating: { + "type": Platform.SWITCH, + "translation_key": "aux_heating", + "name": "Aux Heating", + "icon": "mdi:heat-wave", + }, + CFAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + CFAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xDA: { + "name": "Top Load Washer", + "entities": { + DAAttributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.wash_time: { + "type": Platform.SENSOR, + "translation_key": "wash_time", + "name": "Wash time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.soak_time: { + "type": Platform.SENSOR, + "translation_key": "soak_time", + "name": "Soak time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.dehydration_time: { + "type": Platform.SENSOR, + "translation_key": "dehydration_time", + "name": "Dehydration time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DAAttributes.dehydration_speed: { + "type": Platform.SENSOR, + "translation_key": "dehydration_speed", + "name": "Dehydration speed", + "icon": "mdi:speedometer", + }, + DAAttributes.error_code: { + "type": Platform.SENSOR, + "translation_key": "error_code", + "name": "Error code", + "icon": "mdi:washing-machine-alert", + }, + DAAttributes.rinse_count: { + "type": Platform.SENSOR, + "translation_key": "rinse_count", + "name": "Rinse count", + "icon": "mdi:water-sync", + }, + DAAttributes.rinse_level: { + "type": Platform.SENSOR, + "translation_key": "rinse_level", + "name": "Rinse level", + "icon": "mdi:hydraulic-oil-level", + }, + DAAttributes.wash_level: { + "type": Platform.SENSOR, + "translation_key": "wash_level", + "name": "Rinse count", + "icon": "mdi:hydraulic-oil-level", + }, + DAAttributes.wash_strength: { + "type": Platform.SENSOR, + "translation_key": "wash_strength", + "name": "Wash strength", + "icon": "mdi:network-strength-4-cog", + }, + DAAttributes.softener: { + "type": Platform.SENSOR, + "translation_key": "softener", + "name": "Softener", + "icon": "mdi:tshirt-crew", + }, + DAAttributes.detergent: { + "type": Platform.SENSOR, + "translation_key": "detergent", + "name": "Detergent", + "icon": "mdi:spray-bottle", + }, + DAAttributes.program: { + "type": Platform.SENSOR, + "translation_key": "program", + "name": "Program", + "icon": "mdi:progress-wrench", + }, + DAAttributes.progress: { + "type": Platform.SENSOR, + "translation_key": "progress", + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DAAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + DAAttributes.start: { + "type": Platform.SWITCH, + "translation_key": "start", + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + }, + }, + 0xDB: { + "name": "Front Load Washer", + "entities": { + DBAttributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DBAttributes.progress: { + "type": Platform.SENSOR, + "translation_key": "progress", + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DBAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + DBAttributes.start: { + "type": Platform.SWITCH, + "translation_key": "start", + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + DBAttributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:auto-mode", + }, + DBAttributes.mode: { + "type": Platform.SENSOR, + "translation_key": "mode", + "name": "Mode", + "icon": "mdi:auto-mode", + }, + DBAttributes.dehydration_speed: { + "type": Platform.SENSOR, + "translation_key": "dehydration_speed", + "name": "Dehydration Speed", + "icon": "mdi:speedometer", + }, + DBAttributes.water_level: { + "type": Platform.SENSOR, + "translation_key": "water_level", + "name": "Water Level", + "icon": "mdi:cup-water", + }, + DBAttributes.program: { + "type": Platform.SENSOR, + "translation_key": "program", + "name": "Program", + "icon": "mdi:washing-machine", + }, + DBAttributes.temperature: { + "type": Platform.SENSOR, + "translation_key": "temperature", + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + DBAttributes.detergent: { + "type": Platform.SENSOR, + "translation_key": "detergent", + "name": "Detergent", + "icon": "mdi:water", + }, + DBAttributes.softener: { + "type": Platform.SENSOR, + "translation_key": "softener", + "name": "Softener", + "icon": "mdi:water-outline", + }, + DBAttributes.wash_time: { + "type": Platform.SENSOR, + "translation_key": "wash_time", + "name": "Wash Time", + "icon": "mdi:dishwasher", + }, + DBAttributes.dehydration_time: { + "type": Platform.SENSOR, + "translation_key": "dehydration_time", + "name": "Dehydration Time", + "icon": "mdi:dishwasher", + }, + DBAttributes.wash_time_value: { + "type": Platform.SENSOR, + "translation_key": "wash_time_value", + "name": "Wash Time Value", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DBAttributes.dehydration_time_value: { + "type": Platform.SENSOR, + "translation_key": "dehydration_time_value", + "name": "Dehydration Time Value", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DBAttributes.stains: { + "type": Platform.SENSOR, + "translation_key": "stains", + "name": "Stains", + "icon": "mdi:water-outline", + }, + DBAttributes.dirty_degree: { + "type": Platform.SENSOR, + "translation_key": "dirty_degree", + "name": "Dirty_degree", + "icon": "mdi:water-outline", + }, + }, + }, + 0xDC: { + "name": "Clothes Dryer", + "entities": { + DCAttributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + DCAttributes.progress: { + "type": Platform.SENSOR, + "translation_key": "progress", + "name": "Progress", + "icon": "mdi:rotate-360", + }, + DCAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + DCAttributes.start: { + "type": Platform.SWITCH, + "translation_key": "start", + "name": "Start", + "icon": "mdi:motion-play-outline", + }, + DCAttributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:auto-mode", + }, + DCAttributes.program: { + "type": Platform.SENSOR, + "translation_key": "program", + "name": "Program", + "icon": "mdi:washing-machine", + }, + DCAttributes.dry_temperature: { + "type": Platform.SENSOR, + "translation_key": "dry_temperature", + "name": "Dry Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + DCAttributes.intensity: { + "type": Platform.SENSOR, + "translation_key": "intensity", + "name": "Intensity", + "icon": "mdi:waves-arrow-up", + }, + DCAttributes.dryness_level: { + "type": Platform.SENSOR, + "translation_key": "dryness_level", + "name": "Dryness Level", + "icon": "mdi:spirit-level", + }, + DCAttributes.error_code: { + "type": Platform.SENSOR, + "translation_key": "error_code", + "name": "Error Code", + "icon": "mdi:code-block-tags", + }, + DCAttributes.door_warn: { + "type": Platform.SENSOR, + "translation_key": "door_warn", + "name": "Door Warn", + "icon": "mdi:alert-box", + }, + DCAttributes.ai_switch: { + "type": Platform.SENSOR, + "translation_key": "ai_switch", + "name": "AI Switch", + "icon": "mdi:toggle-switch", + }, + DCAttributes.material: { + "type": Platform.SENSOR, + "translation_key": "material", + "name": "Material", + "icon": "mdi:material-design", + }, + DCAttributes.water_box: { + "type": Platform.SENSOR, + "translation_key": "water_box", + "name": "Water Box", + "icon": "mdi:cup-water", + }, + }, + }, + 0xE1: { + "name": "Dishwasher", + "entities": { + E1Attributes.door: { + "type": Platform.BINARY_SENSOR, + "name": "Door", + "icon": "mdi:box-shadow", + "device_class": BinarySensorDeviceClass.DOOR, + }, + E1Attributes.rinse_aid: { + "type": Platform.BINARY_SENSOR, + "translation_key": "rinse_aid", + "name": "Rinse Aid Shortage", + "icon": "mdi:bottle-tonic", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E1Attributes.salt: { + "type": Platform.BINARY_SENSOR, + "translation_key": "salt", + "name": "Salt Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E1Attributes.humidity: { + "type": Platform.SENSOR, + "name": "Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.progress: { + "type": Platform.SENSOR, + "translation_key": "progress", + "name": "Progress", + "icon": "mdi:rotate-360", + }, + E1Attributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:information", + }, + E1Attributes.storage_remaining: { + "type": Platform.SENSOR, + "translation_key": "storage_remaining", + "name": "Storage Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.HOURS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.temperature: { + "type": Platform.SENSOR, + "name": "Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + E1Attributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + E1Attributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + E1Attributes.storage: { + "type": Platform.SWITCH, + "translation_key": "storage", + "name": "Storage", + "icon": "mdi:repeat-variant", + }, + E1Attributes.mode: { + "type": Platform.SENSOR, + "translation_key": "mode", + "name": "Working Mode", + "icon": "mdi:dishwasher", + }, + E1Attributes.error_code: { + "type": Platform.SENSOR, + "translation_key": "error_code", + "name": "Error Code", + "icon": "mdi:alert-box", + }, + E1Attributes.softwater: { + "type": Platform.SENSOR, + "translation_key": "softwater", + "name": "Softwater Level", + "icon": "mdi:shaker-outline", + }, + E1Attributes.bright: { + "type": Platform.SENSOR, + "translation_key": "bright", + "name": "Bright Level", + "icon": "mdi:star-four-points", + }, + }, + }, + 0xE2: { + "name": "Electric Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-electric-outline", + "default": True, + }, + E2Attributes.heating: { + "type": Platform.BINARY_SENSOR, + "translation_key": "heating", + "name": "Heating", + "icon": "mdi:heat-wave", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.keep_warm: { + "type": Platform.BINARY_SENSOR, + "translation_key": "keep_warm", + "name": "Keep Warm", + "icon": "mdi:menu", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.protection: { + "type": Platform.BINARY_SENSOR, + "translation_key": "protection", + "name": "Protection", + "icon": "mdi:shield-check", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E2Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.heating_time_remaining: { + "type": Platform.SENSOR, + "translation_key": "heating_time_remaining", + "name": "Heating Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.heating_power: { + "type": Platform.SENSOR, + "translation_key": "heating_power", + "name": "Heating Power", + "device_class": SensorDeviceClass.POWER, + "unit": UnitOfPower.WATT, + "state_class": SensorStateClass.MEASUREMENT, + }, + E2Attributes.water_consumption: { + "type": Platform.SENSOR, + "translation_key": "water_consumption", + "name": "Water Consumption", + "icon": "mdi:water", + "unit": UnitOfVolume.LITERS, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + E2Attributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + E2Attributes.variable_heating: { + "type": Platform.SWITCH, + "translation_key": "variable_heating", + "name": "Variable Heating", + "icon": "mdi:waves", + }, + E2Attributes.whole_tank_heating: { + "type": Platform.SWITCH, + "translation_key": "whole_tank_heating", + "name": "Whole Tank Heating", + "icon": "mdi:restore", + }, + }, + }, + 0xE3: { + "name": "Gas Water Heater", + "entities": { + "water_heater": { + "type": Platform.WATER_HEATER, + "icon": "mdi:meter-gas", + "default": True, + }, + E3Attributes.burning_state: { + "type": Platform.BINARY_SENSOR, + "translation_key": "burning_state", + "name": "Burning State", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E3Attributes.protection: { + "type": Platform.BINARY_SENSOR, + "translation_key": "protection", + "name": "Protection", + "icon": "mdi:shield-check", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E3Attributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E3Attributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + E3Attributes.smart_volume: { + "type": Platform.SWITCH, + "translation_key": "smart_volume", + "name": "Smart Volume", + "icon": "mdi:recycle", + }, + E3Attributes.zero_cold_water: { + "type": Platform.SWITCH, + "translation_key": "zero_cold_water", + "name": "Zero Cold Water", + "icon": "mdi:restore", + }, + E3Attributes.zero_cold_pulse: { + "type": Platform.SWITCH, + "translation_key": "zero_cold_pulse", + "name": "Zero Cold Water (Pulse)", + "icon": "mdi:restore-alert", + }, + }, + }, + 0xE6: { + "name": "Gas Boilers", + "entities": { + "water_heater_heating": { + "type": Platform.WATER_HEATER, + "translation_key": "heating", + "name": "Heating", + "icon": "mdi:meter-gas", + "use": 0, + "default": True, + }, + "water_heater_bathing": { + "type": Platform.WATER_HEATER, + "translation_key": "bathing", + "name": "Bathing", + "icon": "mdi:meter-gas", + "use": 1, + "default": True, + }, + E6Attributes.heating_working: { + "type": Platform.BINARY_SENSOR, + "translation_key": "heating_working", + "name": "Heating Working Status", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E6Attributes.bathing_working: { + "type": Platform.BINARY_SENSOR, + "translation_key": "bathing_working", + "name": "Bathing Working Status", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + E6Attributes.heating_leaving_temperature: { + "type": Platform.SENSOR, + "translation_key": "heating_leaving_temperature", + "name": "Heating Leaving Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E6Attributes.bathing_leaving_temperature: { + "type": Platform.SENSOR, + "translation_key": "bathing_leaving_temperature", + "name": "Bathing Leaving Water Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E6Attributes.main_power: { + "type": Platform.SWITCH, + "translation_key": "main_power", + "name": "Main Power", + "icon": "mdi:power", + }, + E6Attributes.heating_power: { + "type": Platform.SWITCH, + "translation_key": "heating_power", + "name": "Heating Power", + "icon": "mdi:heating-coil", + }, + E6Attributes.cold_water_single: { + "type": Platform.SWITCH, + "translation_key": "cold_water_single", + "name": "Cold Water Single", + "icon": "mdi:water", + }, + E6Attributes.cold_water_dot: { + "type": Platform.SWITCH, + "translation_key": "cold_water_dot", + "name": "Cold Water Dot", + "icon": "mdi:water-outline", + }, + E6Attributes.heating_modes: { + "type": Platform.SELECT, + "translation_key": "mode", + "options": "heating_modes", + "name": "Heating Modes", + "icon": "mdi:auto-mode", + }, + }, + }, + 0xE8: { + "name": "Electric Slow Cooker", + "entities": { + E8Attributes.finished: { + "type": Platform.BINARY_SENSOR, + "translation_key": "finished", + "name": "Finished", + "icon": "", + }, + E8Attributes.water_shortage: { + "type": Platform.BINARY_SENSOR, + "translation_key": "water_shortage", + "name": "Water Shortage", + "icon": "mdi:drag", + "device_class": BinarySensorDeviceClass.PROBLEM, + }, + E8Attributes.status: { + "type": Platform.SENSOR, + "translation_key": "status", + "name": "Status", + "icon": "mdi:information", + }, + E8Attributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.keep_warm_remaining: { + "type": Platform.SENSOR, + "translation_key": "keep_warm_remaining", + "name": "Keep Warm Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.working_time: { + "type": Platform.SENSOR, + "translation_key": "working_time", + "name": "Working Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.SECONDS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.target_temperature: { + "type": Platform.SENSOR, + "translation_key": "target_temperature", + "name": "Target Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + E8Attributes.current_temperature: { + "type": Platform.SENSOR, + "translation_key": "current_temperature", + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xEA: { + "name": "Electric Rice Cooker", + "entities": { + EAAttributes.cooking: { + "type": Platform.BINARY_SENSOR, + "translation_key": "cooking", + "name": "Cooking", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + EAAttributes.keep_warm: { + "type": Platform.BINARY_SENSOR, + "translation_key": "keep_warm", + "name": "Keep Warm", + "icon": "mdi:menu", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + EAAttributes.bottom_temperature: { + "type": Platform.SENSOR, + "translation_key": "bottom_temperature", + "name": "Bottom Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.keep_warm_time: { + "type": Platform.SENSOR, + "translation_key": "keep_warm_time", + "name": "Keep Warm Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.mode: { + "type": Platform.SENSOR, + "translation_key": "mode", + "name": "Mode", + "icon": "mdi:orbit", + }, + EAAttributes.progress: { + "type": Platform.SENSOR, + "translation_key": "progress", + "name": "Progress", + "icon": "mdi:rotate-360", + }, + EAAttributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + EAAttributes.top_temperature: { + "type": Platform.SENSOR, + "translation_key": "top_temperature", + "name": "Top Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xEC: { + "name": "Electric Pressure Cooker", + "entities": { + ECAttributes.cooking: { + "type": Platform.BINARY_SENSOR, + "translation_key": "cooking", + "name": "Cooking", + "icon": "mdi:fire", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ECAttributes.with_pressure: { + "type": Platform.BINARY_SENSOR, + "translation_key": "with_pressure", + "name": "With Pressure", + "icon": "mdi:information", + "device_class": BinarySensorDeviceClass.RUNNING, + }, + ECAttributes.bottom_temperature: { + "type": Platform.SENSOR, + "translation_key": "bottom_temperature", + "name": "Bottom Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.keep_warm_time: { + "type": Platform.SENSOR, + "translation_key": "keep_warm_time", + "name": "Keep Warm Time", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.mode: { + "type": Platform.SENSOR, + "translation_key": "mode", + "name": "Mode", + "icon": "mdi:orbit", + }, + ECAttributes.progress: { + "type": Platform.SENSOR, + "translation_key": "progress", + "name": "Progress", + "icon": "mdi:rotate-360", + }, + ECAttributes.time_remaining: { + "type": Platform.SENSOR, + "translation_key": "time_remaining", + "name": "Time Remaining", + "icon": "mdi:progress-clock", + "unit": UnitOfTime.MINUTES, + "state_class": SensorStateClass.MEASUREMENT, + }, + ECAttributes.top_temperature: { + "type": Platform.SENSOR, + "translation_key": "top_temperature", + "name": "Top Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xED: { + "name": "Water Drinking Appliance", + "entities": { + EDAttributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + EDAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + EDAttributes.filter1: { + "type": Platform.SENSOR, + "translation_key": "filter1_days", + "name": "Filter1 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.filter2: { + "type": Platform.SENSOR, + "translation_key": "filter2_days", + "name": "Filter2 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.filter3: { + "type": Platform.SENSOR, + "translation_key": "filter3_days", + "name": "Filter3 Available Days", + "icon": "mdi:air-filter", + "unit": UnitOfTime.DAYS, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life1: { + "type": Platform.SENSOR, + "translation_key": "filter1_life", + "name": "Filter1 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life2: { + "type": Platform.SENSOR, + "translation_key": "filter2_life", + "name": "Filter2 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.life3: { + "type": Platform.SENSOR, + "translation_key": "filter3_life", + "name": "Filter3 Life Level", + "icon": "mdi:percent", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.in_tds: { + "type": Platform.SENSOR, + "translation_key": "in_tds", + "name": "In TDS", + "icon": "mdi:water", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.out_tds: { + "type": Platform.SENSOR, + "translation_key": "out_tds", + "name": "Out TDS", + "icon": "mdi:water-plus", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + EDAttributes.water_consumption: { + "type": Platform.SENSOR, + "translation_key": "water_consumption", + "name": "Water Consumption", + "icon": "mdi:water-pump", + "unit": UnitOfVolume.LITERS, + "state_class": SensorStateClass.TOTAL_INCREASING, + }, + }, + }, + 0xFA: { + "name": "Fan", + "entities": { + "fan": { + "type": Platform.FAN, + "icon": "mdi:fan", + "default": True, + }, + FAAttributes.oscillation_mode: { + "type": Platform.SELECT, + "translation_key": "oscillation_mode", + "name": "Oscillation Mode", + "options": "oscillation_modes", + "icon": "mdi:swap-horizontal-variant", + }, + FAAttributes.oscillation_angle: { + "type": Platform.SELECT, + "translation_key": "oscillation_angle", + "name": "Oscillation Angle", + "options": "oscillation_angles", + "icon": "mdi:pan-horizontal", + }, + FAAttributes.tilting_angle: { + "type": Platform.SELECT, + "translation_key": "tilting_angle", + "name": "Tilting Angle", + "options": "tilting_angles", + "icon": "mdi:pan-vertical", + }, + FAAttributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + FAAttributes.oscillate: { + "type": Platform.SWITCH, + "translation_key": "oscillate", + "name": "Oscillate", + "icon": "mdi:swap-horizontal-bold", + }, + FAAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + }, + }, + 0xFB: { + "name": "Electric Heater", + "entities": { + "climate": { + "type": Platform.CLIMATE, + "icon": "mdi:air-conditioner", + "default": True, + }, + FBAttributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + FBAttributes.heating_level: { + "type": Platform.NUMBER, + "translation_key": "heating_level", + "name": "Heating Level", + "icon": "mdi:fire", + "max": 10, + "min": 1, + "step": 1, + }, + FBAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + FBAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xFC: { + "name": "Air Purifier", + "entities": { + FCAttributes.child_lock: { + "type": Platform.LOCK, + "translation_key": "child_lock", + "name": "Child Lock", + }, + FCAttributes.anion: { + "type": Platform.SWITCH, + "translation_key": "anion", + "name": "Anion", + "icon": "mdi:vanish", + }, + FCAttributes.prompt_tone: { + "type": Platform.SWITCH, + "translation_key": "prompt_tone", + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + FCAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + FCAttributes.standby: { + "type": Platform.SWITCH, + "translation_key": "standby", + "name": "Standby", + "icon": "mdi:smoke-detector-variant", + }, + FCAttributes.detect_mode: { + "type": Platform.SELECT, + "translation_key": "detect_mode", + "name": "Detect Mode", + "options": "detect_modes", + "icon": "mdi:smoke-detector-variant", + }, + FCAttributes.mode: { + "type": Platform.SELECT, + "translation_key": "mode", + "name": "Mode", + "options": "modes", + "icon": "mdi:rotate-360", + }, + FCAttributes.fan_speed: { + "type": Platform.SELECT, + "translation_key": "fan_speed", + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + FCAttributes.screen_display: { + "type": Platform.SELECT, + "translation_key": "screen_display", + "name": "Screen Display", + "options": "screen_displays", + "icon": "mdi:television-ambient-light", + }, + FCAttributes.pm25: { + "type": Platform.SENSOR, + "name": "PM 2.5", + "device_class": SensorDeviceClass.PM25, + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.tvoc: { + "type": Platform.SENSOR, + "translation_key": "tvoc", + "name": "TVOC", + "icon": "mdi:heat-wave", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.hcho: { + "type": Platform.SENSOR, + "translation_key": "hcho", + "name": "Methanal", + "icon": "mdi:molecule", + "unit": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.filter1_life: { + "type": Platform.SENSOR, + "translation_key": "filter1_life", + "name": "Filter1 Life Level", + "icon": "mdi:air-filter", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + FCAttributes.filter2_life: { + "type": Platform.SENSOR, + "translation_key": "filter2_life", + "name": "Filter2 Life Level", + "icon": "mdi:air-filter", + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, + 0xFD: { + "name": "Humidifier", + "entities": { + Platform.HUMIDIFIER: { + "type": Platform.HUMIDIFIER, + "icon": "mdi:air-humidifier", + "default": True, + }, + FDAttributes.disinfect: { + "type": Platform.SWITCH, + "translation_key": "disinfect", + "name": "Disinfect", + "icon": "mdi:water-plus-outline", + }, + FDAttributes.prompt_tone: { + "type": Platform.SWITCH, + "translation_key": "prompt_tone", + "name": "Prompt Tone", + "icon": "mdi:bell", + }, + FDAttributes.power: { + "type": Platform.SWITCH, + "translation_key": "power", + "name": "Power", + "icon": "mdi:power", + }, + FDAttributes.fan_speed: { + "type": Platform.SELECT, + "translation_key": "fan_speed", + "name": "Fan Speed", + "options": "fan_speeds", + "icon": "mdi:fan", + }, + FDAttributes.screen_display: { + "type": Platform.SELECT, + "translation_key": "screen_display", + "name": "Screen Display", + "options": "screen_displays", + "icon": "mdi:television-ambient-light", + }, + FDAttributes.current_humidity: { + "type": Platform.SENSOR, + "name": "Current Humidity", + "device_class": SensorDeviceClass.HUMIDITY, + "unit": PERCENTAGE, + "state_class": SensorStateClass.MEASUREMENT, + }, + FDAttributes.current_temperature: { + "type": Platform.SENSOR, + "name": "Current Temperature", + "device_class": SensorDeviceClass.TEMPERATURE, + "unit": UnitOfTemperature.CELSIUS, + "state_class": SensorStateClass.MEASUREMENT, + }, + }, + }, +} diff --git a/custom_components/midea_ac_lan/midea_entity.py b/custom_components/midea_ac_lan/midea_entity.py new file mode 100644 index 0000000..2b5c645 --- /dev/null +++ b/custom_components/midea_ac_lan/midea_entity.py @@ -0,0 +1,144 @@ +"""Base entity for Midea Lan.""" + +import logging +from typing import Any, cast + +from homeassistant.const import MAJOR_VERSION, MINOR_VERSION +from homeassistant.core import callback + +if (MAJOR_VERSION, MINOR_VERSION) >= (2023, 9): + from homeassistant.helpers.device_registry import DeviceInfo +else: + from homeassistant.helpers.entity import ( # type: ignore[attr-defined] + DeviceInfo, + ) +from homeassistant.helpers.entity import Entity +from midealocal.device import MideaDevice + +from .const import DOMAIN +from .midea_devices import MIDEA_DEVICES + +_LOGGER = logging.getLogger(__name__) + + +class MideaEntity(Entity): + """Base Midea entity.""" + + def __init__(self, device: MideaDevice, entity_key: str) -> None: + """Initialize Midea base entity.""" + self._device = device + self._device.register_update(self.update_state) + self._config = cast( + "dict", + MIDEA_DEVICES[self._device.device_type]["entities"], + )[entity_key] + self._entity_key = entity_key + self._unique_id = f"{DOMAIN}.{self._device.device_id}_{entity_key}" + self.entity_id = self._unique_id + self._device_name = self._device.name + + # HA language setting: + # 1. hass.config.language: Settings / System / General settings + # 2. user language setting in user profile setting + # Entity name translation based on hass.config.language + # add language in /config/configuration.yaml will disable web UI setting + # homeassistant: + # language: zh-Hans # noqa: ERA001 + + # Translating the name and attributes of entities: + # https://developers.home-assistant.io/blog/2023/03/27/entity_name_translations/#translating-entity-name + # https://developers.home-assistant.io/docs/internationalization/core + # translation_key: if defined, Home Assistant will try to find a + # translation in translations/.json. + # If translation exists -> UI shows the translated string. + # If translation not found -> fallback to "name" / device_class / entity_id. + self._attr_translation_key = self._config.get("translation_key") + + # has_entity_name: MUST be True in modern HA (old False behavior is deprecated). + self._attr_has_entity_name = True + + # Step 1: translation_key is defined + # - If translation is found in the current language: + # -> UI displays the translated string. + if self._attr_translation_key is not None: + # skip set attr_name and use translation_key + pass + # set attr_name to None will only show device name without translaion_key + # Step 2: No translation_key + # but english "name" is explicitly set in config: + # -> UI displays this name directly (highest priority). + elif self._config.get("name") is not None: + self._attr_name = self._config["name"] + # Step 3: No translation_key, no name, + # fallback to device_class default label. + # Example: device_class = temperature -> "Temperature". + elif "device_class" in self._config: + self._attr_name = None # Let HA generate from device_class + # Step 4: Nothing available, + else: + self._attr_name = ( + f"{self._device_name} {self._config.get('name')}" + if "name" in self._config + else f"{self._device_name}" + ) + + @property + def device(self) -> MideaDevice: + """Return device structure.""" + return self._device + + @property + def device_info(self) -> DeviceInfo: + """Return device info.""" + return { + "manufacturer": "Midea", + "model": f"{MIDEA_DEVICES[self._device.device_type]['name']} " + f"{self._device.model}" + f" ({self._device.subtype})", + "identifiers": {(DOMAIN, str(self._device.device_id))}, + "name": self._device_name, + } + + @property + def unique_id(self) -> str: + """Return entity unique id.""" + return self._unique_id + + @property + def should_poll(self) -> bool: + """Return true is integration should poll.""" + return False + + @property + def available(self) -> bool: + """Return entity availability.""" + return bool(self._device.available) + + @property + def icon(self) -> str: + """Return entity icon.""" + return cast("str", self._config.get("icon")) + + @callback + def update_state(self, status: Any) -> None: # noqa: ANN401 + """Update entity state.""" + if not self.hass: + _LOGGER.warning( + "MideaEntity update_state for %s [%s] with status %s: HASS is None", + self.name, + type(self), + status, + ) + return + + if self.hass.is_stopping: + _LOGGER.debug( + "MideaEntity update_state for %s [%s] with status %s: HASS is stopping", + self.name, + type(self), + status, + ) + return + + if self._entity_key in status or "available" in status: + self.schedule_update_ha_state() diff --git a/custom_components/midea_ac_lan/number.py b/custom_components/midea_ac_lan/number.py new file mode 100644 index 0000000..9a02165 --- /dev/null +++ b/custom_components/midea_ac_lan/number.py @@ -0,0 +1,102 @@ +"""Number for Midea Lan.""" + +from typing import Any, cast + +from homeassistant.components.number import NumberEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from midealocal.device import MideaDevice + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up numbers for device.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + numbers = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.NUMBER and entity_key in extra_switches: + dev = MideaNumber(device, entity_key) + numbers.append(dev) + async_add_entities(numbers) + + +class MideaNumber(MideaEntity, NumberEntity): + """Represent a Midea number sensor.""" + + def __init__(self, device: MideaDevice, entity_key: str) -> None: + """Midea number sensor init.""" + super().__init__(device, entity_key) + self._max_value = self._config.get("max") + self._min_value = self._config.get("min") + self._step_value = self._config.get("step") + + @property + def native_min_value(self) -> float: + """Return minimum value.""" + return cast( + "float", + ( + self._min_value + if isinstance(self._min_value, int) + else ( + self._device.get_attribute(attr=self._min_value) + if self._device.get_attribute(attr=self._min_value) + else getattr(self._device, self._min_value) + ) + ), + ) + + @property + def native_max_value(self) -> float: + """Return maximum value.""" + return cast( + "float", + ( + self._max_value + if isinstance(self._max_value, int) + else ( + self._device.get_attribute(attr=self._max_value) + if self._device.get_attribute(attr=self._max_value) + else getattr(self._device, self._max_value) + ) + ), + ) + + @property + def native_step(self) -> float: + """Return step value.""" + return cast( + "float", + ( + self._step_value + if isinstance(self._step_value, int) + else ( + self._device.get_attribute(attr=self._step_value) + if self._device.get_attribute(attr=self._step_value) + else getattr(self._device, self._step_value) + ) + ), + ) + + @property + def native_value(self) -> float: + """Return value.""" + return cast("float", self._device.get_attribute(self._entity_key)) + + def set_native_value(self, value: Any) -> None: # noqa: ANN401 + """Set value.""" + self._device.set_attribute(self._entity_key, value) diff --git a/custom_components/midea_ac_lan/select.py b/custom_components/midea_ac_lan/select.py new file mode 100644 index 0000000..99a7136 --- /dev/null +++ b/custom_components/midea_ac_lan/select.py @@ -0,0 +1,57 @@ +"""Select for Midea Lan.""" + +from typing import cast + +from homeassistant.components.select import SelectEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from midealocal.device import MideaDevice + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up selects for device.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + selects = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.SELECT and entity_key in extra_switches: + dev = MideaSelect(device, entity_key) + selects.append(dev) + async_add_entities(selects) + + +class MideaSelect(MideaEntity, SelectEntity): + """Represent a Midea select.""" + + def __init__(self, device: MideaDevice, entity_key: str) -> None: + """Midea select init.""" + super().__init__(device, entity_key) + self._options_name = self._config.get("options") + + @property + def options(self) -> list[str]: + """Return entity options.""" + return cast("list", getattr(self._device, self._options_name)) + + @property + def current_option(self) -> str: + """Return entity current option.""" + return cast("str", self._device.get_attribute(self._entity_key)) + + def select_option(self, option: str) -> None: + """Select entity option.""" + self._device.set_attribute(self._entity_key, option) diff --git a/custom_components/midea_ac_lan/sensor.py b/custom_components/midea_ac_lan/sensor.py new file mode 100644 index 0000000..4d1db3f --- /dev/null +++ b/custom_components/midea_ac_lan/sensor.py @@ -0,0 +1,67 @@ +"""Sensor for Midea Lan.""" + +from typing import Any, cast + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SENSORS, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up sensors for device.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_sensors = config_entry.options.get(CONF_SENSORS, []) + sensors = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.SENSOR and entity_key in extra_sensors: + sensor = MideaSensor(device, entity_key) + sensors.append(sensor) + async_add_entities(sensors) + + +class MideaSensor(MideaEntity, SensorEntity): + """Represent a Midea sensor.""" + + @property + def native_value(self) -> StateType: + """Return entity value.""" + return cast("StateType", self._device.get_attribute(self._entity_key)) + + @property + def device_class(self) -> SensorDeviceClass: + """Return device class.""" + return cast("SensorDeviceClass", self._config.get("device_class")) + + @property + def state_class(self) -> SensorStateClass | None: + """Return state state.""" + return cast("SensorStateClass | None", self._config.get("state_class")) + + @property + def native_unit_of_measurement(self) -> str | None: + """Return unit of measurement.""" + return cast("str | None", self._config.get("unit")) + + @property + def capability_attributes(self) -> dict[str, Any] | None: + """Return capabilities.""" + return {"state_class": self.state_class} if self.state_class else {} diff --git a/custom_components/midea_ac_lan/services.yaml b/custom_components/midea_ac_lan/services.yaml new file mode 100644 index 0000000..4910317 --- /dev/null +++ b/custom_components/midea_ac_lan/services.yaml @@ -0,0 +1,18 @@ +--- +set_attribute: + fields: + device_id: + example: "1234567890" + attribute: + example: "eco_mode" + value: + example: true + +send_command: + fields: + device_id: + example: "1234567890" + cmd_type: + example: 2 + cmd_body: + example: "B0FF01370E0000A500" diff --git a/custom_components/midea_ac_lan/switch.py b/custom_components/midea_ac_lan/switch.py new file mode 100644 index 0000000..ef4b6a8 --- /dev/null +++ b/custom_components/midea_ac_lan/switch.py @@ -0,0 +1,50 @@ +"""Switch for Midea Lan.""" + +from typing import Any, cast + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICE_ID, CONF_SWITCHES, Platform +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up switches for device.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + switches = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.SWITCH and entity_key in extra_switches: + dev = MideaSwitch(device, entity_key) + switches.append(dev) + async_add_entities(switches) + + +class MideaSwitch(MideaEntity, ToggleEntity): + """Represent a Midea switch.""" + + @property + def is_on(self) -> bool: + """Return true if switch is on.""" + return cast("bool", self._device.get_attribute(self._entity_key)) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Turn on switch.""" + self._device.set_attribute(attr=self._entity_key, value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Turn off switch.""" + self._device.set_attribute(attr=self._entity_key, value=False) diff --git a/custom_components/midea_ac_lan/translations/de.json b/custom_components/midea_ac_lan/translations/de.json new file mode 100644 index 0000000..c6adc99 --- /dev/null +++ b/custom_components/midea_ac_lan/translations/de.json @@ -0,0 +1,715 @@ +{ + "config": { + "error": { + "preset_account": "Anmeldung mit voreingestelltem Konto fehlgeschlagen, bitte melden Sie dieses Problem", + "login_failed": "Anmeldung fehlgeschlagen, Konto oder Passwort ist falsch", + "no_devices": "Keine neuen verfügbaren Geräte im Netzwerk gefunden", + "device_exist": "Das Gerät ist bereits konfiguriert", + "config_incorrect": "Die Konfiguration ist falsch", + "connect_error": "Kann keine Verbindung zum Gerät herstellen", + "invalid_token": "Token oder Schlüssel hat ein falsches Format" + }, + "step": { + "user": { + "data": { + "way": "Hinzufügen von Geräten" + }, + "description": "Wählen Sie die Art des Hinzufügens eines Geräts", + "title": "Neues Gerät hinzufügen" + }, + "login": { + "data": { + "account": "Konto", + "password": "Passwort" + }, + "description": "Melden Sie sich mit Ihrem Midea-Konto an. Ihr Konto wird nur verwendet, um die Geräteinformationen zu erhalten.\nSie können die Konfiguration entfernen, nachdem alle Geräte konfiguriert wurden.", + "title": "Login" + }, + "discovery": { + "description": "IP-Adresse des Geräts, geben Sie \"auto\" ein, um es automatisch zu finden.\nSie können auch eine IP-Adresse verwenden, um innerhalb eines bestimmten Netzwerks zu suchen, z.B. \"192.168.1.255\".", + "title": "Suche", + "data": { + "ip_address": "IP-Adresse" + } + }, + "list": { + "description": "{table}", + "title": "Geräte" + }, + "auto": { + "data": { + "device": "Geräte" + }, + "description": "Wählen Sie ein Gerät zum Hinzufügen", + "title": "Neues Gerät gefunden" + }, + "manually": { + "data": { + "name": "Name (z.B. Klimaanlage Wohnzimmer)", + "device_id": "Geräte-ID", + "type": "Typ", + "ip_address": "IP-Adresse", + "port": "Port", + "model": "Modell", + "subtype": "Untertyp", + "protocol": "Protokoll", + "token": "Token", + "key": "Schlüssel" + }, + "description": "Konfiguration des Geräts", + "title": "Neues Gerät" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "Bar Door" + }, + "bar_door_overtime": { + "name": "Bar Door Overtime" + }, + "bathing_working": { + "name": "Bathing Working Status" + }, + "bottom_compartment_cooling": { + "name": "Bottom Compartment Cooling" + }, + "bottom_compartment_door": { + "name": "Bottom Compartment Door" + }, + "bottom_compartment_preheating": { + "name": "Bottom Compartment Preheating" + }, + "burning_state": { + "name": "Burning State" + }, + "cleaning_reminder": { + "name": "Cleaning Reminder" + }, + "compressor_status": { + "name": "Compressor Status" + }, + "cooking": { + "name": "Cooking" + }, + "filter_change_reminder": { + "name": "Filter Change Reminder" + }, + "filter_cleaning_reminder": { + "name": "Filter Cleaning Reminder" + }, + "finished": { + "name": "Finished" + }, + "flex_zone_door": { + "name": "Flex Door" + }, + "flex_zone_door_overtime": { + "name": "Flex Zone Door" + }, + "freezer_door": { + "name": "Freezer Door" + }, + "freezer_door_overtime": { + "name": "Freezer Door Overtime" + }, + "full_dust": { + "name": "Full of Dust" + }, + "heating": { + "name": "Heating" + }, + "heating_working": { + "name": "Heating Working Status" + }, + "keep_warm": { + "name": "Keep Warm" + }, + "lid_status": { + "name": "Lid Status" + }, + "middle_compartment_cooling": { + "name": "Middle Compartment Cooling" + }, + "middle_compartment_door": { + "name": "Middle Compartment Door" + }, + "middle_compartment_preheating": { + "name": "Middle Compartment Preheating" + }, + "oilcup_full": { + "name": "Oil-cup Full" + }, + "protection": { + "name": "Protection" + }, + "refrigerator_door": { + "name": "Refrigerator Door" + }, + "refrigerator_door_overtime": { + "name": "Refrigerator Door Overtime" + }, + "rinse_aid": { + "name": "Rinse Aid Shortage" + }, + "salt": { + "name": "Salt Shortage" + }, + "seat_status": { + "name": "Seat Status" + }, + "status_dhw": { + "name": "DHW status" + }, + "status_heating": { + "name": "Heating status" + }, + "status_ibh": { + "name": "IBH status" + }, + "status_tbh": { + "name": "TBH status" + }, + "tank_ejected": { + "name": "Tank Ejected" + }, + "tank_full": { + "name": "Tank status" + }, + "top_compartment_cooling": { + "name": "Top Compartment Cooling" + }, + "top_compartment_door": { + "name": "Top Compartment Door" + }, + "top_compartment_preheating": { + "name": "Top Compartment Preheating" + }, + "water_change_reminder": { + "name": "Water Change Reminder" + }, + "water_shortage": { + "name": "Water Shortage" + }, + "with_pressure": { + "name": "With Pressure" + }, + "zone1_room_temp_mode": { + "name": "Zone1 Room-temperature Mode" + }, + "zone1_water_temp_mode": { + "name": "Zone1 Water-temperature Mode" + }, + "zone2_room_temp_mode": { + "name": "Zone2 Room-temperature Mode" + }, + "zone2_water_temp_mode": { + "name": "Zone2 Water-temperature Mode" + } + }, + "climate": { + "climate_zone1": { + "name": "Zone1 Thermostat" + }, + "climate_zone2": { + "name": "Zone2 Thermostat" + } + }, + "fan": { + "fresh_air": { + "name": "Fresh Air" + } + }, + "lock": { + "child_lock": { + "name": "Kindersicherung" + } + }, + "number": { + "dry_level": { + "name": "Dry Level" + }, + "heating_level": { + "name": "Heating Level" + }, + "seat_temp_level": { + "name": "Seat Temperature Level" + }, + "water_temp_level": { + "name": "Water Temperature Level" + } + }, + "select": { + "detect_mode": { + "name": "Detect Mode" + }, + "direction": { + "name": "Direction" + }, + "fan_speed": { + "name": "Fan Speed" + }, + "mode": { + "name": "Mode" + }, + "oscillation_angle": { + "name": "Oscillation Angle" + }, + "oscillation_mode": { + "name": "Oscillation Mode" + }, + "screen_display": { + "name": "Screen Display" + }, + "silent_level": { + "name": "Silent Level" + }, + "tilting_angle": { + "name": "Tilting Angle" + }, + "water_level_set": { + "name": "Water Level Setting" + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "Bathing Leaving Water Temperature" + }, + "bottom_compartment_remaining": { + "name": "Bottom Compartment Remaining" + }, + "bottom_compartment_status": { + "name": "Bottom Compartment Status" + }, + "bottom_compartment_temperature": { + "name": "Bottom Compartment Temperature" + }, + "bottom_temperature": { + "name": "Bottom Temperature" + }, + "bright": { + "name": "Bright Level" + }, + "compressor_temperature": { + "name": "Compressor Temperature" + }, + "condenser_temperature": { + "name": "Condenser Temperature" + }, + "current_energy_consumption": { + "name": "Current Energy Consumption" + }, + "current_temperature": { + "name": "Current Temperature" + }, + "dehydration_speed": { + "name": "dehydration speed" + }, + "dehydration_time": { + "name": "dehydration time" + }, + "detergent": { + "name": "detergent" + }, + "energy_consumption": { + "name": "Energy Consumption" + }, + "error_code": { + "name": "Error Code" + }, + "fan_level": { + "name": "Fan level" + }, + "filter_life": { + "name": "Filter Life" + }, + "filter1_days": { + "name": "Filter1 Verfügbare Tage" + }, + "filter1_life": { + "name": "Filter1 Lebensdauer Level" + }, + "filter2_days": { + "name": "Filter2 Verfügbare Tage" + }, + "filter2_life": { + "name": "Filter2 Lebensdauer Level" + }, + "filter3_days": { + "name": "Filter3 Verfügbare Tage" + }, + "filter3_life": { + "name": "Filter3 Lebensdauer Level" + }, + "flex_zone_actual_temp": { + "name": "Flex Zone Actual Temperature" + }, + "flex_zone_setting_temp": { + "name": "Flex Zone Setting Temperature" + }, + "freezer_actual_temp": { + "name": "Freezer Actual Temperature" + }, + "freezer_setting_temp": { + "name": "Freezer Setting Temperature" + }, + "hcho": { + "name": "Methanal" + }, + "heating_leaving_temperature": { + "name": "Heating Leaving Water Temperature" + }, + "heating_power": { + "name": "Heating Power" + }, + "heating_time_remaining": { + "name": "Heating Time Remaining" + }, + "in_tds": { + "name": "TDS-Wert rein" + }, + "indoor_humidity": { + "name": "Indoor Humidity" + }, + "indoor_temperature": { + "name": "Indoor Temperature" + }, + "keep_warm_remaining": { + "name": "Keep Warm Remaining" + }, + "keep_warm_time": { + "name": "Keep Warm Time" + }, + "middle_compartment_remaining": { + "name": "Middle Compartment Remaining" + }, + "middle_compartment_status": { + "name": "Middle Compartment Status" + }, + "middle_compartment_temperature": { + "name": "Middle Compartment Temperature" + }, + "mode": { + "name": "Mode" + }, + "out_tds": { + "name": "TDS-Wert raus" + }, + "outdoor_temperature": { + "name": "Outdoor Temperature" + }, + "program": { + "name": "Program" + }, + "progress": { + "name": "Progress" + }, + "realtime_power": { + "name": "Realtime Power" + }, + "refrigerator_actual_temp": { + "name": "Refrigerator Actual Temperature" + }, + "refrigerator_setting_temp": { + "name": "Refrigerator Setting Temperature" + }, + "right_flex_zone_actual_temp": { + "name": "Right Flex Zone Actual Temperature" + }, + "right_flex_zone_setting_temp": { + "name": "Right Flex Zone Setting Temperature" + }, + "rinse_count": { + "name": "rinse count" + }, + "rinse_level": { + "name": "rinse level" + }, + "seat_temperature": { + "name": "Seat Temperature" + }, + "soak_time": { + "name": "soak time" + }, + "softener": { + "name": "softener" + }, + "softwater": { + "name": "Softwater Level" + }, + "status": { + "name": "Status" + }, + "storage_remaining": { + "name": "Storage Remaining" + }, + "tank": { + "name": "Tank" + }, + "tank_actual_temperature": { + "name": "Tank Actual Temperature" + }, + "target_temperature": { + "name": "Target Temperature" + }, + "time_remaining": { + "name": "Time Remaining" + }, + "top_compartment_remaining": { + "name": "Top Compartment Remaining" + }, + "top_compartment_status": { + "name": "Top Compartment Status" + }, + "top_compartment_temperature": { + "name": "Top Compartment Temperature" + }, + "top_temperature": { + "name": "Top Temperature" + }, + "total_energy_consumption": { + "name": "Total Energy Consumption" + }, + "total_produced_energy": { + "name": "Total produced energy" + }, + "tvoc": { + "name": "TVOC" + }, + "wash_level": { + "name": "rinse count" + }, + "wash_strength": { + "name": "wash strength" + }, + "wash_time": { + "name": "wash time" + }, + "water_consumption": { + "name": "Wasserverbrauch" + }, + "water_temperature": { + "name": "Water Temperature" + }, + "water_level": { + "name": "Water Level" + }, + "working_time": { + "name": "Working Time" + } + }, + "switch": { + "anion": { + "name": "Anion" + }, + "aux_heating": { + "name": "Aux Heating" + }, + "boost_mode": { + "name": "Boost Mode" + }, + "breezeless": { + "name": "Breezeless" + }, + "comfort_mode": { + "name": "Comfort Mode" + }, + "dhw_power": { + "name": "DHW Power" + }, + "disinfect": { + "name": "Disinfect" + }, + "dry": { + "name": "Dry" + }, + "eco_mode": { + "name": "ECO Mode" + }, + "fast_dhw": { + "name": "Fast DHW" + }, + "foam_shield": { + "name": "Foam Shield" + }, + "frost_protect": { + "name": "Frost Protect" + }, + "heating_power": { + "name": "Heating Power" + }, + "indirect_wind": { + "name": "Indirect Wind" + }, + "light": { + "name": "Licht" + }, + "link_to_ac": { + "name": "Link to AC" + }, + "main_light": { + "name": "Main Light" + }, + "main_power": { + "name": "Main Power" + }, + "natural_wind": { + "name": "Natural Wind" + }, + "night_light": { + "name": "Night Light" + }, + "oscillate": { + "name": "Oscillate" + }, + "power": { + "name": "Strom" + }, + "powerful_purify": { + "name": "Powerful Purification" + }, + "prompt_tone": { + "name": "Prompt Tone" + }, + "screen_display": { + "name": "Screen Display" + }, + "screen_display_alternate": { + "name": "Screen Display Alternate" + }, + "sensor_light": { + "name": "Sensor Light" + }, + "silent_mode": { + "name": "Silent Mode" + }, + "sleep_mode": { + "name": "Sleep Mode" + }, + "smart_eye": { + "name": "Smart Eye" + }, + "smart_volume": { + "name": "Smart Volume" + }, + "smelly_sensor": { + "name": "Geruchssensor" + }, + "standby": { + "name": "Standby" + }, + "start": { + "name": "Start" + }, + "storage": { + "name": "Storage" + }, + "swing": { + "name": "swing" + }, + "swing_horizontal": { + "name": "Swing Horizontal" + }, + "swing_vertical": { + "name": "Swing Vertical" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "Variable Heating" + }, + "ventilation": { + "name": "Lüftung" + }, + "whole_tank_heating": { + "name": "Whole Tank Heating" + }, + "zero_cold_pulse": { + "name": "Zero Cold Water (Pulse)" + }, + "zero_cold_water": { + "name": "Zero Cold Water" + }, + "zone1_curve": { + "name": "Zone1 Curve" + }, + "zone1_power": { + "name": "Zone1 Power" + }, + "zone2_curve": { + "name": "Zone2 Curve" + }, + "zone2_power": { + "name": "Zone2 Power" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "Domestic hot water" + }, + "bathing": { + "name": "Bathing" + }, + "heating": { + "name": "Heating" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP-Adresse", + "refresh_interval": "Aktualisierungsintervall (0 bedeutet, dass nicht aktiv aktualisiert wird)", + "sensors": "Zusätzliche Sensoren", + "switches": "Zusätzliche Schalter", + "customize": "Anpassen" + }, + "title": "Konfiguration" + } + }, + "abort": { + "account_option": "Das Konto unterstützt diesen Vorgang nicht.\nKlicken Sie auf \"GERÄT HINZUFÜGEN\" um ein neues Gerät hinzuzufügen." + } + }, + "services": { + "set_attribute": { + "name": "Attribut setzen", + "description": "Einstellen des Attributwerts des Geräts", + "fields": { + "device_id": { + "name": "Geräte-ID", + "description": "Die Geräte-ID des Geräts" + }, + "attribute": { + "name": "Attribut", + "description": "Der Name des Attributs, das gesetzt werden soll" + }, + "value": { + "name": "Wert", + "description": "Der Attributwert, der gesetzt werden soll" + } + } + }, + "send_command": { + "name": "Befehl an Gerät senden", + "description": "Senden eines Befehls an das Gerät", + "fields": { + "device_id": { + "name": "Geräte-ID", + "description": "Die Geräte-ID des Geräts" + }, + "cmd_type": { + "name": "Befehlstyp", + "description": "Die Art des Befehls, sollte 3 (abfragen) oder 2 (setzen) sein" + }, + "cmd_body": { + "name": "Befehl", + "description": "Der Hauptteil des Befehls (ohne den MSmart-Protokollkopf und die Prüfsumme am Ende)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/en.json b/custom_components/midea_ac_lan/translations/en.json new file mode 100644 index 0000000..50a887d --- /dev/null +++ b/custom_components/midea_ac_lan/translations/en.json @@ -0,0 +1,834 @@ +{ + "config": { + "error": { + "preset_account": "Failed to login with preset account, please report this issue", + "login_failed": "Failed to login, account or password was wrong", + "no_devices": "No new available appliances found on the network", + "device_exist": "Appliance is already configured", + "config_incorrect": "The configuration is incorrect", + "connect_error": "Can't connect to appliance", + "invalid_token": "Token or Key in an incorrect format" + }, + "step": { + "user": { + "data": { + "way": "Adding appliances" + }, + "description": "Choose the way to add a appliance", + "title": "Add new appliance" + }, + "login": { + "data": { + "account": "Account", + "password": "Password" + }, + "description": "Login and storage your Midea account only for getting the appliance info.\nYou can remove this configuration after all appliance configured.", + "title": "Login" + }, + "discovery": { + "description": "IP address for device, enter \"auto\" to discover automatically\nYou can also use an IP address to search within a specified network, such as \"192.168.1.255\"", + "title": "Search", + "data": { + "ip_address": "IP address" + } + }, + "list": { + "description": "{table}", + "title": "Appliances" + }, + "auto": { + "data": { + "device": "Appliances" + }, + "description": "Choose a appliance to add", + "title": "New appliance found" + }, + "manually": { + "data": { + "name": "Name (e.g. Living room AC)", + "device_id": "Appliance code", + "type": "Type", + "ip_address": "IP address", + "port": "Port", + "model": "Model", + "subtype": "Subtype", + "protocol": "Protocol", + "token": "Token", + "key": "Key" + }, + "description": "Configuration of appliance", + "title": "New appliance" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "Bar Door" + }, + "bar_door_overtime": { + "name": "Bar Door Overtime" + }, + "bathing_working": { + "name": "Bathing Working Status" + }, + "bottom_compartment_cooling": { + "name": "Bottom Compartment Cooling" + }, + "bottom_compartment_door": { + "name": "Bottom Compartment Door" + }, + "bottom_compartment_preheating": { + "name": "Bottom Compartment Preheating" + }, + "burning_state": { + "name": "Burning State" + }, + "cleaning_reminder": { + "name": "Cleaning Reminder" + }, + "compressor_status": { + "name": "Compressor Status" + }, + "cooking": { + "name": "Cooking" + }, + "filter_change_reminder": { + "name": "Filter Change Reminder" + }, + "filter_cleaning_reminder": { + "name": "Filter Cleaning Reminder" + }, + "finished": { + "name": "Finished" + }, + "flex_zone_door": { + "name": "Flex Door" + }, + "flex_zone_door_overtime": { + "name": "Flex Zone Door" + }, + "freezer_door": { + "name": "Freezer Door" + }, + "freezer_door_overtime": { + "name": "Freezer Door Overtime" + }, + "full_dust": { + "name": "Full of Dust" + }, + "heating": { + "name": "Heating" + }, + "heating_working": { + "name": "Heating Working Status" + }, + "keep_warm": { + "name": "Keep Warm" + }, + "lid_status": { + "name": "Lid Status" + }, + "middle_compartment_cooling": { + "name": "Middle Compartment Cooling" + }, + "middle_compartment_door": { + "name": "Middle Compartment Door" + }, + "middle_compartment_preheating": { + "name": "Middle Compartment Preheating" + }, + "top_elec_heat": { + "name": "Top Electric Heat" + }, + "bottom_elec_heat": { + "name": "Bottom Electric Heat" + }, + "mute_effect": { + "name": "Mute Effect" + }, + "mute_status": { + "name": "Mute Status" + }, + "multi_terminal": { + "name": "Multi Terminal" + }, + "oilcup_full": { + "name": "Oil-cup Full" + }, + "protection": { + "name": "Protection" + }, + "refrigerator_door": { + "name": "Refrigerator Door" + }, + "refrigerator_door_overtime": { + "name": "Refrigerator Door Overtime" + }, + "rinse_aid": { + "name": "Rinse Aid Shortage" + }, + "salt": { + "name": "Salt Shortage" + }, + "seat_status": { + "name": "Seat Status" + }, + "smart_grid": { + "name": "Smart Grid" + }, + "status_dhw": { + "name": "DHW status" + }, + "status_heating": { + "name": "Heating status" + }, + "status_ibh": { + "name": "IBH status" + }, + "status_tbh": { + "name": "TBH status" + }, + "tank_ejected": { + "name": "Tank Ejected" + }, + "tank_full": { + "name": "Tank status" + }, + "top_compartment_cooling": { + "name": "Top Compartment Cooling" + }, + "top_compartment_door": { + "name": "Top Compartment Door" + }, + "top_compartment_preheating": { + "name": "Top Compartment Preheating" + }, + "water_change_reminder": { + "name": "Water Change Reminder" + }, + "water_shortage": { + "name": "Water Shortage" + }, + "with_pressure": { + "name": "With Pressure" + }, + "zone1_room_temp_mode": { + "name": "Zone1 Room-temperature Mode" + }, + "zone1_water_temp_mode": { + "name": "Zone1 Water-temperature Mode" + }, + "zone2_room_temp_mode": { + "name": "Zone2 Room-temperature Mode" + }, + "microcrystal_fresh": { + "name": "Microcrystal Fresh" + }, + "electronic_smell": { + "name": "Deodorizing sterilizing" + } + }, + "climate": { + "climate_zone1": { + "name": "Zone1 Thermostat" + }, + "climate_zone2": { + "name": "Zone2 Thermostat" + }, + "climate_key": { + "state_attributes": { + "fan_mode": { + "state": { + "silent": "Silent", + "full": "Full" + } + } + } + } + }, + "fan": { + "fresh_air": { + "name": "Fresh Air" + } + }, + "lock": { + "child_lock": { + "name": "Child Lock" + } + }, + "number": { + "dry_level": { + "name": "Dry Level" + }, + "heating_level": { + "name": "Heating Level" + }, + "seat_temp_level": { + "name": "Seat Temperature Level" + }, + "water_temp_level": { + "name": "Water Temperature Level" + }, + "fan_speed_percent": { + "name": "Fan Speed Percent" + } + }, + "select": { + "detect_mode": { + "name": "Detect Mode" + }, + "direction": { + "name": "Direction" + }, + "fan_speed": { + "name": "Fan Speed" + }, + "mode": { + "name": "Mode" + }, + "oscillation_angle": { + "name": "Oscillation Angle" + }, + "oscillation_mode": { + "name": "Oscillation Mode" + }, + "screen_display": { + "name": "Screen Display" + }, + "silent_level": { + "name": "Silent Level" + }, + "tilting_angle": { + "name": "Tilting Angle" + }, + "water_level_set": { + "name": "Water Level Setting" + }, + "wind_lr_angle": { + "name": "Airflow Horizontal", + "state": { + "off": "Off", + "left": "Left", + "left-mid": "Left-Mid", + "middle": "Middle", + "right-mid": "Right-Mid", + "right": "Right" + } + }, + "wind_ud_angle": { + "name": "Airflow Vertical", + "state": { + "off": "Off", + "up": "Up", + "up-mid": "Up-Mid", + "middle": "Middle", + "down-mid": "Down-Mid", + "down": "Down" + } + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "Bathing Leaving Water Temperature" + }, + "bottom_compartment_remaining": { + "name": "Bottom Compartment Remaining" + }, + "bottom_compartment_status": { + "name": "Bottom Compartment Status" + }, + "bottom_compartment_temperature": { + "name": "Bottom Compartment Temperature" + }, + "bottom_temperature": { + "name": "Bottom Temperature" + }, + "bright": { + "name": "Bright Level" + }, + "compressor_temperature": { + "name": "Compressor Temperature" + }, + "condenser_temperature": { + "name": "Condenser Temperature" + }, + "current_energy_consumption": { + "name": "Current Energy Consumption" + }, + "current_temperature": { + "name": "Current Temperature" + }, + "dehydration_speed": { + "name": "Dehydration speed" + }, + "dehydration_time": { + "name": "Dehydration time" + }, + "dehydration_time_value": { + "name": "Dehydration time value" + }, + "wash_time_value": { + "name": "Wash time value" + }, + "stains": { + "name": "Stains" + }, + "dirty_degree": { + "name": "Dirty degree" + }, + "detergent": { + "name": "Detergent" + }, + "intensity": { + "name": "Intensity" + }, + "dryness_level": { + "name": "Dryness Level" + }, + "dry_temperature": { + "name": "Dry Temperature" + }, + "door_warn": { + "name": "Door Warn" + }, + "ai_switch": { + "name": "AI Switch" + }, + "material": { + "name": "Material" + }, + "water_box": { + "name": "Water Box" + }, + "energy_consumption": { + "name": "Energy Consumption" + }, + "error_code": { + "name": "Error Code" + }, + "fan_level": { + "name": "Fan level" + }, + "filter_life": { + "name": "Filter Life" + }, + "filter1_days": { + "name": "Filter1 Available Days" + }, + "filter1_life": { + "name": "Filter1 Life Level" + }, + "filter2_days": { + "name": "Filter2 Available Days" + }, + "filter2_life": { + "name": "Filter2 Life Level" + }, + "filter3_days": { + "name": "Filter3 Available Days" + }, + "filter3_life": { + "name": "Filter3 Life Level" + }, + "flex_zone_actual_temp": { + "name": "Flex Zone Actual Temperature" + }, + "flex_zone_setting_temp": { + "name": "Flex Zone Setting Temperature" + }, + "freezer_actual_temp": { + "name": "Freezer Actual Temperature" + }, + "freezer_setting_temp": { + "name": "Freezer Setting Temperature" + }, + "hcho": { + "name": "Methanal" + }, + "heating_leaving_temperature": { + "name": "Heating Leaving Water Temperature" + }, + "heating_power": { + "name": "Heating Power" + }, + "heating_time_remaining": { + "name": "Heating Time Remaining" + }, + "in_tds": { + "name": "In TDS" + }, + "indoor_humidity": { + "name": "Indoor Humidity" + }, + "indoor_temperature": { + "name": "Indoor Temperature" + }, + "keep_warm_remaining": { + "name": "Keep Warm Remaining" + }, + "keep_warm_time": { + "name": "Keep Warm Time" + }, + "middle_compartment_remaining": { + "name": "Middle Compartment Remaining" + }, + "middle_compartment_status": { + "name": "Middle Compartment Status" + }, + "middle_compartment_temperature": { + "name": "Middle Compartment Temperature" + }, + "mode": { + "name": "Mode" + }, + "out_tds": { + "name": "Out TDS" + }, + "outdoor_temperature": { + "name": "Outdoor Temperature" + }, + "program": { + "name": "Program" + }, + "progress": { + "name": "Progress" + }, + "realtime_power": { + "name": "Realtime Power" + }, + "refrigerator_actual_temp": { + "name": "Refrigerator Actual Temperature" + }, + "refrigerator_setting_temp": { + "name": "Refrigerator Setting Temperature" + }, + "right_flex_zone_actual_temp": { + "name": "Right Flex Zone Actual Temperature" + }, + "right_flex_zone_setting_temp": { + "name": "Right Flex Zone Setting Temperature" + }, + "rinse_count": { + "name": "Rinse count" + }, + "rinse_level": { + "name": "Rinse level" + }, + "seat_temperature": { + "name": "Seat Temperature" + }, + "soak_time": { + "name": "Soak time" + }, + "softener": { + "name": "Softener" + }, + "softwater": { + "name": "Softwater Level" + }, + "status": { + "name": "Status" + }, + "storage_remaining": { + "name": "Storage Remaining" + }, + "tank": { + "name": "Tank" + }, + "tank_actual_temperature": { + "name": "Tank Actual Temperature" + }, + "target_temperature": { + "name": "Target Temperature" + }, + "time_remaining": { + "name": "Time Remaining" + }, + "top_compartment_remaining": { + "name": "Top Compartment Remaining" + }, + "top_compartment_status": { + "name": "Top Compartment Status" + }, + "top_compartment_temperature": { + "name": "Top Compartment Temperature" + }, + "top_temperature": { + "name": "Top Temperature" + }, + "total_energy_consumption": { + "name": "Total Energy Consumption" + }, + "total_produced_energy": { + "name": "Total produced energy" + }, + "tvoc": { + "name": "TVOC" + }, + "wash_strength": { + "name": "Wash strength" + }, + "wash_time": { + "name": "Wash time" + }, + "water_consumption": { + "name": "Water Consumption" + }, + "water_temperature": { + "name": "Water Temperature" + }, + "water_level": { + "name": "Water Level" + }, + "wind": { + "name": "Wind" + }, + "typeinfo": { + "name": "Type Info" + }, + "elec_heat": { + "name": "Electric Heat" + }, + "water_pump": { + "name": "Water Pump" + }, + "four_way": { + "name": "Four Way Valve" + }, + "back_water": { + "name": "Back Water" + }, + "sterilize": { + "name": "Sterilize" + }, + "working_time": { + "name": "Working Time" + }, + "humidity": { + "name": "Humidity" + }, + "variable_mode": { + "name": "Variable Mode" + } + }, + "switch": { + "anion": { + "name": "Anion" + }, + "aux_heating": { + "name": "Aux Heating" + }, + "boost_mode": { + "name": "Boost Mode" + }, + "breezeless": { + "name": "Breezeless" + }, + "comfort_mode": { + "name": "Comfort Mode" + }, + "dhw_power": { + "name": "DHW Power" + }, + "disinfect": { + "name": "Disinfect" + }, + "dry": { + "name": "Dry" + }, + "cold_water_single": { + "name": "Cold Water Single" + }, + "cold_water_dot": { + "name": "Cold Water Dot" + }, + "eco_mode": { + "name": "ECO Mode" + }, + "fast_dhw": { + "name": "Fast DHW" + }, + "foam_shield": { + "name": "Foam Shield" + }, + "frost_protect": { + "name": "Frost Protect" + }, + "heating_power": { + "name": "Heating Power" + }, + "indirect_wind": { + "name": "Indirect Wind" + }, + "light": { + "name": "Light" + }, + "link_to_ac": { + "name": "Link to AC" + }, + "main_light": { + "name": "Main Light" + }, + "main_power": { + "name": "Main Power" + }, + "natural_wind": { + "name": "Natural Wind" + }, + "night_light": { + "name": "Night Light" + }, + "oscillate": { + "name": "Oscillate" + }, + "power": { + "name": "Power" + }, + "powerful_purify": { + "name": "Powerful Purification" + }, + "prompt_tone": { + "name": "Prompt Tone" + }, + "screen_display": { + "name": "Screen Display" + }, + "screen_display_alternate": { + "name": "Screen Display Alternate" + }, + "sensor_light": { + "name": "Sensor Light" + }, + "silent_mode": { + "name": "Silent Mode" + }, + "sleep_mode": { + "name": "Sleep Mode" + }, + "smart_eye": { + "name": "Smart Eye" + }, + "smart_volume": { + "name": "Smart Volume" + }, + "smelly_sensor": { + "name": "Smelly Sensor" + }, + "standby": { + "name": "Standby" + }, + "start": { + "name": "Start" + }, + "storage": { + "name": "Storage" + }, + "swing": { + "name": "Swing" + }, + "swing_horizontal": { + "name": "Swing Horizontal" + }, + "swing_vertical": { + "name": "Swing Vertical" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "Variable Heating" + }, + "ventilation": { + "name": "Ventilation" + }, + "whole_tank_heating": { + "name": "Whole Tank Heating" + }, + "zero_cold_pulse": { + "name": "Zero Cold Water (Pulse)" + }, + "zero_cold_water": { + "name": "Zero Cold Water" + }, + "zone1_curve": { + "name": "Zone1 Curve" + }, + "zone1_power": { + "name": "Zone1 Power" + }, + "zone2_curve": { + "name": "Zone2 Curve" + }, + "zone2_power": { + "name": "Zone2 Power" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "Domestic hot water" + }, + "bathing": { + "name": "Bathing" + }, + "heating": { + "name": "Heating" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP address", + "refresh_interval": "Refresh interval (0 means not refreshing actively)", + "sensors": "Extra sensors", + "switches": "Extra controls", + "customize": "Customize" + }, + "title": "Configure" + } + }, + "abort": { + "account_option": "The account does not supports this operation.\nClick \"ADD DEVICE\" to add a new device." + } + }, + "services": { + "set_attribute": { + "name": "Set attribute", + "description": "Set the attribute value of device", + "fields": { + "device_id": { + "name": "Appliance code", + "description": "The appliance code (Device ID) of appliance" + }, + "attribute": { + "name": "Attribute", + "description": "The attribute name want to set" + }, + "value": { + "name": "Value", + "description": "The attribute value want to set" + } + } + }, + "send_command": { + "name": "Customize command", + "description": "Send a customize command to device", + "fields": { + "device_id": { + "name": "Appliance code", + "description": "The appliance code (Device ID) of appliance" + }, + "cmd_type": { + "name": "Command type", + "description": "The type of command,should be 3(query) or 2(set)" + }, + "cmd_body": { + "name": "Command body", + "description": "The body of command, (not include MSmart protocol head and checksum at the end)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/es.json b/custom_components/midea_ac_lan/translations/es.json new file mode 100644 index 0000000..ea1a3b8 --- /dev/null +++ b/custom_components/midea_ac_lan/translations/es.json @@ -0,0 +1,795 @@ +{ + "config": { + "error": { + "preset_account": "Fallo al iniciar sesión con la cuenta preestablecida, por favor reporte este problema", + "login_failed": "Fallo al iniciar sesión, la cuenta o la contraseña son incorrectas", + "no_devices": "No se encontraron nuevos electrodomésticos disponibles en la red", + "device_exist": "El electrodoméstico ya está configurado", + "config_incorrect": "La configuración es incorrecta", + "connect_error": "No se puede conectar con el electrodoméstico", + "invalid_token": "Identificador o clave en un formato incorrecto" + }, + "step": { + "user": { + "data": { + "way": "Añadir electrodomésticos" + }, + "description": "Elija la forma de añadir un electrodoméstico", + "title": "Añadir nuevo electrodoméstico" + }, + "login": { + "data": { + "account": "Cuenta", + "password": "Contraseña" + }, + "description": "Inicie sesión y guarde su cuenta Midea solo para obtener la información del electrodoméstico.\nPuede eliminar esta configuración después de haber configurado todos los dispositivos.", + "title": "Iniciar sesión" + }, + "discovery": { + "description": "Dirección IP del dispositivo, escriba \"auto\" para descubrir automáticamente.\nTambién puede usar una dirección IP para buscar dentro de una red específica, como \"192.168.1.255\"", + "title": "Buscar", + "data": { + "ip_address": "Dirección IP" + } + }, + "list": { + "description": "{table}", + "title": "Electrodomésticos" + }, + "auto": { + "data": { + "device": "Electrodomésticos" + }, + "description": "Elija un electrodoméstico para añadir", + "title": "Nuevo electrodoméstico encontrado" + }, + "manually": { + "data": { + "name": "Nombre (ej. AA Salón)", + "device_id": "Código del electrodoméstico", + "type": "Tipo", + "ip_address": "Dirección IP", + "port": "Puerto", + "model": "Modelo", + "subtype": "Subtipo", + "protocol": "Protocolo", + "token": "Token", + "key": "Clave" + }, + "description": "Configuración del electrodoméstico", + "title": "Nuevo electrodoméstico" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "Puerta del bar" + }, + "bar_door_overtime": { + "name": "Puerta del bar abierta demasiado tiempo" + }, + "bathing_working": { + "name": "Estado de funcionamiento del baño" + }, + "bottom_compartment_cooling": { + "name": "Enfriamiento del compartimento inferior" + }, + "bottom_compartment_door": { + "name": "Puerta del compartimento inferior" + }, + "bottom_compartment_preheating": { + "name": "Precalentamiento del compartimento inferior" + }, + "burning_state": { + "name": "Estado de combustión" + }, + "cleaning_reminder": { + "name": "Recordatorio de limpieza" + }, + "compressor_status": { + "name": "Estado del compresor" + }, + "cooking": { + "name": "Cocinando" + }, + "filter_change_reminder": { + "name": "Recordatorio de cambio de filtro" + }, + "filter_cleaning_reminder": { + "name": "Recordatorio de limpieza de filtro" + }, + "finished": { + "name": "Terminado" + }, + "flex_zone_door": { + "name": "Puerta de zona flexible" + }, + "flex_zone_door_overtime": { + "name": "Puerta de zona flexible abierta demasiado tiempo" + }, + "freezer_door": { + "name": "Puerta del congelador" + }, + "freezer_door_overtime": { + "name": "Puerta del congelador abierta demasiado tiempo" + }, + "full_dust": { + "name": "Lleno de polvo" + }, + "heating": { + "name": "Calentando" + }, + "heating_working": { + "name": "Estado de funcionamiento de calefacción" + }, + "keep_warm": { + "name": "Mantener caliente" + }, + "lid_status": { + "name": "Estado de la tapa" + }, + "middle_compartment_cooling": { + "name": "Enfriamiento del compartimento medio" + }, + "middle_compartment_door": { + "name": "Puerta del compartimento medio" + }, + "middle_compartment_preheating": { + "name": "Precalentamiento del compartimento medio" + }, + "oilcup_full": { + "name": "Depósito de aceite lleno" + }, + "protection": { + "name": "Protección" + }, + "refrigerator_door": { + "name": "Puerta del frigorífico" + }, + "refrigerator_door_overtime": { + "name": "Puerta del frigorífico abierta demasiado tiempo" + }, + "rinse_aid": { + "name": "Escasez de abrillantador" + }, + "salt": { + "name": "Escasez de sal" + }, + "seat_status": { + "name": "Estado del asiento" + }, + "status_dhw": { + "name": "Estado ACS" + }, + "status_heating": { + "name": "Estado calefacción" + }, + "status_ibh": { + "name": "Estado IBH" + }, + "status_tbh": { + "name": "Estado TBH" + }, + "tank_ejected": { + "name": "Depósito extraído" + }, + "tank_full": { + "name": "Estado del depósito" + }, + "top_compartment_cooling": { + "name": "Enfriamiento del compartimento superior" + }, + "top_compartment_door": { + "name": "Puerta del compartimento superior" + }, + "top_compartment_preheating": { + "name": "Precalentamiento del compartimento superior" + }, + "water_change_reminder": { + "name": "Recordatorio de cambio de agua" + }, + "water_shortage": { + "name": "Escasez de agua" + }, + "with_pressure": { + "name": "Con presión" + }, + "zone1_room_temp_mode": { + "name": "Zona 1 Modo temperatura ambiente" + }, + "zone1_water_temp_mode": { + "name": "Zona 1 Modo temperatura del agua" + }, + "zone2_room_temp_mode": { + "name": "Zona 2 Modo temperatura ambiente" + }, + "microcrystal_fresh": { + "name": "Frescura microcristalina" + }, + "electronic_smell": { + "name": "Esterilización desodorizante" + } + }, + "climate": { + "climate_zone1": { + "name": "Termostato Zona 1" + }, + "climate_zone2": { + "name": "Termostato Zona 2" + }, + "climate_key": { + "state_attributes": { + "fan_mode": { + "state": { + "silent": "Silencioso", + "full": "Máximo" + } + } + } + } + }, + "fan": { + "fresh_air": { + "name": "Aire fresco" + } + }, + "lock": { + "child_lock": { + "name": "Bloqueo infantil" + } + }, + "number": { + "dry_level": { + "name": "Nivel de secado" + }, + "heating_level": { + "name": "Nivel de calefacción" + }, + "seat_temp_level": { + "name": "Nivel de temperatura del asiento" + }, + "water_temp_level": { + "name": "Nivel de temperatura del agua" + }, + "fan_speed_percent": { + "name": "Porcentaje de velocidad del ventilador" + } + }, + "select": { + "detect_mode": { + "name": "Modo de detección" + }, + "direction": { + "name": "Dirección" + }, + "fan_speed": { + "name": "Velocidad del ventilador" + }, + "mode": { + "name": "Modo" + }, + "oscillation_angle": { + "name": "Ángulo de oscilación" + }, + "oscillation_mode": { + "name": "Modo de oscilación" + }, + "screen_display": { + "name": "Visualización de pantalla" + }, + "silent_level": { + "name": "Nivel de silencio" + }, + "tilting_angle": { + "name": "Ángulo de inclinación" + }, + "water_level_set": { + "name": "Ajuste de nivel de agua" + }, + "wind_lr_angle": { + "name": "Flujo de aire horizontal", + "state": { + "off": "Apagado", + "left": "Izquierda", + "left-mid": "Izquierda-Medio", + "middle": "Medio", + "right-mid": "Derecha-Medio", + "right": "Derecha" + } + }, + "wind_ud_angle": { + "name": "Flujo de aire vertical", + "state": { + "off": "Apagado", + "up": "Arriba", + "up-mid": "Arriba-Medio", + "middle": "Medio", + "down-mid": "Abajo-Medio", + "down": "Abajo" + } + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "Temperatura de salida del agua de baño" + }, + "bottom_compartment_remaining": { + "name": "Tiempo restante compartimento inferior" + }, + "bottom_compartment_status": { + "name": "Estado del compartimento inferior" + }, + "bottom_compartment_temperature": { + "name": "Temperatura del compartimento inferior" + }, + "bottom_temperature": { + "name": "Temperatura inferior" + }, + "bright": { + "name": "Nivel de brillo" + }, + "compressor_temperature": { + "name": "Temperatura del compresor" + }, + "condenser_temperature": { + "name": "Temperatura del condensador" + }, + "current_energy_consumption": { + "name": "Consumo de energía actual" + }, + "current_temperature": { + "name": "Temperatura actual" + }, + "dehydration_speed": { + "name": "Velocidad de centrifugado" + }, + "dehydration_time": { + "name": "Tiempo de centrifugado" + }, + "dehydration_time_value": { + "name": "Valor del tiempo de centrifugado" + }, + "wash_time_value": { + "name": "Valor del tiempo de lavado" + }, + "stains": { + "name": "Manchas" + }, + "dirty_degree": { + "name": "Grado de suciedad" + }, + "detergent": { + "name": "Detergente" + }, + "intensity": { + "name": "Intensidad" + }, + "dryness_level": { + "name": "Nivel de sequedad" + }, + "dry_temperature": { + "name": "Temperatura de secado" + }, + "door_warn": { + "name": "Advertencia de puerta" + }, + "ai_switch": { + "name": "Interruptor IA" + }, + "material": { + "name": "Material" + }, + "water_box": { + "name": "Caja de agua" + }, + "energy_consumption": { + "name": "Consumo de energía" + }, + "error_code": { + "name": "Código de error" + }, + "fan_level": { + "name": "Nivel del ventilador" + }, + "filter_life": { + "name": "Vida útil del filtro" + }, + "filter1_days": { + "name": "Días disponibles Filtro 1" + }, + "filter1_life": { + "name": "Nivel de vida Filtro 1" + }, + "filter2_days": { + "name": "Días disponibles Filtro 2" + }, + "filter2_life": { + "name": "Nivel de vida Filtro 2" + }, + "filter3_days": { + "name": "Días disponibles Filtro 3" + }, + "filter3_life": { + "name": "Nivel de vida Filtro 3" + }, + "flex_zone_actual_temp": { + "name": "Temperatura real de zona flexible" + }, + "flex_zone_setting_temp": { + "name": "Temperatura establecida de zona flexible" + }, + "freezer_actual_temp": { + "name": "Temperatura real del congelador" + }, + "freezer_setting_temp": { + "name": "Temperatura establecida del congelador" + }, + "hcho": { + "name": "Formaldehído" + }, + "heating_leaving_temperature": { + "name": "Temperatura de salida del agua de calefacción" + }, + "heating_power": { + "name": "Potencia de calefacción" + }, + "heating_time_remaining": { + "name": "Tiempo restante de calefacción" + }, + "in_tds": { + "name": "TDS de entrada" + }, + "indoor_humidity": { + "name": "Humedad interior" + }, + "indoor_temperature": { + "name": "Temperatura interior" + }, + "keep_warm_remaining": { + "name": "Tiempo restante de mantener caliente" + }, + "keep_warm_time": { + "name": "Tiempo de mantener caliente" + }, + "middle_compartment_remaining": { + "name": "Tiempo restante compartimento medio" + }, + "middle_compartment_status": { + "name": "Estado del compartimento medio" + }, + "middle_compartment_temperature": { + "name": "Temperatura del compartimento medio" + }, + "mode": { + "name": "Modo" + }, + "out_tds": { + "name": "TDS de salida" + }, + "outdoor_temperature": { + "name": "Temperatura exterior" + }, + "program": { + "name": "Programa" + }, + "progress": { + "name": "Progreso" + }, + "realtime_power": { + "name": "Potencia en tiempo real" + }, + "refrigerator_actual_temp": { + "name": "Temperatura real del frigorífico" + }, + "refrigerator_setting_temp": { + "name": "Temperatura establecida del frigorífico" + }, + "right_flex_zone_actual_temp": { + "name": "Temperatura real de zona flexible derecha" + }, + "right_flex_zone_setting_temp": { + "name": "Temperatura establecida de zona flexible derecha" + }, + "rinse_count": { + "name": "Recuento de aclarados" + }, + "rinse_level": { + "name": "Nivel de aclarado" + }, + "seat_temperature": { + "name": "Temperatura del asiento" + }, + "soak_time": { + "name": "Tiempo de remojo" + }, + "softener": { + "name": "Suavizante" + }, + "softwater": { + "name": "Nivel de agua blanda" + }, + "status": { + "name": "Estado" + }, + "storage_remaining": { + "name": "Almacenamiento restante" + }, + "tank": { + "name": "Depósito" + }, + "tank_actual_temperature": { + "name": "Temperatura real del depósito" + }, + "target_temperature": { + "name": "Temperatura objetivo" + }, + "time_remaining": { + "name": "Tiempo restante" + }, + "top_compartment_remaining": { + "name": "Tiempo restante compartimento superior" + }, + "top_compartment_status": { + "name": "Estado del compartimento superior" + }, + "top_compartment_temperature": { + "name": "Temperatura del compartimento superior" + }, + "top_temperature": { + "name": "Temperatura superior" + }, + "total_energy_consumption": { + "name": "Consumo total de energía" + }, + "total_produced_energy": { + "name": "Energía total producida" + }, + "tvoc": { + "name": "COVT" + }, + "wash_strength": { + "name": "Potencia de lavado" + }, + "wash_time": { + "name": "Tiempo de lavado" + }, + "water_consumption": { + "name": "Consumo de agua" + }, + "water_temperature": { + "name": "Temperatura del agua" + }, + "water_level": { + "name": "Nivel de agua" + }, + "working_time": { + "name": "Tiempo de funcionamiento" + }, + "humidity": { + "name": "Humedad" + }, + "variable_mode": { + "name": "Modo variable" + } + }, + "switch": { + "anion": { + "name": "Anión" + }, + "aux_heating": { + "name": "Calefacción auxiliar" + }, + "boost_mode": { + "name": "Modo potente" + }, + "breezeless": { + "name": "Sin corrientes" + }, + "comfort_mode": { + "name": "Modo confort" + }, + "dhw_power": { + "name": "Potencia ACS" + }, + "disinfect": { + "name": "Desinfectar" + }, + "dry": { + "name": "Secado" + }, + "cold_water_single": { + "name": "Agua fría individual" + }, + "cold_water_dot": { + "name": "Agua fría punto" + }, + "eco_mode": { + "name": "Modo ECO" + }, + "fast_dhw": { + "name": "ACS Rápido" + }, + "foam_shield": { + "name": "Escudo de espuma" + }, + "frost_protect": { + "name": "Protección contra heladas" + }, + "heating_power": { + "name": "Potencia de calefacción" + }, + "indirect_wind": { + "name": "Viento indirecto" + }, + "light": { + "name": "Luz" + }, + "link_to_ac": { + "name": "Vincular a AA" + }, + "main_light": { + "name": "Luz principal" + }, + "main_power": { + "name": "Alimentación principal" + }, + "natural_wind": { + "name": "Viento natural" + }, + "night_light": { + "name": "Luz nocturna" + }, + "oscillate": { + "name": "Oscilar" + }, + "power": { + "name": "Encendido" + }, + "powerful_purify": { + "name": "Purificación potente" + }, + "prompt_tone": { + "name": "Tono de aviso" + }, + "screen_display": { + "name": "Visualización de pantalla" + }, + "screen_display_alternate": { + "name": "Pantalla alterna" + }, + "sensor_light": { + "name": "Luz con sensor" + }, + "silent_mode": { + "name": "Modo silencioso" + }, + "sleep_mode": { + "name": "Modo sueño" + }, + "smart_eye": { + "name": "Ojo inteligente" + }, + "smart_volume": { + "name": "Volumen inteligente" + }, + "smelly_sensor": { + "name": "Sensor de olores" + }, + "standby": { + "name": "En espera" + }, + "start": { + "name": "Iniciar" + }, + "storage": { + "name": "Almacenamiento" + }, + "swing": { + "name": "Oscilación" + }, + "swing_horizontal": { + "name": "Oscilación horizontal" + }, + "swing_vertical": { + "name": "Oscilación vertical" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "Calefacción variable" + }, + "ventilation": { + "name": "Ventilación" + }, + "whole_tank_heating": { + "name": "Calentamiento de tanque completo" + }, + "zero_cold_pulse": { + "name": "Cero agua fría (Pulso)" + }, + "zero_cold_water": { + "name": "Cero agua fría" + }, + "zone1_curve": { + "name": "Curva Zona 1" + }, + "zone1_power": { + "name": "Potencia Zona 1" + }, + "zone2_curve": { + "name": "Curva Zona 2" + }, + "zone2_power": { + "name": "Potencia Zona 2" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "Agua caliente sanitaria" + }, + "bathing": { + "name": "Baño" + }, + "heating": { + "name": "Calefacción" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "Dirección IP", + "refresh_interval": "Intervalo de actualización (0 significa no actualizar activamente)", + "sensors": "Sensores extra", + "switches": "Controles extra", + "customize": "Personalizar" + }, + "title": "Configurar" + } + }, + "abort": { + "account_option": "La cuenta no admite esta operación.\nHaga clic en \"AÑADIR DISPOSITIVO\" para agregar un nuevo dispositivo." + } + }, + "services": { + "set_attribute": { + "name": "Establecer atributo", + "description": "Establecer el valor del atributo del dispositivo", + "fields": { + "device_id": { + "name": "Código del electrodoméstico", + "description": "El código (ID de dispositivo) del electrodoméstico" + }, + "attribute": { + "name": "Atributo", + "description": "El nombre del atributo que desea establecer" + }, + "value": { + "name": "Valor", + "description": "El valor del atributo que desea establecer" + } + } + }, + "send_command": { + "name": "Comando personalizado", + "description": "Enviar un comando personalizado al dispositivo", + "fields": { + "device_id": { + "name": "Código del electrodoméstico", + "description": "El código (ID de dispositivo) del electrodoméstico" + }, + "cmd_type": { + "name": "Tipo de comando", + "description": "El tipo de comando, debe ser 3 (consulta) o 2 (ajuste)" + }, + "cmd_body": { + "name": "Cuerpo del comando", + "description": "El cuerpo del comando, (no incluye el encabezado del protocolo MSmart ni la suma de comprobación al final)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/fr.json b/custom_components/midea_ac_lan/translations/fr.json new file mode 100644 index 0000000..22c2625 --- /dev/null +++ b/custom_components/midea_ac_lan/translations/fr.json @@ -0,0 +1,715 @@ +{ + "config": { + "error": { + "preset_account": "Failed to login with preset account, please report this issue", + "login_failed": "Failed to login, account or password was wrong", + "no_devices": "Aucun nouvel apparel trouvé sur le réseau", + "device_exist": "L'appareil est déjà configurée", + "config_incorrect": "La configuration est incorrecte", + "connect_error": "Impossible de se connecter à l'appareil", + "invalid_token": "Token ou Key dans un format incorrect" + }, + "step": { + "user": { + "data": { + "way": "Ajout d'appareil" + }, + "description": "Choisissez comment ajouter des appareils", + "title": "Ajouter un nouvel apparel" + }, + "login": { + "data": { + "account": "Account", + "password": "Password" + }, + "description": "Login and storage your Midea account only for getting the appliance info.\nYou can remove this configuration after all appliance configured.", + "title": "Login" + }, + "discovery": { + "description": "Saisissez l'address IP de l'appareil, entrez \"auto\" pour découvrir automatiquement.\nVous pouvez également utiliser une address IP pour rechercher dans un réseau spécifié, comme \"192.168.1.255\".", + "title": "Recherche", + "data": { + "ip_address": "Address IP" + } + }, + "list": { + "description": "{table}", + "title": "Apparel" + }, + "auto": { + "data": { + "device": "Apparel" + }, + "description": "Choisissez un apparel à ajouter", + "title": "Nouvel apparel trouvé" + }, + "manually": { + "data": { + "name": "Nom (ex: Clim Salon)", + "device_id": "Code apparel", + "type": "Type", + "ip_address": "Address IP", + "port": "Port", + "model": "Modèle", + "subtype": "Soustype", + "protocol": "Protocole", + "token": "Token", + "key": "Key" + }, + "description": "Configuration de l'appareil", + "title": "Nouvel apparel" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "Bar Door" + }, + "bar_door_overtime": { + "name": "Bar Door Overtime" + }, + "bathing_working": { + "name": "Bathing Working Status" + }, + "bottom_compartment_cooling": { + "name": "Bottom Compartment Cooling" + }, + "bottom_compartment_door": { + "name": "Bottom Compartment Door" + }, + "bottom_compartment_preheating": { + "name": "Bottom Compartment Preheating" + }, + "burning_state": { + "name": "Burning State" + }, + "cleaning_reminder": { + "name": "Cleaning Reminder" + }, + "compressor_status": { + "name": "Compressor Status" + }, + "cooking": { + "name": "Cooking" + }, + "filter_change_reminder": { + "name": "Filter Change Reminder" + }, + "filter_cleaning_reminder": { + "name": "Filter Cleaning Reminder" + }, + "finished": { + "name": "Finished" + }, + "flex_zone_door": { + "name": "Flex Door" + }, + "flex_zone_door_overtime": { + "name": "Flex Zone Door" + }, + "freezer_door": { + "name": "Freezer Door" + }, + "freezer_door_overtime": { + "name": "Freezer Door Overtime" + }, + "full_dust": { + "name": "Full of Dust" + }, + "heating": { + "name": "Heating" + }, + "heating_working": { + "name": "Heating Working Status" + }, + "keep_warm": { + "name": "Keep Warm" + }, + "lid_status": { + "name": "Lid Status" + }, + "middle_compartment_cooling": { + "name": "Middle Compartment Cooling" + }, + "middle_compartment_door": { + "name": "Middle Compartment Door" + }, + "middle_compartment_preheating": { + "name": "Middle Compartment Preheating" + }, + "oilcup_full": { + "name": "Oil-cup Full" + }, + "protection": { + "name": "Protection" + }, + "refrigerator_door": { + "name": "Refrigerator Door" + }, + "refrigerator_door_overtime": { + "name": "Refrigerator Door Overtime" + }, + "rinse_aid": { + "name": "Rinse Aid Shortage" + }, + "salt": { + "name": "Salt Shortage" + }, + "seat_status": { + "name": "Seat Status" + }, + "status_dhw": { + "name": "DHW status" + }, + "status_heating": { + "name": "Heating status" + }, + "status_ibh": { + "name": "IBH status" + }, + "status_tbh": { + "name": "TBH status" + }, + "tank_ejected": { + "name": "Tank Ejected" + }, + "tank_full": { + "name": "Tank status" + }, + "top_compartment_cooling": { + "name": "Top Compartment Cooling" + }, + "top_compartment_door": { + "name": "Top Compartment Door" + }, + "top_compartment_preheating": { + "name": "Top Compartment Preheating" + }, + "water_change_reminder": { + "name": "Water Change Reminder" + }, + "water_shortage": { + "name": "Water Shortage" + }, + "with_pressure": { + "name": "With Pressure" + }, + "zone1_room_temp_mode": { + "name": "Zone1 Room-temperature Mode" + }, + "zone1_water_temp_mode": { + "name": "Zone1 Water-temperature Mode" + }, + "zone2_room_temp_mode": { + "name": "Zone2 Room-temperature Mode" + }, + "zone2_water_temp_mode": { + "name": "Zone2 Water-temperature Mode" + } + }, + "climate": { + "climate_zone1": { + "name": "Zone1 Thermostat" + }, + "climate_zone2": { + "name": "Zone2 Thermostat" + } + }, + "fan": { + "fresh_air": { + "name": "Fresh Air" + } + }, + "lock": { + "child_lock": { + "name": "Child Lock" + } + }, + "number": { + "dry_level": { + "name": "Dry Level" + }, + "heating_level": { + "name": "Heating Level" + }, + "seat_temp_level": { + "name": "Seat Temperature Level" + }, + "water_temp_level": { + "name": "Water Temperature Level" + } + }, + "select": { + "detect_mode": { + "name": "Detect Mode" + }, + "direction": { + "name": "Direction" + }, + "fan_speed": { + "name": "Fan Speed" + }, + "mode": { + "name": "Mode" + }, + "oscillation_angle": { + "name": "Oscillation Angle" + }, + "oscillation_mode": { + "name": "Oscillation Mode" + }, + "screen_display": { + "name": "Screen Display" + }, + "silent_level": { + "name": "Silent Level" + }, + "tilting_angle": { + "name": "Tilting Angle" + }, + "water_level_set": { + "name": "Water Level Setting" + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "Bathing Leaving Water Temperature" + }, + "bottom_compartment_remaining": { + "name": "Bottom Compartment Remaining" + }, + "bottom_compartment_status": { + "name": "Bottom Compartment Status" + }, + "bottom_compartment_temperature": { + "name": "Bottom Compartment Temperature" + }, + "bottom_temperature": { + "name": "Bottom Temperature" + }, + "bright": { + "name": "Bright Level" + }, + "compressor_temperature": { + "name": "Compressor Temperature" + }, + "condenser_temperature": { + "name": "Condenser Temperature" + }, + "current_energy_consumption": { + "name": "Current Energy Consumption" + }, + "current_temperature": { + "name": "Current Temperature" + }, + "dehydration_speed": { + "name": "dehydration speed" + }, + "dehydration_time": { + "name": "dehydration time" + }, + "detergent": { + "name": "detergent" + }, + "energy_consumption": { + "name": "Energy Consumption" + }, + "error_code": { + "name": "Error Code" + }, + "fan_level": { + "name": "Fan level" + }, + "filter_life": { + "name": "Filter Life" + }, + "filter1_days": { + "name": "Filter1 Available Days" + }, + "filter1_life": { + "name": "Filter1 Life Level" + }, + "filter2_days": { + "name": "Filter2 Available Days" + }, + "filter2_life": { + "name": "Filter2 Life Level" + }, + "filter3_days": { + "name": "Filter3 Available Days" + }, + "filter3_life": { + "name": "Filter3 Life Level" + }, + "flex_zone_actual_temp": { + "name": "Flex Zone Actual Temperature" + }, + "flex_zone_setting_temp": { + "name": "Flex Zone Setting Temperature" + }, + "freezer_actual_temp": { + "name": "Freezer Actual Temperature" + }, + "freezer_setting_temp": { + "name": "Freezer Setting Temperature" + }, + "hcho": { + "name": "Methanal" + }, + "heating_leaving_temperature": { + "name": "Heating Leaving Water Temperature" + }, + "heating_power": { + "name": "Heating Power" + }, + "heating_time_remaining": { + "name": "Heating Time Remaining" + }, + "in_tds": { + "name": "In TDS" + }, + "indoor_humidity": { + "name": "Indoor Humidity" + }, + "indoor_temperature": { + "name": "Indoor Temperature" + }, + "keep_warm_remaining": { + "name": "Keep Warm Remaining" + }, + "keep_warm_time": { + "name": "Keep Warm Time" + }, + "middle_compartment_remaining": { + "name": "Middle Compartment Remaining" + }, + "middle_compartment_status": { + "name": "Middle Compartment Status" + }, + "middle_compartment_temperature": { + "name": "Middle Compartment Temperature" + }, + "mode": { + "name": "Mode" + }, + "out_tds": { + "name": "Out TDS" + }, + "outdoor_temperature": { + "name": "Outdoor Temperature" + }, + "program": { + "name": "Program" + }, + "progress": { + "name": "Progress" + }, + "realtime_power": { + "name": "Realtime Power" + }, + "refrigerator_actual_temp": { + "name": "Refrigerator Actual Temperature" + }, + "refrigerator_setting_temp": { + "name": "Refrigerator Setting Temperature" + }, + "right_flex_zone_actual_temp": { + "name": "Right Flex Zone Actual Temperature" + }, + "right_flex_zone_setting_temp": { + "name": "Right Flex Zone Setting Temperature" + }, + "rinse_count": { + "name": "rinse count" + }, + "rinse_level": { + "name": "rinse level" + }, + "seat_temperature": { + "name": "Seat Temperature" + }, + "soak_time": { + "name": "soak time" + }, + "softener": { + "name": "softener" + }, + "softwater": { + "name": "Softwater Level" + }, + "status": { + "name": "Status" + }, + "storage_remaining": { + "name": "Storage Remaining" + }, + "tank": { + "name": "Tank" + }, + "tank_actual_temperature": { + "name": "Tank Actual Temperature" + }, + "target_temperature": { + "name": "Target Temperature" + }, + "time_remaining": { + "name": "Time Remaining" + }, + "top_compartment_remaining": { + "name": "Top Compartment Remaining" + }, + "top_compartment_status": { + "name": "Top Compartment Status" + }, + "top_compartment_temperature": { + "name": "Top Compartment Temperature" + }, + "top_temperature": { + "name": "Top Temperature" + }, + "total_energy_consumption": { + "name": "Total Energy Consumption" + }, + "total_produced_energy": { + "name": "Total produced energy" + }, + "tvoc": { + "name": "TVOC" + }, + "wash_level": { + "name": "rinse count" + }, + "wash_strength": { + "name": "wash strength" + }, + "wash_time": { + "name": "wash time" + }, + "water_consumption": { + "name": "Water Consumption" + }, + "water_temperature": { + "name": "Water Temperature" + }, + "water_level": { + "name": "Water Level" + }, + "working_time": { + "name": "Working Time" + } + }, + "switch": { + "anion": { + "name": "Anion" + }, + "aux_heating": { + "name": "Aux Heating" + }, + "boost_mode": { + "name": "Boost Mode" + }, + "breezeless": { + "name": "Breezeless" + }, + "comfort_mode": { + "name": "Comfort Mode" + }, + "dhw_power": { + "name": "DHW Power" + }, + "disinfect": { + "name": "Disinfect" + }, + "dry": { + "name": "Dry" + }, + "eco_mode": { + "name": "ECO Mode" + }, + "fast_dhw": { + "name": "Fast DHW" + }, + "foam_shield": { + "name": "Foam Shield" + }, + "frost_protect": { + "name": "Frost Protect" + }, + "heating_power": { + "name": "Heating Power" + }, + "indirect_wind": { + "name": "Indirect Wind" + }, + "light": { + "name": "Light" + }, + "link_to_ac": { + "name": "Link to AC" + }, + "main_light": { + "name": "Main Light" + }, + "main_power": { + "name": "Main Power" + }, + "natural_wind": { + "name": "Natural Wind" + }, + "night_light": { + "name": "Night Light" + }, + "oscillate": { + "name": "Oscillate" + }, + "power": { + "name": "Power" + }, + "powerful_purify": { + "name": "Powerful Purification" + }, + "prompt_tone": { + "name": "Prompt Tone" + }, + "screen_display": { + "name": "Screen Display" + }, + "screen_display_alternate": { + "name": "Screen Display Alternate" + }, + "sensor_light": { + "name": "Sensor Light" + }, + "silent_mode": { + "name": "Silent Mode" + }, + "sleep_mode": { + "name": "Sleep Mode" + }, + "smart_eye": { + "name": "Smart Eye" + }, + "smart_volume": { + "name": "Smart Volume" + }, + "smelly_sensor": { + "name": "Smelly Sensor" + }, + "standby": { + "name": "Standby" + }, + "start": { + "name": "Start" + }, + "storage": { + "name": "Storage" + }, + "swing": { + "name": "swing" + }, + "swing_horizontal": { + "name": "Swing Horizontal" + }, + "swing_vertical": { + "name": "Swing Vertical" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "Variable Heating" + }, + "ventilation": { + "name": "Ventilation" + }, + "whole_tank_heating": { + "name": "Whole Tank Heating" + }, + "zero_cold_pulse": { + "name": "Zero Cold Water (Pulse)" + }, + "zero_cold_water": { + "name": "Zero Cold Water" + }, + "zone1_curve": { + "name": "Zone1 Curve" + }, + "zone1_power": { + "name": "Zone1 Power" + }, + "zone2_curve": { + "name": "Zone2 Curve" + }, + "zone2_power": { + "name": "Zone2 Power" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "Domestic hot water" + }, + "bathing": { + "name": "Bathing" + }, + "heating": { + "name": "Heating" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "Address IP", + "refresh_interval": "Refresh interval (0 means not refreshing actively)", + "sensors": "Capteurs supplémentaire", + "switches": "Contrôles supplémentaire", + "customize": "Personnaliser" + }, + "title": "Configurer" + } + }, + "abort": { + "account_option": "The account does not supports this operation.\nClick \"ADD DEVICE\" to add a new device." + } + }, + "services": { + "set_attribute": { + "name": "Définir l'attribut", + "description": "Définir la valeur d'attribut de l'appareil", + "fields": { + "device_id": { + "name": "Code apparel", + "description": "Code de l'appareil (ID de l'appareil)" + }, + "attribute": { + "name": "Attribute", + "description": "Nom de l'attribut que vous souhaitez définir" + }, + "value": { + "name": "Valeur", + "description": "Valeur d'attribut que vous souhaitez définir" + } + } + }, + "send_command": { + "name": "Customize command", + "description": "Send a customize command to device", + "fields": { + "device_id": { + "name": "Code apparel", + "description": "Code de l'appareil (ID de l'appareil)" + }, + "cmd_type": { + "name": "command type", + "description": "The type of command,should be 3(query) or 2(set)" + }, + "cmd_body": { + "name": "command body", + "description": "The body of command, (not include MSmart protocol head and checksum at the end)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/hu.json b/custom_components/midea_ac_lan/translations/hu.json new file mode 100644 index 0000000..435ed6e --- /dev/null +++ b/custom_components/midea_ac_lan/translations/hu.json @@ -0,0 +1,715 @@ +{ + "config": { + "error": { + "preset_account": "Failed to login with preset account, please report this issue", + "login_failed": "Bejelentkezés sikertelen, a fiók vagy a jelszó helytelen", + "no_devices": "Nem találhatóak új eszközök a hálózaton", + "device_exist": "Az eszköz már be van állítva", + "config_incorrect": "A beállítások helytelenek", + "connect_error": "Nem sikerült csatlakozni az eszközhöz", + "invalid_token": "A Token vagy a Key formátuma helytelen" + }, + "step": { + "user": { + "data": { + "way": "Eszközök hozzáadása" + }, + "description": "Válassza ki az eszközök hozzáadásának módját", + "title": "Új eszköz hozzáadása" + }, + "login": { + "data": { + "account": "Fiók", + "password": "Jelszó" + }, + "description": "Jelentkezzen be és tárolja el Midea-fiókját, kizárólag a készülékei információinak a megszerzéséhez.\nAz összes készülékének beállítása után eltávolíthatja ezt a konfigurációt.", + "title": "Bejelentkezés" + }, + "discovery": { + "description": "Adja meg az eszköz IP címét vagy írja be az \"auto\" kifejezést az automatikus kereséshez.\nIP-címet is használhat egy megadott hálózaton belüli kereséshez, például \"192.168.1.255\".", + "title": "Keresés", + "data": { + "ip_address": "IP cím" + } + }, + "list": { + "description": "{table}", + "title": "Eszközök" + }, + "auto": { + "data": { + "device": "Eszközök" + }, + "description": "Válassza ki a hozzáadandó eszközt", + "title": "Új eszköz található" + }, + "manually": { + "data": { + "name": "Név (pl. Nappali légkondicionáló)", + "device_id": "Eszköz kód", + "type": "Típus", + "ip_address": "IP cím", + "port": "Port", + "model": "Model", + "subtype": "Altípus", + "protocol": "Protokoll", + "token": "Token", + "key": "Key" + }, + "description": "Eszköz konfigurációja", + "title": "Új eszköz" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "Bar Door" + }, + "bar_door_overtime": { + "name": "Bar Door Overtime" + }, + "bathing_working": { + "name": "Bathing Working Status" + }, + "bottom_compartment_cooling": { + "name": "Bottom Compartment Cooling" + }, + "bottom_compartment_door": { + "name": "Bottom Compartment Door" + }, + "bottom_compartment_preheating": { + "name": "Bottom Compartment Preheating" + }, + "burning_state": { + "name": "Burning State" + }, + "cleaning_reminder": { + "name": "Cleaning Reminder" + }, + "compressor_status": { + "name": "Compressor Status" + }, + "cooking": { + "name": "Cooking" + }, + "filter_change_reminder": { + "name": "Filter Change Reminder" + }, + "filter_cleaning_reminder": { + "name": "Filter Cleaning Reminder" + }, + "finished": { + "name": "Finished" + }, + "flex_zone_door": { + "name": "Flex Door" + }, + "flex_zone_door_overtime": { + "name": "Flex Zone Door" + }, + "freezer_door": { + "name": "Freezer Door" + }, + "freezer_door_overtime": { + "name": "Freezer Door Overtime" + }, + "full_dust": { + "name": "Full of Dust" + }, + "heating": { + "name": "Heating" + }, + "heating_working": { + "name": "Heating Working Status" + }, + "keep_warm": { + "name": "Keep Warm" + }, + "lid_status": { + "name": "Lid Status" + }, + "middle_compartment_cooling": { + "name": "Middle Compartment Cooling" + }, + "middle_compartment_door": { + "name": "Middle Compartment Door" + }, + "middle_compartment_preheating": { + "name": "Middle Compartment Preheating" + }, + "oilcup_full": { + "name": "Oil-cup Full" + }, + "protection": { + "name": "Protection" + }, + "refrigerator_door": { + "name": "Refrigerator Door" + }, + "refrigerator_door_overtime": { + "name": "Refrigerator Door Overtime" + }, + "rinse_aid": { + "name": "Rinse Aid Shortage" + }, + "salt": { + "name": "Salt Shortage" + }, + "seat_status": { + "name": "Seat Status" + }, + "status_dhw": { + "name": "DHW status" + }, + "status_heating": { + "name": "Heating status" + }, + "status_ibh": { + "name": "IBH status" + }, + "status_tbh": { + "name": "TBH status" + }, + "tank_ejected": { + "name": "Tank Ejected" + }, + "tank_full": { + "name": "Tank status" + }, + "top_compartment_cooling": { + "name": "Top Compartment Cooling" + }, + "top_compartment_door": { + "name": "Top Compartment Door" + }, + "top_compartment_preheating": { + "name": "Top Compartment Preheating" + }, + "water_change_reminder": { + "name": "Water Change Reminder" + }, + "water_shortage": { + "name": "Water Shortage" + }, + "with_pressure": { + "name": "With Pressure" + }, + "zone1_room_temp_mode": { + "name": "Zone1 Room-temperature Mode" + }, + "zone1_water_temp_mode": { + "name": "Zone1 Water-temperature Mode" + }, + "zone2_room_temp_mode": { + "name": "Zone2 Room-temperature Mode" + }, + "zone2_water_temp_mode": { + "name": "Zone2 Water-temperature Mode" + } + }, + "climate": { + "climate_zone1": { + "name": "Zone1 Thermostat" + }, + "climate_zone2": { + "name": "Zone2 Thermostat" + } + }, + "fan": { + "fresh_air": { + "name": "Fresh Air" + } + }, + "lock": { + "child_lock": { + "name": "Child Lock" + } + }, + "number": { + "dry_level": { + "name": "Dry Level" + }, + "heating_level": { + "name": "Heating Level" + }, + "seat_temp_level": { + "name": "Seat Temperature Level" + }, + "water_temp_level": { + "name": "Water Temperature Level" + } + }, + "select": { + "detect_mode": { + "name": "Detect Mode" + }, + "direction": { + "name": "Direction" + }, + "fan_speed": { + "name": "Fan Speed" + }, + "mode": { + "name": "Mode" + }, + "oscillation_angle": { + "name": "Oscillation Angle" + }, + "oscillation_mode": { + "name": "Oscillation Mode" + }, + "screen_display": { + "name": "Screen Display" + }, + "silent_level": { + "name": "Silent Level" + }, + "tilting_angle": { + "name": "Tilting Angle" + }, + "water_level_set": { + "name": "Water Level Setting" + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "Bathing Leaving Water Temperature" + }, + "bottom_compartment_remaining": { + "name": "Bottom Compartment Remaining" + }, + "bottom_compartment_status": { + "name": "Bottom Compartment Status" + }, + "bottom_compartment_temperature": { + "name": "Bottom Compartment Temperature" + }, + "bottom_temperature": { + "name": "Bottom Temperature" + }, + "bright": { + "name": "Bright Level" + }, + "compressor_temperature": { + "name": "Compressor Temperature" + }, + "condenser_temperature": { + "name": "Condenser Temperature" + }, + "current_energy_consumption": { + "name": "Current Energy Consumption" + }, + "current_temperature": { + "name": "Current Temperature" + }, + "dehydration_speed": { + "name": "dehydration speed" + }, + "dehydration_time": { + "name": "dehydration time" + }, + "detergent": { + "name": "detergent" + }, + "energy_consumption": { + "name": "Energy Consumption" + }, + "error_code": { + "name": "Error Code" + }, + "fan_level": { + "name": "Fan level" + }, + "filter_life": { + "name": "Filter Life" + }, + "filter1_days": { + "name": "Filter1 Available Days" + }, + "filter1_life": { + "name": "Filter1 Life Level" + }, + "filter2_days": { + "name": "Filter2 Available Days" + }, + "filter2_life": { + "name": "Filter2 Life Level" + }, + "filter3_days": { + "name": "Filter3 Available Days" + }, + "filter3_life": { + "name": "Filter3 Life Level" + }, + "flex_zone_actual_temp": { + "name": "Flex Zone Actual Temperature" + }, + "flex_zone_setting_temp": { + "name": "Flex Zone Setting Temperature" + }, + "freezer_actual_temp": { + "name": "Freezer Actual Temperature" + }, + "freezer_setting_temp": { + "name": "Freezer Setting Temperature" + }, + "hcho": { + "name": "Methanal" + }, + "heating_leaving_temperature": { + "name": "Heating Leaving Water Temperature" + }, + "heating_power": { + "name": "Heating Power" + }, + "heating_time_remaining": { + "name": "Heating Time Remaining" + }, + "in_tds": { + "name": "In TDS" + }, + "indoor_humidity": { + "name": "Indoor Humidity" + }, + "indoor_temperature": { + "name": "Indoor Temperature" + }, + "keep_warm_remaining": { + "name": "Keep Warm Remaining" + }, + "keep_warm_time": { + "name": "Keep Warm Time" + }, + "middle_compartment_remaining": { + "name": "Middle Compartment Remaining" + }, + "middle_compartment_status": { + "name": "Middle Compartment Status" + }, + "middle_compartment_temperature": { + "name": "Middle Compartment Temperature" + }, + "mode": { + "name": "Mode" + }, + "out_tds": { + "name": "Out TDS" + }, + "outdoor_temperature": { + "name": "Outdoor Temperature" + }, + "program": { + "name": "Program" + }, + "progress": { + "name": "Progress" + }, + "realtime_power": { + "name": "Realtime Power" + }, + "refrigerator_actual_temp": { + "name": "Refrigerator Actual Temperature" + }, + "refrigerator_setting_temp": { + "name": "Refrigerator Setting Temperature" + }, + "right_flex_zone_actual_temp": { + "name": "Right Flex Zone Actual Temperature" + }, + "right_flex_zone_setting_temp": { + "name": "Right Flex Zone Setting Temperature" + }, + "rinse_count": { + "name": "rinse count" + }, + "rinse_level": { + "name": "rinse level" + }, + "seat_temperature": { + "name": "Seat Temperature" + }, + "soak_time": { + "name": "soak time" + }, + "softener": { + "name": "softener" + }, + "softwater": { + "name": "Softwater Level" + }, + "status": { + "name": "Status" + }, + "storage_remaining": { + "name": "Storage Remaining" + }, + "tank": { + "name": "Tank" + }, + "tank_actual_temperature": { + "name": "Tank Actual Temperature" + }, + "target_temperature": { + "name": "Target Temperature" + }, + "time_remaining": { + "name": "Time Remaining" + }, + "top_compartment_remaining": { + "name": "Top Compartment Remaining" + }, + "top_compartment_status": { + "name": "Top Compartment Status" + }, + "top_compartment_temperature": { + "name": "Top Compartment Temperature" + }, + "top_temperature": { + "name": "Top Temperature" + }, + "total_energy_consumption": { + "name": "Total Energy Consumption" + }, + "total_produced_energy": { + "name": "Total produced energy" + }, + "tvoc": { + "name": "TVOC" + }, + "wash_level": { + "name": "rinse count" + }, + "wash_strength": { + "name": "wash strength" + }, + "wash_time": { + "name": "wash time" + }, + "water_consumption": { + "name": "Water Consumption" + }, + "water_temperature": { + "name": "Water Temperature" + }, + "water_level": { + "name": "Water Level" + }, + "working_time": { + "name": "Working Time" + } + }, + "switch": { + "anion": { + "name": "Anion" + }, + "aux_heating": { + "name": "Aux Heating" + }, + "boost_mode": { + "name": "Boost Mode" + }, + "breezeless": { + "name": "Breezeless" + }, + "comfort_mode": { + "name": "Comfort Mode" + }, + "dhw_power": { + "name": "DHW Power" + }, + "disinfect": { + "name": "Disinfect" + }, + "dry": { + "name": "Dry" + }, + "eco_mode": { + "name": "ECO Mode" + }, + "fast_dhw": { + "name": "Fast DHW" + }, + "foam_shield": { + "name": "Foam Shield" + }, + "frost_protect": { + "name": "Frost Protect" + }, + "heating_power": { + "name": "Heating Power" + }, + "indirect_wind": { + "name": "Indirect Wind" + }, + "light": { + "name": "Light" + }, + "link_to_ac": { + "name": "Link to AC" + }, + "main_light": { + "name": "Main Light" + }, + "main_power": { + "name": "Main Power" + }, + "natural_wind": { + "name": "Natural Wind" + }, + "night_light": { + "name": "Night Light" + }, + "oscillate": { + "name": "Oscillate" + }, + "power": { + "name": "Power" + }, + "powerful_purify": { + "name": "Powerful Purification" + }, + "prompt_tone": { + "name": "Prompt Tone" + }, + "screen_display": { + "name": "Screen Display" + }, + "screen_display_alternate": { + "name": "Screen Display Alternate" + }, + "sensor_light": { + "name": "Sensor Light" + }, + "silent_mode": { + "name": "Silent Mode" + }, + "sleep_mode": { + "name": "Sleep Mode" + }, + "smart_eye": { + "name": "Smart Eye" + }, + "smart_volume": { + "name": "Smart Volume" + }, + "smelly_sensor": { + "name": "Smelly Sensor" + }, + "standby": { + "name": "Standby" + }, + "start": { + "name": "Start" + }, + "storage": { + "name": "Storage" + }, + "swing": { + "name": "swing" + }, + "swing_horizontal": { + "name": "Swing Horizontal" + }, + "swing_vertical": { + "name": "Swing Vertical" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "Variable Heating" + }, + "ventilation": { + "name": "Ventilation" + }, + "whole_tank_heating": { + "name": "Whole Tank Heating" + }, + "zero_cold_pulse": { + "name": "Zero Cold Water (Pulse)" + }, + "zero_cold_water": { + "name": "Zero Cold Water" + }, + "zone1_curve": { + "name": "Zone1 Curve" + }, + "zone1_power": { + "name": "Zone1 Power" + }, + "zone2_curve": { + "name": "Zone2 Curve" + }, + "zone2_power": { + "name": "Zone2 Power" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "Domestic hot water" + }, + "bathing": { + "name": "Bathing" + }, + "heating": { + "name": "Heating" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP cím", + "refresh_interval": "Frissítési intervallum (0 azt jelenti, hogy nincs aktív frissítés)", + "sensors": "Extra érzékelők", + "switches": "Extra vezérlők", + "customize": "Személyre szabás" + }, + "title": "Konfigurálás" + } + }, + "abort": { + "account_option": "A fiók nem támogatja ezt a műveletet.\nKattintson az \"ESZKÖZ HOZZÁADÁSA\" gombra egy új eszköz hozzáadásához." + } + }, + "services": { + "set_attribute": { + "name": "Attribútum beállítása", + "description": "Állítsa be az eszköz attribútumának értékét", + "fields": { + "device_id": { + "name": "Eszköz kód", + "description": "A készülék kódja (eszköz ID)" + }, + "attribute": { + "name": "Attribútum", + "description": "A beállítandó attribútum neve" + }, + "value": { + "name": "Érték", + "description": "A beállítandó attribútum értéke" + } + } + }, + "send_command": { + "name": "Egyéni parancs", + "description": "Küldjön egy egyéni parancsot az eszköznek", + "fields": { + "device_id": { + "name": "Eszköz kód", + "description": "A készülék kódja (Eszköz ID)" + }, + "cmd_type": { + "name": "Parancs típusa", + "description": "A parancs típusa, lehet 3 (lekérdezés) vagy 2 (beállítás)" + }, + "cmd_body": { + "name": "Parancs törzse", + "description": "A parancs törzse (nem tartalmazza az MSmart protokoll fejlécét és a végén lévő ellenőrző összeget)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/ru.json b/custom_components/midea_ac_lan/translations/ru.json new file mode 100644 index 0000000..555245d --- /dev/null +++ b/custom_components/midea_ac_lan/translations/ru.json @@ -0,0 +1,745 @@ +{ + "config": { + "error": { + "preset_account": "Failed to login with preset account, please report this issue", + "login_failed": "Не удалось авторизоваться, неверное имя аккаунта или пароль", + "no_devices": "Новые доступные устройства в локальной сети не найдены", + "device_exist": "Устройство уже настроено", + "config_incorrect": "Некорректная конфигурация", + "connect_error": "Не удается подключиться к устройству", + "invalid_token": "Некорректный формат Token или Key" + }, + "step": { + "user": { + "data": { + "way": "Добавление устройства" + }, + "description": "Выберите способ добавления устройств", + "title": "Добавить новое устройство" + }, + "login": { + "data": { + "account": "Аккаунт", + "password": "Пароль" + }, + "description": "Login and storage your Midea account only for getting the appliance info.\nYou can remove this configuration after all appliance configured.", + "title": "Login" + }, + "discovery": { + "description": "Введите IP-адрес устройства или \"auto\", чтобы обнаружить устройства автоматически.\nВы также можете использовать IP-адрес для поиска в определенной сети, например, \"192.168.1.255\"", + "title": "Поиск", + "data": { + "ip_address": "IP-адрес" + } + }, + "list": { + "description": "{table}", + "title": "Устройства" + }, + "auto": { + "data": { + "device": "Устройства" + }, + "description": "Выберите устройство для добавления", + "title": "Найдено новое устройство" + }, + "manually": { + "data": { + "name": "Название (например, Кондиционер в гостиной)", + "device_id": "Идентификатор устройства", + "type": "Тип", + "ip_address": "IP-адрес", + "port": "Порт", + "model": "Модель", + "subtype": "Подтип", + "protocol": "Протокол", + "token": "Token", + "key": "Key" + }, + "description": "Настройка устройства", + "title": "Новое устройство" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "Bar Door" + }, + "bar_door_overtime": { + "name": "Bar Door Overtime" + }, + "bathing_working": { + "name": "Bathing Working Status" + }, + "bottom_compartment_cooling": { + "name": "Bottom Compartment Cooling" + }, + "bottom_compartment_door": { + "name": "Bottom Compartment Door" + }, + "bottom_compartment_preheating": { + "name": "Bottom Compartment Preheating" + }, + "burning_state": { + "name": "Burning State" + }, + "cleaning_reminder": { + "name": "Требуется очистка" + }, + "compressor_status": { + "name": "Статус компрессора" + }, + "cooking": { + "name": "Приготовление" + }, + "filter_change_reminder": { + "name": "Требуется замена фильтра" + }, + "filter_cleaning_reminder": { + "name": "Требуется очистка фильтра" + }, + "finished": { + "name": "Завершено" + }, + "flex_zone_door": { + "name": "Flex Door" + }, + "flex_zone_door_overtime": { + "name": "Flex Zone Door" + }, + "freezer_door": { + "name": "Freezer Door" + }, + "freezer_door_overtime": { + "name": "Freezer Door Overtime" + }, + "full_dust": { + "name": "Загрязнение" + }, + "heating": { + "name": "Нагрев" + }, + "heating_working": { + "name": "Heating Working Status" + }, + "keep_warm": { + "name": "Поддержание температуры" + }, + "lid_status": { + "name": "Крышка" + }, + "middle_compartment_cooling": { + "name": "Middle Compartment Cooling" + }, + "middle_compartment_door": { + "name": "Middle Compartment Door" + }, + "middle_compartment_preheating": { + "name": "Middle Compartment Preheating" + }, + "oilcup_full": { + "name": "Oil-cup Full" + }, + "protection": { + "name": "Защита" + }, + "refrigerator_door": { + "name": "Refrigerator Door" + }, + "refrigerator_door_overtime": { + "name": "Refrigerator Door Overtime" + }, + "rinse_aid": { + "name": "Нехватка ополаскивателя" + }, + "salt": { + "name": "Соль" + }, + "seat_status": { + "name": "Сиденье" + }, + "status_dhw": { + "name": "DHW status" + }, + "status_heating": { + "name": "Подогрев" + }, + "status_ibh": { + "name": "IBH status" + }, + "status_tbh": { + "name": "TBH status" + }, + "tank_ejected": { + "name": "Tank Ejected" + }, + "tank_full": { + "name": "Tank status" + }, + "top_compartment_cooling": { + "name": "Top Compartment Cooling" + }, + "top_compartment_door": { + "name": "Top Compartment Door" + }, + "top_compartment_preheating": { + "name": "Top Compartment Preheating" + }, + "water_change_reminder": { + "name": "Water Change Reminder" + }, + "water_shortage": { + "name": "Нехватка воды" + }, + "with_pressure": { + "name": "With Pressure" + }, + "zone1_room_temp_mode": { + "name": "Zone1 Room-temperature Mode" + }, + "zone1_water_temp_mode": { + "name": "Zone1 Water-temperature Mode" + }, + "zone2_room_temp_mode": { + "name": "Zone2 Room-temperature Mode" + }, + "zone2_water_temp_mode": { + "name": "Zone2 Water-temperature Mode" + } + }, + "climate": { + "climate_zone1": { + "name": "Zone1 Thermostat" + }, + "climate_zone2": { + "name": "Zone2 Thermostat" + } + }, + "fan": { + "fresh_air": { + "name": "Fresh Air" + } + }, + "lock": { + "child_lock": { + "name": "Блокировка" + } + }, + "number": { + "dry_level": { + "name": "Dry Level" + }, + "heating_level": { + "name": "Heating Level" + }, + "seat_temp_level": { + "name": "Seat Temperature Level" + }, + "water_temp_level": { + "name": "Water Temperature Level" + } + }, + "select": { + "detect_mode": { + "name": "Detect Mode" + }, + "direction": { + "name": "Направление" + }, + "fan_speed": { + "name": "Скорость" + }, + "mode": { + "name": "Режим" + }, + "oscillation_angle": { + "name": "Oscillation Angle" + }, + "oscillation_mode": { + "name": "Oscillation Mode" + }, + "screen_display": { + "name": "Screen Display" + }, + "silent_level": { + "name": "Silent Level" + }, + "tilting_angle": { + "name": "Угол поворота" + }, + "water_level_set": { + "name": "Water Level Setting" + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "Bathing Leaving Water Temperature" + }, + "bottom_compartment_remaining": { + "name": "Bottom Compartment Remaining" + }, + "bottom_compartment_status": { + "name": "Bottom Compartment Status" + }, + "bottom_compartment_temperature": { + "name": "Bottom Compartment Temperature" + }, + "bottom_temperature": { + "name": "Bottom Temperature" + }, + "bright": { + "name": "Bright Level" + }, + "compressor_temperature": { + "name": "Compressor Temperature" + }, + "condenser_temperature": { + "name": "Condenser Temperature" + }, + "current_energy_consumption": { + "name": "Current Energy Consumption" + }, + "current_temperature": { + "name": "Текущая температура" + }, + "dehydration_speed": { + "name": "Dehydration speed" + }, + "dehydration_time": { + "name": "Dehydration time" + }, + "dehydration_time_value": { + "name": "Dehydration time value" + }, + "wash_time_value": { + "name": "Wash time value" + }, + "stains": { + "name": "Stains" + }, + "dirty_degree": { + "name": "Dirty degree" + }, + "detergent": { + "name": "Моющее средство" + }, + "intensity": { + "name": "Интенсивность" + }, + "dryness_level": { + "name": "Dryness Level" + }, + "dry_temperature": { + "name": "Dry Temperature" + }, + "door_warn": { + "name": "Door Warn" + }, + "ai_switch": { + "name": "AI Switch" + }, + "material": { + "name": "Материал" + }, + "water_box": { + "name": "Water Box" + }, + "energy_consumption": { + "name": "Энергопотребление" + }, + "error_code": { + "name": "Код ошибки" + }, + "fan_level": { + "name": "Fan level" + }, + "filter_life": { + "name": "Состояние фильтра" + }, + "filter1_days": { + "name": "Filter1 Available Days" + }, + "filter1_life": { + "name": "Filter1 Life Level" + }, + "filter2_days": { + "name": "Filter2 Available Days" + }, + "filter2_life": { + "name": "Filter2 Life Level" + }, + "filter3_days": { + "name": "Filter3 Available Days" + }, + "filter3_life": { + "name": "Filter3 Life Level" + }, + "flex_zone_actual_temp": { + "name": "Flex Zone Actual Temperature" + }, + "flex_zone_setting_temp": { + "name": "Flex Zone Setting Temperature" + }, + "freezer_actual_temp": { + "name": "Freezer Actual Temperature" + }, + "freezer_setting_temp": { + "name": "Freezer Setting Temperature" + }, + "hcho": { + "name": "Формальдегид" + }, + "heating_leaving_temperature": { + "name": "Heating Leaving Water Temperature" + }, + "heating_power": { + "name": "Мощность нагрева" + }, + "heating_time_remaining": { + "name": "Heating Time Remaining" + }, + "in_tds": { + "name": "In TDS" + }, + "indoor_humidity": { + "name": "Indoor Humidity" + }, + "indoor_temperature": { + "name": "Indoor Temperature" + }, + "keep_warm_remaining": { + "name": "Keep Warm Remaining" + }, + "keep_warm_time": { + "name": "Keep Warm Time" + }, + "middle_compartment_remaining": { + "name": "Middle Compartment Remaining" + }, + "middle_compartment_status": { + "name": "Middle Compartment Status" + }, + "middle_compartment_temperature": { + "name": "Middle Compartment Temperature" + }, + "mode": { + "name": "Режим" + }, + "out_tds": { + "name": "Out TDS" + }, + "outdoor_temperature": { + "name": "Outdoor Temperature" + }, + "program": { + "name": "Программа" + }, + "progress": { + "name": "Этап" + }, + "realtime_power": { + "name": "Энергопотребление" + }, + "refrigerator_actual_temp": { + "name": "Refrigerator Actual Temperature" + }, + "refrigerator_setting_temp": { + "name": "Refrigerator Setting Temperature" + }, + "right_flex_zone_actual_temp": { + "name": "Right Flex Zone Actual Temperature" + }, + "right_flex_zone_setting_temp": { + "name": "Right Flex Zone Setting Temperature" + }, + "rinse_count": { + "name": "Rinse count" + }, + "rinse_level": { + "name": "Уровень ополаскивателя" + }, + "seat_temperature": { + "name": "Температура сиденья" + }, + "soak_time": { + "name": "Время замачивания" + }, + "softener": { + "name": "Умягчитель" + }, + "softwater": { + "name": "Уровень умягчителя" + }, + "status": { + "name": "Статус" + }, + "storage_remaining": { + "name": "Storage Remaining" + }, + "tank": { + "name": "Tank" + }, + "tank_actual_temperature": { + "name": "Tank Actual Temperature" + }, + "target_temperature": { + "name": "Целевая температура" + }, + "time_remaining": { + "name": "Время цикла" + }, + "top_compartment_remaining": { + "name": "Top Compartment Remaining" + }, + "top_compartment_status": { + "name": "Top Compartment Status" + }, + "top_compartment_temperature": { + "name": "Top Compartment Temperature" + }, + "top_temperature": { + "name": "Top Temperature" + }, + "total_energy_consumption": { + "name": "Общее энергопотребление" + }, + "total_produced_energy": { + "name": "Total produced energy" + }, + "tvoc": { + "name": "TVOC" + }, + "wash_strength": { + "name": "Wash strength" + }, + "wash_time": { + "name": "Время мойки" + }, + "water_consumption": { + "name": "Water Consumption" + }, + "water_temperature": { + "name": "Температура воды" + }, + "water_level": { + "name": "Уровень воды" + }, + "working_time": { + "name": "Время работы" + } + }, + "switch": { + "anion": { + "name": "Anion" + }, + "aux_heating": { + "name": "Aux Heating" + }, + "boost_mode": { + "name": "Boost Mode" + }, + "breezeless": { + "name": "Breezeless" + }, + "comfort_mode": { + "name": "Comfort Mode" + }, + "dhw_power": { + "name": "DHW Power" + }, + "disinfect": { + "name": "Дезинфекция" + }, + "dry": { + "name": "Dry" + }, + "eco_mode": { + "name": "Эко-режим" + }, + "fast_dhw": { + "name": "Fast DHW" + }, + "foam_shield": { + "name": "Foam Shield" + }, + "frost_protect": { + "name": "Защита от замерзания" + }, + "heating_power": { + "name": "Нагреватель" + }, + "indirect_wind": { + "name": "Indirect Wind" + }, + "light": { + "name": "Свет" + }, + "link_to_ac": { + "name": "Link to AC" + }, + "main_light": { + "name": "Main Light" + }, + "main_power": { + "name": "Main Power" + }, + "natural_wind": { + "name": "Natural Wind" + }, + "night_light": { + "name": "Night Light" + }, + "oscillate": { + "name": "Режим поворота" + }, + "power": { + "name": "Питание" + }, + "powerful_purify": { + "name": "Powerful Purification" + }, + "prompt_tone": { + "name": "Prompt Tone" + }, + "screen_display": { + "name": "Screen Display" + }, + "screen_display_alternate": { + "name": "Screen Display Alternate" + }, + "sensor_light": { + "name": "Sensor Light" + }, + "silent_mode": { + "name": "Silent Mode" + }, + "sleep_mode": { + "name": "Sleep Mode" + }, + "smart_eye": { + "name": "Smart Eye" + }, + "smart_volume": { + "name": "Smart Volume" + }, + "smelly_sensor": { + "name": "Smelly Sensor" + }, + "standby": { + "name": "Ожидание" + }, + "start": { + "name": "Старт" + }, + "storage": { + "name": "Storage" + }, + "swing": { + "name": "Swing" + }, + "swing_horizontal": { + "name": "Swing Horizontal" + }, + "swing_vertical": { + "name": "Swing Vertical" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "Variable Heating" + }, + "ventilation": { + "name": "Вентиляция" + }, + "whole_tank_heating": { + "name": "Whole Tank Heating" + }, + "zero_cold_pulse": { + "name": "Zero Cold Water (Pulse)" + }, + "zero_cold_water": { + "name": "Zero Cold Water" + }, + "zone1_curve": { + "name": "Zone1 Curve" + }, + "zone1_power": { + "name": "Zone1 Power" + }, + "zone2_curve": { + "name": "Zone2 Curve" + }, + "zone2_power": { + "name": "Zone2 Power" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "Domestic hot water" + }, + "bathing": { + "name": "Bathing" + }, + "heating": { + "name": "Heating" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP-адрес", + "refresh_interval": "Интервал обновления (0 не выполняет активное обновление)", + "sensors": "Дополнительные сенсоры", + "switches": "Дополнительные элементы управления", + "customize": "Настроить" + }, + "title": "Конфигурация" + } + }, + "abort": { + "account_option": "The account does not supports this operation.\nClick \"ADD DEVICE\" to add a new device." + } + }, + "services": { + "set_attribute": { + "name": "Установить атрибут", + "description": "Установить значение атрибута устройства", + "fields": { + "device_id": { + "name": "Идентификатор устройства", + "description": "Код (Идентификатор) устройства" + }, + "attribute": { + "name": "Атрибут", + "description": "Название атрибута для установки" + }, + "value": { + "name": "Значение", + "description": "Значение атрибута для установки" + } + } + }, + "send_command": { + "name": "Пользовательская команда", + "description": "Отправить пользовательскую команду на устройство", + "fields": { + "device_id": { + "name": "Идентификатор устройства", + "description": "Код (Идентификатор) устройства" + }, + "cmd_type": { + "name": "Тип команды", + "description": "Тип команды, может быть 3 (запрос) или 2 (установка значения)" + }, + "cmd_body": { + "name": "Тело команды", + "description": "Тело команды (без заголовков протокола MSmart и контрольной суммы в конце)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/sk.json b/custom_components/midea_ac_lan/translations/sk.json new file mode 100644 index 0000000..afbaf51 --- /dev/null +++ b/custom_components/midea_ac_lan/translations/sk.json @@ -0,0 +1,715 @@ +{ + "config": { + "error": { + "preset_account": "Failed to login with preset account, please report this issue", + "login_failed": "Failed to login, account or password was wrong", + "no_devices": "V sieti sa nenašli žiadne nové dostupné zariadenia", + "device_exist": "Spotrebič je už nakonfigurovaný", + "config_incorrect": "Konfigurácia je nesprávna", + "connect_error": "Nedá sa pripojiť k spotrebiču", + "invalid_token": "Token alebo Key v nesprávnom formáte" + }, + "step": { + "user": { + "data": { + "way": "Pridávanie spotrebičov" + }, + "description": "Vyberte spôsob pridávania zariadení", + "title": "Pridať nový spotrebič" + }, + "login": { + "data": { + "account": "Account", + "password": "Password" + }, + "description": "Login and storage your Midea account only for getting the appliance info.\nYou can remove this configuration after all appliance configured.", + "title": "Login" + }, + "discovery": { + "description": "Zadajte IP adresu pre zariadenie, zadajte \"auto\" pre automatické vyhľadávanie.\nNa vyhľadávanie v rámci určitej siete môžete použiť aj IP adresu, napríklad \"192.168.1.255\".", + "title": "Hľadať", + "data": { + "ip_address": "IP adresa" + } + }, + "list": { + "description": "{table}", + "title": "Spotrebiče" + }, + "auto": { + "data": { + "device": "Spotrebiče" + }, + "description": "Vyberte zariadenie, ktoré chcete pridať", + "title": "Našiel sa nový spotrebič" + }, + "manually": { + "data": { + "name": "Názov (napr. AC obývačka)", + "device_id": "Kód spotrebiča", + "type": "Typ", + "ip_address": "IP adresa", + "port": "Port", + "model": "Model", + "subtype": "Podtyp", + "protocol": "Protokol", + "token": "Token", + "key": "Key" + }, + "description": "Konfigurácia spotrebiča", + "title": "Nový spotrebič" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "Bar Door" + }, + "bar_door_overtime": { + "name": "Bar Door Overtime" + }, + "bathing_working": { + "name": "Bathing Working Status" + }, + "bottom_compartment_cooling": { + "name": "Bottom Compartment Cooling" + }, + "bottom_compartment_door": { + "name": "Bottom Compartment Door" + }, + "bottom_compartment_preheating": { + "name": "Bottom Compartment Preheating" + }, + "burning_state": { + "name": "Burning State" + }, + "cleaning_reminder": { + "name": "Cleaning Reminder" + }, + "compressor_status": { + "name": "Compressor Status" + }, + "cooking": { + "name": "Cooking" + }, + "filter_change_reminder": { + "name": "Filter Change Reminder" + }, + "filter_cleaning_reminder": { + "name": "Filter Cleaning Reminder" + }, + "finished": { + "name": "Finished" + }, + "flex_zone_door": { + "name": "Flex Door" + }, + "flex_zone_door_overtime": { + "name": "Flex Zone Door" + }, + "freezer_door": { + "name": "Freezer Door" + }, + "freezer_door_overtime": { + "name": "Freezer Door Overtime" + }, + "full_dust": { + "name": "Full of Dust" + }, + "heating": { + "name": "Heating" + }, + "heating_working": { + "name": "Heating Working Status" + }, + "keep_warm": { + "name": "Keep Warm" + }, + "lid_status": { + "name": "Lid Status" + }, + "middle_compartment_cooling": { + "name": "Middle Compartment Cooling" + }, + "middle_compartment_door": { + "name": "Middle Compartment Door" + }, + "middle_compartment_preheating": { + "name": "Middle Compartment Preheating" + }, + "oilcup_full": { + "name": "Oil-cup Full" + }, + "protection": { + "name": "Protection" + }, + "refrigerator_door": { + "name": "Refrigerator Door" + }, + "refrigerator_door_overtime": { + "name": "Refrigerator Door Overtime" + }, + "rinse_aid": { + "name": "Rinse Aid Shortage" + }, + "salt": { + "name": "Salt Shortage" + }, + "seat_status": { + "name": "Seat Status" + }, + "status_dhw": { + "name": "DHW status" + }, + "status_heating": { + "name": "Heating status" + }, + "status_ibh": { + "name": "IBH status" + }, + "status_tbh": { + "name": "TBH status" + }, + "tank_ejected": { + "name": "Tank Ejected" + }, + "tank_full": { + "name": "Tank status" + }, + "top_compartment_cooling": { + "name": "Top Compartment Cooling" + }, + "top_compartment_door": { + "name": "Top Compartment Door" + }, + "top_compartment_preheating": { + "name": "Top Compartment Preheating" + }, + "water_change_reminder": { + "name": "Water Change Reminder" + }, + "water_shortage": { + "name": "Water Shortage" + }, + "with_pressure": { + "name": "With Pressure" + }, + "zone1_room_temp_mode": { + "name": "Zone1 Room-temperature Mode" + }, + "zone1_water_temp_mode": { + "name": "Zone1 Water-temperature Mode" + }, + "zone2_room_temp_mode": { + "name": "Zone2 Room-temperature Mode" + }, + "zone2_water_temp_mode": { + "name": "Zone2 Water-temperature Mode" + } + }, + "climate": { + "climate_zone1": { + "name": "Zone1 Thermostat" + }, + "climate_zone2": { + "name": "Zone2 Thermostat" + } + }, + "fan": { + "fresh_air": { + "name": "Fresh Air" + } + }, + "lock": { + "child_lock": { + "name": "Child Lock" + } + }, + "number": { + "dry_level": { + "name": "Dry Level" + }, + "heating_level": { + "name": "Heating Level" + }, + "seat_temp_level": { + "name": "Seat Temperature Level" + }, + "water_temp_level": { + "name": "Water Temperature Level" + } + }, + "select": { + "detect_mode": { + "name": "Detect Mode" + }, + "direction": { + "name": "Direction" + }, + "fan_speed": { + "name": "Fan Speed" + }, + "mode": { + "name": "Mode" + }, + "oscillation_angle": { + "name": "Oscillation Angle" + }, + "oscillation_mode": { + "name": "Oscillation Mode" + }, + "screen_display": { + "name": "Screen Display" + }, + "silent_level": { + "name": "Silent Level" + }, + "tilting_angle": { + "name": "Tilting Angle" + }, + "water_level_set": { + "name": "Water Level Setting" + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "Bathing Leaving Water Temperature" + }, + "bottom_compartment_remaining": { + "name": "Bottom Compartment Remaining" + }, + "bottom_compartment_status": { + "name": "Bottom Compartment Status" + }, + "bottom_compartment_temperature": { + "name": "Bottom Compartment Temperature" + }, + "bottom_temperature": { + "name": "Bottom Temperature" + }, + "bright": { + "name": "Bright Level" + }, + "compressor_temperature": { + "name": "Compressor Temperature" + }, + "condenser_temperature": { + "name": "Condenser Temperature" + }, + "current_energy_consumption": { + "name": "Current Energy Consumption" + }, + "current_temperature": { + "name": "Current Temperature" + }, + "dehydration_speed": { + "name": "dehydration speed" + }, + "dehydration_time": { + "name": "dehydration time" + }, + "detergent": { + "name": "detergent" + }, + "energy_consumption": { + "name": "Energy Consumption" + }, + "error_code": { + "name": "Error Code" + }, + "fan_level": { + "name": "Fan level" + }, + "filter_life": { + "name": "Filter Life" + }, + "filter1_days": { + "name": "Filter1 Available Days" + }, + "filter1_life": { + "name": "Filter1 Life Level" + }, + "filter2_days": { + "name": "Filter2 Available Days" + }, + "filter2_life": { + "name": "Filter2 Life Level" + }, + "filter3_days": { + "name": "Filter3 Available Days" + }, + "filter3_life": { + "name": "Filter3 Life Level" + }, + "flex_zone_actual_temp": { + "name": "Flex Zone Actual Temperature" + }, + "flex_zone_setting_temp": { + "name": "Flex Zone Setting Temperature" + }, + "freezer_actual_temp": { + "name": "Freezer Actual Temperature" + }, + "freezer_setting_temp": { + "name": "Freezer Setting Temperature" + }, + "hcho": { + "name": "Methanal" + }, + "heating_leaving_temperature": { + "name": "Heating Leaving Water Temperature" + }, + "heating_power": { + "name": "Heating Power" + }, + "heating_time_remaining": { + "name": "Heating Time Remaining" + }, + "in_tds": { + "name": "In TDS" + }, + "indoor_humidity": { + "name": "Indoor Humidity" + }, + "indoor_temperature": { + "name": "Indoor Temperature" + }, + "keep_warm_remaining": { + "name": "Keep Warm Remaining" + }, + "keep_warm_time": { + "name": "Keep Warm Time" + }, + "middle_compartment_remaining": { + "name": "Middle Compartment Remaining" + }, + "middle_compartment_status": { + "name": "Middle Compartment Status" + }, + "middle_compartment_temperature": { + "name": "Middle Compartment Temperature" + }, + "mode": { + "name": "Mode" + }, + "out_tds": { + "name": "Out TDS" + }, + "outdoor_temperature": { + "name": "Outdoor Temperature" + }, + "program": { + "name": "Program" + }, + "progress": { + "name": "Progress" + }, + "realtime_power": { + "name": "Realtime Power" + }, + "refrigerator_actual_temp": { + "name": "Refrigerator Actual Temperature" + }, + "refrigerator_setting_temp": { + "name": "Refrigerator Setting Temperature" + }, + "right_flex_zone_actual_temp": { + "name": "Right Flex Zone Actual Temperature" + }, + "right_flex_zone_setting_temp": { + "name": "Right Flex Zone Setting Temperature" + }, + "rinse_count": { + "name": "rinse count" + }, + "rinse_level": { + "name": "rinse level" + }, + "seat_temperature": { + "name": "Seat Temperature" + }, + "soak_time": { + "name": "soak time" + }, + "softener": { + "name": "softener" + }, + "softwater": { + "name": "Softwater Level" + }, + "status": { + "name": "Status" + }, + "storage_remaining": { + "name": "Storage Remaining" + }, + "tank": { + "name": "Tank" + }, + "tank_actual_temperature": { + "name": "Tank Actual Temperature" + }, + "target_temperature": { + "name": "Target Temperature" + }, + "time_remaining": { + "name": "Time Remaining" + }, + "top_compartment_remaining": { + "name": "Top Compartment Remaining" + }, + "top_compartment_status": { + "name": "Top Compartment Status" + }, + "top_compartment_temperature": { + "name": "Top Compartment Temperature" + }, + "top_temperature": { + "name": "Top Temperature" + }, + "total_energy_consumption": { + "name": "Total Energy Consumption" + }, + "total_produced_energy": { + "name": "Total produced energy" + }, + "tvoc": { + "name": "TVOC" + }, + "wash_level": { + "name": "rinse count" + }, + "wash_strength": { + "name": "wash strength" + }, + "wash_time": { + "name": "wash time" + }, + "water_consumption": { + "name": "Water Consumption" + }, + "water_temperature": { + "name": "Water Temperature" + }, + "water_level": { + "name": "Water Level" + }, + "working_time": { + "name": "Working Time" + } + }, + "switch": { + "anion": { + "name": "Anion" + }, + "aux_heating": { + "name": "Aux Heating" + }, + "boost_mode": { + "name": "Boost Mode" + }, + "breezeless": { + "name": "Breezeless" + }, + "comfort_mode": { + "name": "Comfort Mode" + }, + "dhw_power": { + "name": "DHW Power" + }, + "disinfect": { + "name": "Disinfect" + }, + "dry": { + "name": "Dry" + }, + "eco_mode": { + "name": "ECO Mode" + }, + "fast_dhw": { + "name": "Fast DHW" + }, + "foam_shield": { + "name": "Foam Shield" + }, + "frost_protect": { + "name": "Frost Protect" + }, + "heating_power": { + "name": "Heating Power" + }, + "indirect_wind": { + "name": "Indirect Wind" + }, + "light": { + "name": "Light" + }, + "link_to_ac": { + "name": "Link to AC" + }, + "main_light": { + "name": "Main Light" + }, + "main_power": { + "name": "Main Power" + }, + "natural_wind": { + "name": "Natural Wind" + }, + "night_light": { + "name": "Night Light" + }, + "oscillate": { + "name": "Oscillate" + }, + "power": { + "name": "Power" + }, + "powerful_purify": { + "name": "Powerful Purification" + }, + "prompt_tone": { + "name": "Prompt Tone" + }, + "screen_display": { + "name": "Screen Display" + }, + "screen_display_alternate": { + "name": "Screen Display Alternate" + }, + "sensor_light": { + "name": "Sensor Light" + }, + "silent_mode": { + "name": "Silent Mode" + }, + "sleep_mode": { + "name": "Sleep Mode" + }, + "smart_eye": { + "name": "Smart Eye" + }, + "smart_volume": { + "name": "Smart Volume" + }, + "smelly_sensor": { + "name": "Smelly Sensor" + }, + "standby": { + "name": "Standby" + }, + "start": { + "name": "Start" + }, + "storage": { + "name": "Storage" + }, + "swing": { + "name": "swing" + }, + "swing_horizontal": { + "name": "Swing Horizontal" + }, + "swing_vertical": { + "name": "Swing Vertical" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "Variable Heating" + }, + "ventilation": { + "name": "Ventilation" + }, + "whole_tank_heating": { + "name": "Whole Tank Heating" + }, + "zero_cold_pulse": { + "name": "Zero Cold Water (Pulse)" + }, + "zero_cold_water": { + "name": "Zero Cold Water" + }, + "zone1_curve": { + "name": "Zone1 Curve" + }, + "zone1_power": { + "name": "Zone1 Power" + }, + "zone2_curve": { + "name": "Zone2 Curve" + }, + "zone2_power": { + "name": "Zone2 Power" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "Domestic hot water" + }, + "bathing": { + "name": "Bathing" + }, + "heating": { + "name": "Heating" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP adresa", + "refresh_interval": "Refresh interval (0 means not refreshing actively)", + "sensors": "Extra Snímače", + "switches": "Extra Ovládacie prvky", + "customize": "Voliteľné" + }, + "title": "Konfigurovať" + } + }, + "abort": { + "account_option": "The account does not supports this operation.\nClick \"ADD DEVICE\" to add a new device." + } + }, + "services": { + "set_attribute": { + "name": "Nastaviť atribút", + "description": "Nastavte hodnotu atribútu zariadenia", + "fields": { + "device_id": { + "name": "Kód spotrebiča", + "description": "Kód spotrebiča (ID zariadenia) spotrebiča" + }, + "attribute": { + "name": "Atribút", + "description": "Názov atribútu, ktorý chcete nastaviť" + }, + "value": { + "name": "Hodnota", + "description": "Hodnota atribútu, ktorú chcete nastaviť" + } + } + }, + "send_command": { + "name": "Prispôsobiť príkaz", + "description": "Pošlite do zariadenia príkaz na prispôsobenie", + "fields": { + "device_id": { + "name": "Kód spotrebiča", + "description": "Kód spotrebiča (ID zariadenia) spotrebiča" + }, + "cmd_type": { + "name": "typ príkazu", + "description": "Typ príkazu by mal byť 3 (dopyt) alebo 2 (nastaviť)" + }, + "cmd_body": { + "name": "obsah príkazu", + "description": "Obsah príkazu, (nezahŕňa hlavičku protokolu MSmart a kontrolný súčet na konci)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/translations/zh-Hans.json b/custom_components/midea_ac_lan/translations/zh-Hans.json new file mode 100644 index 0000000..045c369 --- /dev/null +++ b/custom_components/midea_ac_lan/translations/zh-Hans.json @@ -0,0 +1,802 @@ +{ + "config": { + "error": { + "preset_account": "预置账户登录失败,请报告此问题", + "login_failed": "登录失败, 用户名或密码错", + "no_devices": "未在网络上发现可用新设备", + "device_exist": "设备已经存在, 请添加其它设备", + "config_incorrect": "配置信息不正确, 请检查后重新输入", + "connect_error": "无法连接到指定设备", + "invalid_token": "Token或Key格式不正确" + }, + "step": { + "user": { + "data": { + "way": "添加方式" + }, + "description": "选择添加设备的方式", + "title": "添加新设备" + }, + "login": { + "data": { + "account": "账号", + "password": "密码" + }, + "description": "登录并保存你的美的账户,仅用于获取添加设备时设备信息\n添加设备完成后,你可以删除该配置", + "title": "登录" + }, + "discovery": { + "description": "输入设备的IP地址, 输入\"auto\"自动搜索设备\n你也可以使用IP地址在指定网络中搜索, 比如`192.168.1.255`", + "title": "搜索", + "data": { + "ip_address": "IP地址" + } + }, + "list": { + "description": "{table}", + "title": "设备" + }, + "auto": { + "data": { + "device": "设备" + }, + "description": "选择设备并添加", + "title": "发现新设备" + }, + "manually": { + "data": { + "name": "名称(如客厅空调)", + "device_id": "设备编号", + "type": "设备类型", + "ip_address": "IP地址", + "port": "端口", + "model": "型号", + "subtype": "子型号", + "protocol": "协议版本", + "token": "Token", + "key": "Key" + }, + "description": "配置设备参数", + "title": "新设备" + } + } + }, + "entity": { + "binary_sensor": { + "bar_door": { + "name": "吧台门状态" + }, + "bar_door_overtime": { + "name": "吧台门超时" + }, + "bathing_working": { + "name": "淋浴工作状态" + }, + "bottom_compartment_cooling": { + "name": "下层冷却" + }, + "bottom_compartment_door": { + "name": "下层门状态" + }, + "bottom_compartment_preheating": { + "name": "下层预热" + }, + "burning_state": { + "name": "燃烧状态" + }, + "cleaning_reminder": { + "name": "清洁提示" + }, + "compressor_status": { + "name": "压缩机状态" + }, + "cooking": { + "name": "烹饪中" + }, + "filter_change_reminder": { + "name": "滤芯更换提醒" + }, + "filter_cleaning_reminder": { + "name": "滤芯清洁提醒" + }, + "finished": { + "name": "完成" + }, + "flex_zone_door": { + "name": "变温区门状态" + }, + "flex_zone_door_overtime": { + "name": "变温区门超时" + }, + "freezer_door": { + "name": "冷冻室门状态" + }, + "freezer_door_overtime": { + "name": "冷冻室门超时" + }, + "full_dust": { + "name": "尘满" + }, + "heating": { + "name": "加热" + }, + "heating_working": { + "name": "取暖工作状态" + }, + "keep_warm": { + "name": "保温" + }, + "lid_status": { + "name": "盖子状态" + }, + "middle_compartment_cooling": { + "name": "中层冷却" + }, + "middle_compartment_door": { + "name": "中层门状态" + }, + "middle_compartment_preheating": { + "name": "中层预热" + }, + "oilcup_full": { + "name": "油杯满提示" + }, + "protection": { + "name": "安全防护" + }, + "refrigerator_door": { + "name": "冷藏室门状态" + }, + "refrigerator_door_overtime": { + "name": "冷藏室门超时" + }, + "rinse_aid": { + "name": "漂洗剂不足" + }, + "salt": { + "name": "软水盐不足" + }, + "seat_status": { + "name": "入座状态" + }, + "status_dhw": { + "name": "DHW状态" + }, + "status_heating": { + "name": "加热状态" + }, + "status_ibh": { + "name": "IBH状态" + }, + "status_tbh": { + "name": "TBH状态" + }, + "tank_ejected": { + "name": "水箱弹出" + }, + "tank_full": { + "name": "水箱状态" + }, + "top_compartment_cooling": { + "name": "上层冷却" + }, + "top_compartment_door": { + "name": "上层门状态" + }, + "top_compartment_preheating": { + "name": "上层预热" + }, + "water_change_reminder": { + "name": "换水提醒" + }, + "water_shortage": { + "name": "缺水" + }, + "with_pressure": { + "name": "带压中" + }, + "zone1_room_temp_mode": { + "name": "区域1室温模式" + }, + "zone1_water_temp_mode": { + "name": "区域1水温模式" + }, + "zone2_room_temp_mode": { + "name": "区域2室温模式" + }, + "zone2_water_temp_mode": { + "name": "区域2水温模式" + }, + "microcrystal_fresh": { + "name": "微晶一周鲜" + }, + "electronic_smell": { + "name": "净味杀菌" + } + }, + "climate": { + "climate_zone1": { + "name": "区域1恒温器" + }, + "climate_zone2": { + "name": "区域2恒温器" + }, + "climate_key": { + "state_attributes": { + "fan_mode": { + "state": { + "silent": "静音", + "full": "全速" + } + } + } + } + }, + "fan": { + "fresh_air": { + "name": "新风" + } + }, + "lock": { + "child_lock": { + "name": "童锁" + } + }, + "number": { + "dry_level": { + "name": "烘干档位" + }, + "heating_level": { + "name": "加热档位" + }, + "seat_temp_level": { + "name": "座温档位" + }, + "water_temp_level": { + "name": "水温档位" + }, + "fan_speed_percent": { + "name": "风速百分比" + } + }, + "select": { + "detect_mode": { + "name": "检测模式" + }, + "direction": { + "name": "摇摆方向" + }, + "fan_speed": { + "name": "风速设定" + }, + "mode": { + "name": "模式" + }, + "oscillation_angle": { + "name": "水平摆头角度" + }, + "oscillation_mode": { + "name": "水平摆头模式" + }, + "screen_display": { + "name": "屏幕显示" + }, + "silent_level": { + "name": "静音级别" + }, + "tilting_angle": { + "name": "垂直摆头角度" + }, + "water_level_set": { + "name": "水位设定" + }, + "wind_lr_angle": { + "name": "水平出风", + "state": { + "off": "停止", + "left": "左方", + "left-mid": "左中", + "middle": "中部", + "right-mid": "右中", + "right": "右方" + } + }, + "wind_ud_angle": { + "name": "垂直出风", + "state": { + "off": "停止", + "up": "上方", + "up-mid": "中上", + "middle": "中部", + "down-mid": "中下", + "down": "下方" + } + } + }, + "sensor": { + "bathing_leaving_temperature": { + "name": "淋浴出水温度" + }, + "bottom_compartment_remaining": { + "name": "下层剩余时间" + }, + "bottom_compartment_status": { + "name": "下层状态" + }, + "bottom_compartment_temperature": { + "name": "下层温度" + }, + "bottom_temperature": { + "name": "底部温度" + }, + "bright": { + "name": "亮碟剂档位" + }, + "compressor_temperature": { + "name": "压缩机温度" + }, + "condenser_temperature": { + "name": "冷凝器温度" + }, + "current_energy_consumption": { + "name": "当前能耗" + }, + "current_temperature": { + "name": "当前温度" + }, + "dehydration_speed": { + "name": "脱水速度" + }, + "dehydration_time": { + "name": "脱水时间档位" + }, + "dehydration_time_value": { + "name": "脱水时间值" + }, + "wash_time_value": { + "name": "洗涤时间值" + }, + "stains": { + "name": "污渍" + }, + "dirty_degree": { + "name": "脏度" + }, + "intensity": { + "name": "强度" + }, + "dryness_level": { + "name": "干燥档位" + }, + "dry_temperature": { + "name": "干燥温度" + }, + "door_warn": { + "name": "开门警告" + }, + "ai_switch": { + "name": "AI开关" + }, + "material": { + "name": "材质" + }, + "water_box": { + "name": "水箱" + }, + "energy_consumption": { + "name": "能耗" + }, + "error_code": { + "name": "错误码" + }, + "fan_level": { + "name": "当前风扇档位" + }, + "filter_life": { + "name": "滤芯寿命" + }, + "filter1_days": { + "name": "滤芯1可用天数" + }, + "filter1_life": { + "name": "滤芯1剩余寿命" + }, + "filter2_days": { + "name": "滤芯2可用天数" + }, + "filter2_life": { + "name": "滤芯2剩余寿命" + }, + "filter3_days": { + "name": "滤芯3可用天数" + }, + "filter3_life": { + "name": "滤芯3剩余寿命" + }, + "flex_zone_actual_temp": { + "name": "变温区实际温度" + }, + "flex_zone_setting_temp": { + "name": "变温区设置温度" + }, + "freezer_actual_temp": { + "name": "冷冻室实际温度" + }, + "freezer_setting_temp": { + "name": "冷冻室设置温度" + }, + "hcho": { + "name": "甲醛" + }, + "heating_leaving_temperature": { + "name": "取暖出水温度" + }, + "heating_power": { + "name": "加热功率" + }, + "heating_time_remaining": { + "name": "加热剩余时间" + }, + "in_tds": { + "name": "进水TDS" + }, + "indoor_humidity": { + "name": "室内湿度" + }, + "indoor_temperature": { + "name": "室内温度" + }, + "keep_warm_remaining": { + "name": "保温剩余时间" + }, + "keep_warm_time": { + "name": "保温时间" + }, + "middle_compartment_remaining": { + "name": "中层剩余时间" + }, + "middle_compartment_status": { + "name": "中层状态" + }, + "middle_compartment_temperature": { + "name": "中层温度" + }, + "mode": { + "name": "模式" + }, + "out_tds": { + "name": "出水TDS" + }, + "outdoor_temperature": { + "name": "室外温度" + }, + "program": { + "name": "程序" + }, + "progress": { + "name": "当前程序" + }, + "realtime_power": { + "name": "实时功率" + }, + "refrigerator_actual_temp": { + "name": "冷藏室实际温度" + }, + "refrigerator_setting_temp": { + "name": "冷藏室设置温度" + }, + "right_flex_zone_actual_temp": { + "name": "右变温区实际温度" + }, + "right_flex_zone_setting_temp": { + "name": "右变温区设置温度" + }, + "rinse_count": { + "name": "漂洗次数" + }, + "rinse_level": { + "name": "漂洗档位" + }, + "seat_temperature": { + "name": "当前座温" + }, + "soak_time": { + "name": "浸泡时间" + }, + "softener": { + "name": "柔顺剂" + }, + "softwater": { + "name": "软水盐档位" + }, + "status": { + "name": "状态" + }, + "storage_remaining": { + "name": "保管剩余时间" + }, + "tank": { + "name": "水箱水位" + }, + "tank_actual_temperature": { + "name": "水箱实际温度" + }, + "target_temperature": { + "name": "设定温度" + }, + "time_remaining": { + "name": "剩余时间" + }, + "top_compartment_remaining": { + "name": "上层剩余时间" + }, + "top_compartment_status": { + "name": "上层状态" + }, + "top_compartment_temperature": { + "name": "上层温度" + }, + "top_temperature": { + "name": "顶部温度" + }, + "total_energy_consumption": { + "name": "总能耗" + }, + "total_produced_energy": { + "name": "总计产生能量" + }, + "tvoc": { + "name": "TVOC" + }, + "wash_level": { + "name": "洗涤档位" + }, + "wash_strength": { + "name": "洗涤强度" + }, + "wash_time": { + "name": "洗涤时间档位" + }, + "detergent": { + "name": "洗涤剂" + }, + "water_consumption": { + "name": "总耗水量" + }, + "water_level": { + "name": "水位" + }, + "water_temperature": { + "name": "水温" + }, + "working_time": { + "name": "工作时间" + }, + "humidity": { + "name": "湿度" + }, + "variable_mode": { + "name": "变温区模式" + } + }, + "switch": { + "anion": { + "name": "负离子开关" + }, + "aux_heating": { + "name": "电辅热" + }, + "boost_mode": { + "name": "强劲模式" + }, + "breezeless": { + "name": "无风感" + }, + "comfort_mode": { + "name": "舒省模式" + }, + "cold_water_single": { + "name": "单次零冷水" + }, + "cold_water_dot": { + "name": "点动零冷水" + }, + "dhw_power": { + "name": "生活热水电源开关" + }, + "disinfect": { + "name": "消毒" + }, + "dry": { + "name": "干燥" + }, + "eco_mode": { + "name": "ECO模式" + }, + "fast_dhw": { + "name": "快速生活热水" + }, + "foam_shield": { + "name": "泡沫盾" + }, + "frost_protect": { + "name": "防霜冻" + }, + "heating_power": { + "name": "加热", + "_comments": "E2 加热功率, E6 加热开关" + }, + "indirect_wind": { + "name": "防直吹" + }, + "light": { + "name": "灯光" + }, + "link_to_ac": { + "name": "空调联动开关" + }, + "main_light": { + "name": "主灯" + }, + "main_power": { + "name": "主电源" + }, + "natural_wind": { + "name": "自然风" + }, + "night_light": { + "name": "夜灯" + }, + "oscillate": { + "name": "摆头开关" + }, + "power": { + "name": "电源开关" + }, + "powerful_purify": { + "name": "强净开关" + }, + "prompt_tone": { + "name": "提示音" + }, + "screen_display": { + "name": "屏幕显示" + }, + "screen_display_alternate": { + "name": "屏幕显示备用开关" + }, + "sensor_light": { + "name": "感应夜灯" + }, + "silent_mode": { + "name": "静音模式" + }, + "sleep_mode": { + "name": "睡眠模式" + }, + "smart_eye": { + "name": "智慧眼" + }, + "smart_volume": { + "name": "智能变容" + }, + "smelly_sensor": { + "name": "异味感应" + }, + "standby": { + "name": "待机" + }, + "start": { + "name": "启动" + }, + "storage": { + "name": "保管开关" + }, + "swing": { + "name": "摆风开关" + }, + "swing_horizontal": { + "name": "水平摆风" + }, + "swing_vertical": { + "name": "垂直摆风" + }, + "tbh": { + "name": "TBH" + }, + "variable_heating": { + "name": "变频加热" + }, + "ventilation": { + "name": "换气" + }, + "whole_tank_heating": { + "name": "全胆速热" + }, + "zero_cold_pulse": { + "name": "零冷水(点动)" + }, + "zero_cold_water": { + "name": "零冷水" + }, + "zone1_curve": { + "name": "区域1曲线" + }, + "zone1_power": { + "name": "区域1恒温器开关" + }, + "zone2_curve": { + "name": "区域2曲线" + }, + "zone2_power": { + "name": "区域2恒温器开关" + } + }, + "water_heater": { + "domestic_hot_water": { + "name": "生活热水" + }, + "bathing": { + "name": "淋浴" + }, + "heating": { + "name": "加热" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ip_address": "IP地址", + "refresh_interval": "刷新间隔(设0为不进行主动刷新)", + "sensors": "扩展传感器", + "switches": "扩展控制", + "customize": "自定义" + }, + "title": "配置" + } + }, + "abort": { + "account_option": "账户配置不支持此操作.\n点击\"添加设备\"完成添加新设备的操作" + } + }, + "services": { + "set_attribute": { + "name": "设置属性", + "description": "设置设备的属性值", + "fields": { + "device_id": { + "name": "设备编码", + "description": "设备编码(Device ID)" + }, + "attribute": { + "name": "属性", + "description": "要设置的属性名称" + }, + "value": { + "name": "值", + "description": "要设置的属性值" + } + } + }, + "send_command": { + "name": "自定义命令", + "description": "向设备发送一个自定义命令", + "fields": { + "device_id": { + "name": "设备编码", + "description": "设备编码(Device ID)" + }, + "cmd_type": { + "name": "命令类型", + "description": "命令类型,可以为2(查询)或3(设置)" + }, + "cmd_body": { + "name": "命令体", + "description": "命令的消息体(不包括前部的MSmart协议头及后部的校验码)" + } + } + } + } +} diff --git a/custom_components/midea_ac_lan/water_heater.py b/custom_components/midea_ac_lan/water_heater.py new file mode 100644 index 0000000..43c2ae2 --- /dev/null +++ b/custom_components/midea_ac_lan/water_heater.py @@ -0,0 +1,455 @@ +"""Midea Water Heater entries.""" + +import functools as ft +import logging +from typing import Any, ClassVar, TypeAlias, cast + +from homeassistant.components.water_heater import ( + WaterHeaterEntity, + WaterHeaterEntityFeature, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_DEVICE_ID, + CONF_SWITCHES, + PRECISION_HALVES, + PRECISION_WHOLE, + STATE_OFF, + STATE_ON, + Platform, + UnitOfTemperature, +) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from midealocal.device import DeviceType +from midealocal.devices.c3 import DeviceAttributes as C3Attributes +from midealocal.devices.c3 import MideaC3Device +from midealocal.devices.cd import DeviceAttributes as CDAttributes +from midealocal.devices.cd import MideaCDDevice +from midealocal.devices.e2 import DeviceAttributes as E2Attributes +from midealocal.devices.e2 import MideaE2Device +from midealocal.devices.e3 import MideaE3Device +from midealocal.devices.e6 import DeviceAttributes as E6Attributes +from midealocal.devices.e6 import MideaE6Device + +from .const import DEVICES, DOMAIN +from .midea_devices import MIDEA_DEVICES +from .midea_entity import MideaEntity + +_LOGGER = logging.getLogger(__name__) + +E2_TEMPERATURE_MAX = 75 +E2_TEMPERATURE_MIN = 30 +E3_TEMPERATURE_MAX = 65 +E3_TEMPERATURE_MIN = 35 + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up water heater entries.""" + device_id = config_entry.data.get(CONF_DEVICE_ID) + device = hass.data[DOMAIN][DEVICES].get(device_id) + extra_switches = config_entry.options.get(CONF_SWITCHES, []) + devs: list[ + MideaE2WaterHeater + | MideaE3WaterHeater + | MideaE6WaterHeater + | MideaC3WaterHeater + | MideaCDWaterHeater + ] = [] + for entity_key, config in cast( + "dict", + MIDEA_DEVICES[device.device_type]["entities"], + ).items(): + if config["type"] == Platform.WATER_HEATER and ( + config.get("default") or entity_key in extra_switches + ): + if device.device_type == DeviceType.E2: + devs.append(MideaE2WaterHeater(device, entity_key)) + elif device.device_type == DeviceType.E3: + devs.append(MideaE3WaterHeater(device, entity_key)) + elif device.device_type == DeviceType.E6: + devs.append(MideaE6WaterHeater(device, entity_key, config["use"])) + elif device.device_type == DeviceType.C3: + devs.append(MideaC3WaterHeater(device, entity_key)) + elif device.device_type == DeviceType.CD: + devs.append(MideaCDWaterHeater(device, entity_key)) + async_add_entities(devs) + + +MideaWaterHeaterDevice: TypeAlias = ( + MideaE2Device | MideaE3Device | MideaC3Device | MideaE6Device | MideaCDDevice +) + + +class MideaWaterHeater(MideaEntity, WaterHeaterEntity): + """Midea Water Heater Entries Base Class.""" + + _device: MideaWaterHeaterDevice + + def __init__(self, device: MideaWaterHeaterDevice, entity_key: str) -> None: + """Midea Water Heater entity init.""" + super().__init__(device, entity_key) + self._operations: list[str] = [] + + @property + def supported_features(self) -> WaterHeaterEntityFeature: + """Midea Water Heater supported features.""" + return WaterHeaterEntityFeature.TARGET_TEMPERATURE + + @property + def extra_state_attributes(self) -> dict[str, Any]: + """Midea Water Heater extra state attributes.""" + attrs: dict[str, Any] = self._device.attributes + if hasattr(self._device, "temperature_step"): + attrs["target_temperature_step"] = self._device.temperature_step + return attrs + + @property + def min_temp(self) -> float: + """Midea Water Heater min temperature.""" + raise NotImplementedError + + @property + def max_temp(self) -> float: + """Midea Water Heater max temperature.""" + raise NotImplementedError + + @property + def precision(self) -> float: + """Midea Water Heater precision.""" + return float(PRECISION_WHOLE) + + @property + def temperature_unit(self) -> UnitOfTemperature: + """Midea Water Heater temperature unix.""" + return UnitOfTemperature.CELSIUS + + @property + def current_operation(self) -> str | None: + """Midea Water Heater current operation.""" + return cast( + "str", + ( + self._device.get_attribute("mode") + if self._device.get_attribute("power") + else STATE_OFF + ), + ) + + @property + def current_temperature(self) -> float: + """Midea Water Heater current temperature.""" + return cast("float", self._device.get_attribute("current_temperature")) + + @property + def target_temperature(self) -> float: + """Midea Water Heater target temperature.""" + return cast("float", self._device.get_attribute("target_temperature")) + + def set_temperature(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea Water Heater set temperature.""" + if ATTR_TEMPERATURE not in kwargs: + return + # input target_temperature should be float + temperature = float(kwargs[ATTR_TEMPERATURE]) + self._device.set_attribute("target_temperature", temperature) + + def set_operation_mode(self, operation_mode: str) -> None: + """Midea Water Heater set operation mode.""" + self._device.set_attribute(attr="mode", value=operation_mode) + + @property + def operation_list(self) -> list[str] | None: + """Midea Water Heater operation list.""" + if not hasattr(self._device, "preset_modes"): + return None + return cast("list", self._device.preset_modes) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Water Heater turn on.""" + self._device.set_attribute(attr="power", value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea Water Heater turn off.""" + self._device.set_attribute(attr="power", value=False) + + async def async_turn_on(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea Water Heater async turn on.""" + await self.hass.async_add_executor_job(ft.partial(self.turn_on, **kwargs)) + + async def async_turn_off(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea Water Heater async off.""" + await self.hass.async_add_executor_job(ft.partial(self.turn_off, **kwargs)) + + def update_state(self, status: Any) -> None: # noqa: ANN401, ARG002 + """Midea Water Heater update state.""" + if not self.hass: + _LOGGER.warning( + "Water update_state skipped for %s [%s]: HASS is None", + self.name, + type(self), + ) + return + self.schedule_update_ha_state() + + +class MideaE2WaterHeater(MideaWaterHeater): + """Midea E2 Water Heater Entries.""" + + _device: MideaE2Device + + def __init__(self, device: MideaE2Device, entity_key: str) -> None: + """Midea E2 Water Heater entity init.""" + super().__init__(device, entity_key) + + @property + def current_operation(self) -> str: + """Midea E2 Water Heater current operation.""" + return str( + STATE_ON if self._device.get_attribute(E2Attributes.power) else STATE_OFF, + ) + + @property + def min_temp(self) -> float: + """Midea E2 Water Heater min temperature.""" + return E2_TEMPERATURE_MIN + + @property + def max_temp(self) -> float: + """Midea E2 Water Heater max temperature.""" + return E2_TEMPERATURE_MAX + + @property + def supported_features(self) -> WaterHeaterEntityFeature: + """Midea E2 Water Heater supported features.""" + return ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.ON_OFF + ) + + +class MideaE3WaterHeater(MideaWaterHeater): + """Midea E3 Water Heater Entries.""" + + _device: MideaE3Device + + def __init__(self, device: MideaE3Device, entity_key: str) -> None: + """Midea E3 Water Heater entity init.""" + super().__init__(device, entity_key) + + @property + def min_temp(self) -> float: + """Midea E3 Water Heater min temperature.""" + return E3_TEMPERATURE_MIN + + @property + def max_temp(self) -> float: + """Midea E3 Water Heater max temperature.""" + return E3_TEMPERATURE_MAX + + @property + def precision(self) -> float: + """Midea E3 Water Heater precision.""" + return float( + PRECISION_HALVES if self._device.precision_halves else PRECISION_WHOLE, + ) + + @property + def current_operation(self) -> str: + """Midea E3 Water Heater current operation.""" + return str( + STATE_ON if self._device.get_attribute("power") else STATE_OFF, + ) + + +class MideaC3WaterHeater(MideaWaterHeater): + """Midea C3 Water Heater Entries.""" + + _device: MideaC3Device + + def __init__(self, device: MideaC3Device, entity_key: str) -> None: + """Midea C3 Water Heater entity init.""" + super().__init__(device, entity_key) + + @property + def current_operation(self) -> str: + """Midea C3 Water Heater current operation.""" + return str( + ( + STATE_ON + if self._device.get_attribute(C3Attributes.dhw_power) + else STATE_OFF + ), + ) + + @property + def current_temperature(self) -> float: + """Midea C3 Water Heater current temperature.""" + return cast( + "float", + self._device.get_attribute(C3Attributes.tank_actual_temperature), + ) + + @property + def target_temperature(self) -> float: + """Midea C3 Water Heater target temperature.""" + return cast("float", self._device.get_attribute(C3Attributes.dhw_target_temp)) + + def set_temperature(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea C3 Water Heater set temperature.""" + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(kwargs[ATTR_TEMPERATURE]) + self._device.set_attribute(C3Attributes.dhw_target_temp, temperature) + + @property + def min_temp(self) -> float: + """Midea C3 Water Heater min temperature.""" + return cast("float", self._device.get_attribute(C3Attributes.dhw_temp_min)) + + @property + def max_temp(self) -> float: + """Midea C3 Water Heater max temperature.""" + return cast("float", self._device.get_attribute(C3Attributes.dhw_temp_max)) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea C3 Water Heater turn on.""" + self._device.set_attribute(attr=C3Attributes.dhw_power, value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea C3 Water Heater turn off.""" + self._device.set_attribute(attr=C3Attributes.dhw_power, value=False) + + +class MideaE6WaterHeater(MideaWaterHeater): + """Midea E6 Water Heater Entries.""" + + _device: MideaE6Device + + _powers: ClassVar[list[E6Attributes]] = [ + E6Attributes.heating_power, + E6Attributes.main_power, + ] + _current_temperatures: ClassVar[list[E6Attributes]] = [ + E6Attributes.heating_leaving_temperature, + E6Attributes.bathing_leaving_temperature, + ] + _target_temperatures: ClassVar[list[E6Attributes]] = [ + E6Attributes.heating_temperature, + E6Attributes.bathing_temperature, + ] + + def __init__(self, device: MideaE6Device, entity_key: str, use: int) -> None: + """Midea E6 Water Heater entity init.""" + super().__init__(device, entity_key) + self._use = use + self._power_attr = MideaE6WaterHeater._powers[self._use] + self._current_temperature_attr = MideaE6WaterHeater._current_temperatures[ + self._use + ] + self._target_temperature_attr = MideaE6WaterHeater._target_temperatures[ + self._use + ] + + @property + def current_operation(self) -> str: + """Midea E6 Water Heater current operation.""" + if self._use == 0: # for heating + return str( + ( + STATE_ON + if self._device.get_attribute(E6Attributes.main_power) + and self._device.get_attribute(E6Attributes.heating_power) + else STATE_OFF + ), + ) + # for bathing + return str( + ( + STATE_ON + if self._device.get_attribute(E6Attributes.main_power) + else STATE_OFF + ), + ) + + @property + def current_temperature(self) -> float: + """Midea E6 Water Heater current temperature.""" + return cast("float", self._device.get_attribute(self._current_temperature_attr)) + + @property + def target_temperature(self) -> float: + """Midea E6 Water Heater target temperature.""" + return cast("float", self._device.get_attribute(self._target_temperature_attr)) + + def set_temperature(self, **kwargs: Any) -> None: # noqa: ANN401 + """Midea E6 Water Heater set temperature.""" + if ATTR_TEMPERATURE not in kwargs: + return + temperature = float(kwargs[ATTR_TEMPERATURE]) + self._device.set_attribute(self._target_temperature_attr, temperature) + + @property + def min_temp(self) -> float: + """Midea E6 Water Heater min temperature.""" + min_temperature = cast( + "list[str]", + self._device.get_attribute(E6Attributes.min_temperature), + ) + return cast( + "float", + min_temperature[self._use], + ) + + @property + def max_temp(self) -> float: + """Midea E6 Water Heater max temperature.""" + max_temperature = cast( + "list[str]", + self._device.get_attribute(E6Attributes.max_temperature), + ) + return cast( + "float", + max_temperature[self._use], + ) + + def turn_on(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea E6 Water Heater turn on.""" + self._device.set_attribute(attr=self._power_attr, value=True) + + def turn_off(self, **kwargs: Any) -> None: # noqa: ANN401, ARG002 + """Midea E6 Water Heater turn off.""" + self._device.set_attribute(attr=self._power_attr, value=False) + + +class MideaCDWaterHeater(MideaWaterHeater): + """Midea CD Water Heater Entries.""" + + _device: MideaCDDevice + + def __init__(self, device: MideaCDDevice, entity_key: str) -> None: + """Midea CD Water Heater entity init.""" + super().__init__(device, entity_key) + + @property + def supported_features(self) -> WaterHeaterEntityFeature: + """Midea CD Water Heater supported features.""" + return ( + WaterHeaterEntityFeature.TARGET_TEMPERATURE + | WaterHeaterEntityFeature.OPERATION_MODE + ) + + @property + def min_temp(self) -> float: + """Midea CD Water Heater min temperature.""" + return cast("float", self._device.get_attribute(CDAttributes.min_temperature)) + + @property + def max_temp(self) -> float: + """Midea CD Water Heater max temperature.""" + return cast("float", self._device.get_attribute(CDAttributes.max_temperature))