From 89ac3bd24f88ba318e8aa569cd22cf8c69d287cb Mon Sep 17 00:00:00 2001 From: Franco Pellicciotti Date: Fri, 12 Jun 2026 11:51:37 -0400 Subject: [PATCH] Added Midea Dishwasher Integration --- .../brands/integrations/midea_ac_lan/logo.png | Bin 0 -> 28356 bytes .ha_run.lock | 2 +- automations.yaml | 521 +-- custom_components/midea_ac_lan/__init__.py | 346 ++ .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 15758 bytes .../__pycache__/binary_sensor.cpython-314.pyc | Bin 0 -> 3553 bytes .../__pycache__/climate.cpython-314.pyc | Bin 0 -> 45470 bytes .../__pycache__/config_flow.cpython-314.pyc | Bin 0 -> 45427 bytes .../__pycache__/const.cpython-314.pyc | Bin 0 -> 1461 bytes .../__pycache__/diagnostics.cpython-314.pyc | Bin 0 -> 1221 bytes .../__pycache__/fan.cpython-314.pyc | Bin 0 -> 23228 bytes .../__pycache__/humidifier.cpython-314.pyc | Bin 0 -> 10982 bytes .../__pycache__/light.cpython-314.pyc | Bin 0 -> 13319 bytes .../__pycache__/lock.cpython-314.pyc | Bin 0 -> 4184 bytes .../__pycache__/midea_devices.cpython-314.pyc | Bin 0 -> 75864 bytes .../__pycache__/midea_entity.cpython-314.pyc | Bin 0 -> 6815 bytes .../__pycache__/number.cpython-314.pyc | Bin 0 -> 6728 bytes .../__pycache__/select.cpython-314.pyc | Bin 0 -> 4548 bytes .../__pycache__/sensor.cpython-314.pyc | Bin 0 -> 5042 bytes .../__pycache__/switch.cpython-314.pyc | Bin 0 -> 3858 bytes .../__pycache__/water_heater.cpython-314.pyc | Bin 0 -> 32610 bytes .../midea_ac_lan/binary_sensor.py | 50 + custom_components/midea_ac_lan/climate.py | 707 ++++ custom_components/midea_ac_lan/config_flow.py | 998 ++++++ custom_components/midea_ac_lan/const.py | 38 + custom_components/midea_ac_lan/diagnostics.py | 32 + custom_components/midea_ac_lan/fan.py | 320 ++ custom_components/midea_ac_lan/humidifier.py | 140 + custom_components/midea_ac_lan/light.py | 184 + custom_components/midea_ac_lan/lock.py | 54 + custom_components/midea_ac_lan/manifest.json | 14 + .../midea_ac_lan/midea_devices.py | 3060 +++++++++++++++++ .../midea_ac_lan/midea_entity.py | 144 + custom_components/midea_ac_lan/number.py | 102 + custom_components/midea_ac_lan/select.py | 57 + custom_components/midea_ac_lan/sensor.py | 67 + custom_components/midea_ac_lan/services.yaml | 18 + custom_components/midea_ac_lan/switch.py | 50 + .../midea_ac_lan/translations/de.json | 715 ++++ .../midea_ac_lan/translations/en.json | 834 +++++ .../midea_ac_lan/translations/es.json | 795 +++++ .../midea_ac_lan/translations/fr.json | 715 ++++ .../midea_ac_lan/translations/hu.json | 715 ++++ .../midea_ac_lan/translations/ru.json | 745 ++++ .../midea_ac_lan/translations/sk.json | 715 ++++ .../midea_ac_lan/translations/zh-Hans.json | 802 +++++ .../midea_ac_lan/water_heater.py | 455 +++ 47 files changed, 13150 insertions(+), 245 deletions(-) create mode 100644 .cache/brands/integrations/midea_ac_lan/logo.png create mode 100644 custom_components/midea_ac_lan/__init__.py create mode 100644 custom_components/midea_ac_lan/__pycache__/__init__.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/binary_sensor.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/climate.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/config_flow.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/const.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/diagnostics.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/fan.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/humidifier.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/light.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/lock.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/midea_devices.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/midea_entity.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/number.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/select.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/sensor.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/switch.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/__pycache__/water_heater.cpython-314.pyc create mode 100644 custom_components/midea_ac_lan/binary_sensor.py create mode 100644 custom_components/midea_ac_lan/climate.py create mode 100644 custom_components/midea_ac_lan/config_flow.py create mode 100644 custom_components/midea_ac_lan/const.py create mode 100644 custom_components/midea_ac_lan/diagnostics.py create mode 100644 custom_components/midea_ac_lan/fan.py create mode 100644 custom_components/midea_ac_lan/humidifier.py create mode 100644 custom_components/midea_ac_lan/light.py create mode 100644 custom_components/midea_ac_lan/lock.py create mode 100644 custom_components/midea_ac_lan/manifest.json create mode 100644 custom_components/midea_ac_lan/midea_devices.py create mode 100644 custom_components/midea_ac_lan/midea_entity.py create mode 100644 custom_components/midea_ac_lan/number.py create mode 100644 custom_components/midea_ac_lan/select.py create mode 100644 custom_components/midea_ac_lan/sensor.py create mode 100644 custom_components/midea_ac_lan/services.yaml create mode 100644 custom_components/midea_ac_lan/switch.py create mode 100644 custom_components/midea_ac_lan/translations/de.json create mode 100644 custom_components/midea_ac_lan/translations/en.json create mode 100644 custom_components/midea_ac_lan/translations/es.json create mode 100644 custom_components/midea_ac_lan/translations/fr.json create mode 100644 custom_components/midea_ac_lan/translations/hu.json create mode 100644 custom_components/midea_ac_lan/translations/ru.json create mode 100644 custom_components/midea_ac_lan/translations/sk.json create mode 100644 custom_components/midea_ac_lan/translations/zh-Hans.json create mode 100644 custom_components/midea_ac_lan/water_heater.py 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 0000000000000000000000000000000000000000..33adb36ff46b19d386e0f5315623687ab8c528f7 GIT binary patch literal 28356 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 W45|;wfFf$ny-<_|%altQ2mTL^e&0s` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..14716b67aa258effba00d0292e66cb035c5fc80a GIT binary patch literal 15758 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�Zvq$f?cPx*uvD{-F*4P(-)phNjTk-&9`ZU^iyy1rWsPeq88gdE94%3^&rv84BPA4tB*pJ975V%S8FH=JYF>K zmaHL6gL9_V7y_kkc|cb^P&8<-UvF1QJve7Bf|DmON|l^Jp%o?o)0 zB=s%tG3)}rn<$FTB!tkXqAb8aGb+v(vFIKq;LM2vdRc&R8grvJfiI6y$4FsQ*4RQ# z7|^LRVpLUUikm2K;Id&DTTz`tFob1sxBON?mjU_=`R#BeZwDVRwxp96P`v{G*y%aE zxJ}0i6-E%m0G=r@5*QJK>j$1eaB$zB3U=4W`=6j_V$yO0AF1A(e28RSJu25^ljb#7PxoF$tM`31?V z;0}47*~~l`8rjl1$)-=tUx$fuc$F390>LT#HK-sFc_n`iCBz{QY?QCz%G__yUGFc{ zbuUd78@8;P>Ne^d#rnZJ^&nq@9j!)Wn)6KaznP5ngOAG4l&TPgpZhdU^6QMWfstmpndSjR7O zD1N+gsOQi@`h&ezhyw>CYb=(?%*0}nbuM$}403`NP*_1qY^t*LcI#RA4;}m&O#TmD z&)MW0_#5SVl=CFEe?mqitI{NS9&6*XFt^lM8Ip1=>e=x(aNmCq2iU*>Jf9WjGxItA zRjg=w6hg_Jf@?W*HN_V6RPQ-ss2@}{}ZZjmFoLj zs{UtG|3ibFHaw)%=(Bo?-uW|X&nML3JazbIRL?_`4cj65j6?t^aM$J$ZT>qp|J7i@ m*12T;%mV-Q`)mOJc<@=!i1y9T5(ZEreAWl-=)-=M9Q_yGo+`5d literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a1121627970574064e31247880810355913d4e71 GIT binary patch literal 3553 zcmbUjOKcm*b(Xta{wYeP#KhHyk|A2ORWgVz+bvvwqK`?%mgyD1#3=3Unq0|jD3YBW zIgt<)YEz);p{NV=kOBdkLk@P(qCNUppr_tU2r`v{jTUH&A~&gO;`G+{W_Lx(7JBFa zGxO%n%$s@tcQP6Y5@=Hcli&ZHO32@E;Wk){w)YuK8$=_F+$NfomvSEF89~^SmvahJ za$e>YYdNpxe9R|cCGXD#SRfZw|Ap z>jqs*T4vp9(Bu;IYt@QrB(ECvOj_bf*|1pJ!xQ$oMR9D#yjLxo(>24gcwhI%Y@Jov z8e9uaH|k5(Wmwa-w9KQ^*B9pWnb~4~dREWR@UU1f&Mquo*A}_IP%{|3yaE>^xyFil z*|Mq@GwKWwM=neL7tOd>G&=VPlJga8RozD4efYg{?y$Z7#TqUN(FbfYc6!(a_}ATopzMbl#ArB+a=eh$mG!u(Gsdov~KKSFBRqe z7ZmuP@Ll(=Js)c%Y)l!eU1I7_^;i6LLbK%b1{Ws0iy>e+5`a7px z=(GdG$lMh|-i6ocdquw{ci%4!xTg!RxKL+4_ckHR!ie8IM#x*M--0b?eUgyUH{6qN zU2*@>3>4MEkc;ok#egfl5%Ol9kN_DbMb9WHsvW!r#y=%jrL=eb=%UGzt4$}0SR#y6 zM3BgErBW?3?%%h6g{?Ksg>(;W7|h`EvdJjwnxg1w2nOzJG+DJ#x2O+Ms0JEAe8{G$ zBL(iQGIPbEXb?Q|Mty^;>nUL=P~qv#Yo;LxmAxN3tu zV!p?y;rNrqqX+!U;z7a6n*NTt##P%xxoSI~g=UsYrIL1|s4d=W2LTJ*zG=q=Ith5; zl79x^3-F+wFzFp?$3|PR(ROUS6&wGwz8Ra^$Ztn_e{u6?H-B|+D>D4?yIYat?a0Yi z1|ZTq87{r&fs+5;1< zfr%}D`oW1U|J0sW9#NmG#25aN`N8tPeDX-N3m?DT9y-+;I`t%U z>QV5?xy7gbsZZ{0_J8}sx$W@Lc6hWE9{nWsB%I!jz}4L-@h0y7@QIT8kA+hG@w9J- zNPmjTI1ZeEu?*P#=Ej6Q46p`pev2>(@}=kYaV8It24qd9-0~L5DM|A{1_S%YJm&fa zQ=L6U?0FCD`81XJi-ZLmNzH6h6V$_!hLhTCeLBXPIglCfd)W6fFr1eT z&kc*xcOjuqqH#JcE5U;@#jrr*FhDw@NG9!}=;%D6>t)QA=nA@Sp+%2v2HmsKM*nm zT_?QRNA0nI!Tp7%d0dxwg9h@t9@j(~OWHn@6zpXL(>(0Bl)!;l@FDJl^o1n>9fs9I zC@d_qwj>f=$`-`yc7K zPvNn@&=H2qH2y-UHVMH0Qms&?r=v{B%gUWbqvj}+X5s!zDx|Lg7Q2P%*3r|v(9^v1 zONJVnmpY$DWU+)tJ^SsV}1nrD5?(Hf(xX0p2EmVHocF<4v4MmlqOQDLQADCLuCgdZW%0FB`Sk5b{gG9Y=}x=RTVG z&DCFDZ4aJl4W9V}YoEQ+I(ubvFt^#0f9%f-p*;kb3Ua@$*NqjZX?RfAR~nVo8p2TR z{9x6nIXgYNzEq_at5u;=YQVNn*DH-O0RARzG)>CZD4H7m4h*~>yb*Y>LXGC~X2H?k zE`KeXwVJMjLyLc-HC?g|_83yZ;W%^)X@31v;OTSa#9zIU=N@#^J(;M{9RXrV6-Dg4 zjr~f1f;mBCA^j?y0UBI|>{hKW+wxD{g>o1Ihg}Y5Iu&5%KvEKZM-{w2_~H$8I+1pu z_!Pw^Kl0KM%Spi^--?Xd_u?69igJ~YAKbWW)t6Ir;-;1E%$=U18L0ZBD?d5 zUANjcW!XtRY~ySKG>T_2Jqn@FiwI#{vs0=6ysh;M^akKi7uF>hc4SGCJ||=UAc^OS yEP1~46KU!%WUfW#o{`CCWcHtA>=~JOM#jHXvCf?cN%PW2p~vHAzaj{V7yb)k)f(Oa literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f6e75f01b3538a5ae401606985e8732515d606b9 GIT binary patch literal 45470 zcmeHw3v`s%edjkXX*8M{jow1f(Gv+IVGv+27!Z0e!a@=wA!0``8S4RZgrt7+VH?&? zh~p#>J27sY7+P9G*Qw#uT|0ZyJ#IGblDIv`r|Ir$knkwqlE!Vln`Te9D@C|+v*~t! z|9ijJ%zS!)AMu{kd!+l_@4mh}_kZvI{$Kar^tY$MRh4kVBrO; zAcTc8La-uVkPh7=NJ3DMEJuZ)rNb&&@w9f>0(QwBa7d0oij)#al~T(D#M?TYfix*C z;F4T{bSXXHmfQi42vlAXCZ=WJy_pY$-dCBjp5grQATClo!aC@&g4@ zL12xvCQv9928yI2R;RtAI8Y*$u&|?}G*Bj$1^kjfP%f1RDx``)rBoTHlBxpLQgxt4 zstK%>*0Q`Q9kqcvsg8wHJJto(OY2$K*-;;8kQ!Jxt)nrpLAr;9T?lWKHY)L(5Z}bY z=^dK_o2AW4{1(J-Wnp*6mcYHzy-NHx#BXO|Psg^vebRkO{0_wLWMOZ|j==rW{Rmrv z86CRwlOEm8{$XLhs(+N8EJ;fS^8Srt6`vs^f`Z?lEJOM#=M zQpZuBv_F_7JrK;6I)gd+LNGU981qR7j%++~Z+)s>^2mL0Z=2)Z?l-h+lK0d$Uc6?P0Nnh%GY29%Qk_h%GV123c$=V#^G% zy)4#`*m6T`U(ix1)K#9&J1{yl9QL=2jh+Zg!~WrMNgN%HG%y zZNa{d7G&}$ozONejY_BFoVeHa;jna49G0E*YY$8e4cA%Z^p=V7k;NIGJ6Fl!Zddl_P{$M^b1F=+nyGLA;c96 z2Q9}fLCXMAxC>ebfSHxBZ9ticf)KO^9UJVJA1OblblH9!b3k?vGH{3RVeyol6^@)5 z9}I{PI=r>gQ4snsW*%uK6^C7EneQq;67O8UkMw1pC6n=!6QP z3|019del_Iv&UFUiPt+$MWU0oe!;BtfVw0K^@Vz+0|YeYjIm~4>bGfMu%mr!=~c_} zr&*~1x0*sK72($OePrVM1LDHs+$HQaCS$x_4m)7ZA4)%d2?30$4zHY+hcq0Q2M zQV<@)_r=Hiok5$ihJie_U{8S>(x2NtB?!mR@5RCc7)rBt39(;DR>MpEfx-+ z+>UppbPGzG{7OchwMmf2fpd>rd$^Q)Bh#K)D(|yszJ)E?sJS%7h0#zl!fx zsCDR1_T7?YzFV#4J-knaf#Iv)Tg6vv)Ee~XieIf$^O}D3y;Xd5omzwbWM4&Vmh9sO zH81+u8FUy&PjZj9Ssnn`SA=z=TBrV`b{qo^D8_hw;M1tX9O4_*5@vmXl8*~QnouhA zTgy>{4wGO?rLe^|YN<;-o!>hw`A<&z6%40Q9EQMv;v0+IpLTkhgcFW2u|njIny=g71Gk@Jc1+bwpS>toOOC{IdjjC#PVvRd9^p4bywHj zbZ%L2*ve8CQ-oCa7lyxhY$4TF=8ZZF&$_TixwFpKd~MgIPsK_aq9qM)r#H;Hv!2_3 zZvXjHF?ab5clqUq-mY$bCnGnOQ69}GfBB(Tj$Ao1ld*IgX@AsYjer8lEmZ(~r1&G%T;xCrI9Y zOOqvN1AF7_7Z9>j2&_)V5g$u&(0ee2*dKK*lQNaGRF>w9OUqKy(pZ`+E-hP0OJ`~B zxU?K4&BM~XacQ|qS_Vt=#iix3R%Wu4EJ~5`Sxh#I0qcgC0v1CooRb(Y#H?X4`78!3 z7h(!o%o-L0b_+2@ET)LXfYm}wF^ego7^x&!DwXzIq_Tctheh&(SS*v>3|lC3`*b;b z(^#uE&RY5Rh9kp>1V!1{9!I(oZA9>0pygpFAZtdv_F6s-aRGe5ENI_gs}!y|#H}bG z9wzT0^6rDzRcDp6jLm6fxKFl4B(csa?m!mV6ABGdI|$*1LJ^{jD$X#p!N^N_jj?>A zk^FoTStFJ3-Y3%2>S&lJFIu*7j-G!UVUOz)ixqbvUmUTC<%NEzdx2J8`k{o8xa$Ss z_!);F9L`b~^fE|E(7D#rh4J0V(Vu@5d=I!)tL%uJ1nVO^Lm`R^g|69Udt`WQMDG_l zP5Hg}t<(fyO6Auc!<(F&@N0QB?3WT{h+q1y({nC0>MXdB_qMa1|18WxjrE}ZWAO7M zupIn0;0Il^a;Ooku35xaHo_dRrgE?vq z>*!Ln)7Z^D)Yb&21EGg{C&3v-8HSuRW)s{JIW-zz&u+{)X&KO{d7WNwvIZ^Nz7OgX z)DI{l1MAk~)$371hIVPRyqYEm#;?`q=<$=5em=kw<}1e2TbH_t%!NoN4@Lc zvspa#@6uai)Z6%;!|K@pwlSkJ=52_28{SK`c^c!MYr|cEax^*wi;FXZA8N+JIYFSi*R%Nw@K)MwR1HfC{ z1+8~bo5s?r7!Lzd=rNgC{T3t9Un%Hpvl$}{oM}H@5RWsDKN=qNpMdbHQFcsDJO*@W z8zm_UMb)LqsUd~p^nj=ig;fC%qo8FM6%J9(2!n@g!;n=*s>sF{fmR))X7^$wJ`pS19xdDc zM$L3t^K|y!v#qn)`7iE%e)r|gH?wP}+_kqd@}GO++!IsIA|1>hWWdRek+F%ewDOR9 zk$wcB0XF2Uj&2JrdEJ&PZTzV>2YHy&MqMXffs)l`VwD5bP0(_WHmZ~w@CaJf5vYzC zK--o`a7bYhaP))CQDuUS;xi~MXYrJfA?2El^GQ!3UHl?E%sRqBOk7pNX@aj&*?3Ho zT+z3vP&qtcniRp8bAI22zLy3r4#Z0Cjh5UywXJiyxQnqXb|@ z{B#{WDT*L-;vjh1&4#A`34a&(U^#1ad}v}qG=fs3nI#f3GyqS|Rw^^*l>KqbhDna! zrdo{9?7H0gO6Qf%SkS743UPimO(Io0@8Jftxk{}4NmZo>*0xsh!X3Hj13P@@_5gPNb!^6gfu)TfsoU% zp6J2nMQV>oO%#cY6w}GeAdg0;_+@x;1X((746mE7xaw+WNV!bKz6mdRy&I3EZ@H1a zB_rn9cEhvnR(9#R z)_1c@&$iym$U49InNw5F0)5il{q(J~hblwEpi5`xWBq;yO_ zqvw+=<8Z8brthGEY$Z|`&GrkeFLhq*j1@OUi<_o1HcvS>>;0laNa+`0p$f&9UoL~T z4OZ}+cD6{$DW81|q(Ows39!&xQGR8p{Rg#?%5r%>f$RP&HsbZ~zsi7jzurh>W{#*A zOd$8d9t7?T_Xe#&+r88_I7k6VALmHaSChKUKQZ1YLd78TU9-w|>U!DnXn5@8Ffme3 zAztg6u_&W=|)ijL#NZ5&C6Cnmc!}Z1Wycsv1~}r(BgC1^6ulFq8i#c?<| zA(bX#$iD4Sk%dFxDi-H+W_xAKZEwY;2r`O%riT{qY6o-W^WedEpY*0*do{SQDR zG@W-~s^{R%yn|=cXLAZ)-2eRkOP`z0X*`=Yo1SyY`I7shdnUbV7Lteq=MH?md(Q6k z^jLn{(s^Eb@$=7rK9*k}&9A@u;A^34q3MQQGx@uxg8k?1q)>7{?~eJJqQ0itwVP(w zl;CeJRmj}@kAgij>;2mIQE0537 zNEJWhntnKqxvefvNrk*OQhI zfmbF=(Rda;y@$yA1gG;EL92nMOP+15X`C1x-N(;(^1~D77?_l(Hp6iUcYohL&+1#Sp zyuueJo}Y;2t&irB-ZpRRoI~)e{YSy(@#>4zU9X)AK`FjOKar2F3ka?}y8at=fwqw5 zJN98ACI4E}9DBaLnZGBGFd8Lngnb$QQuHKS%#fbw)bT_jf1QVCqtLG`JgXjI3DjvC z#-~AraaVOhZ5v1N@;bEz_O9x5nAWLr+;>%HifNtNl6_Zorkd1g{lGASnU!HqQ-4}@ z(&et&X%Cv!`9Wc1R_6zWky)J|6h=vP2GOT=E;B;LD4`L57c;Ss@dDy+At?S1c_dsE zFTrc?f((Y?5L(x{yhX>KOe8}M*X1&>uVnNEm64qjV?#{#5}_Fn>lXt-VUWL=5EO<~ zhLHM!2!U9$^JCeS(d^2z&e^>3Sl-%b-rDKBIuH%%d6(ADq*oJ(pds0--Pboyxoh5b z2WCA5m%3&=>*ABUu2-?-eU#ie}i zLtv?;W-Zdl9c!QkL^mfkRog6~*c;GDn0}p0QbtC1KCbO4*CGsqQ3@VJdl zNH8-oCNa2eM`_H;jO^!5J$LHtsax*s=eo~z&-wyHd#630b_+_TzHhj0r`kQ!xjb3qj{NR zD|90KIMeOO>ig(8d+(At)*sUn zB~N+Dr@h6a+8VU_f48r987FsQ580gef>W$iT<hV$>Jug6Ku^`?6zZIeMw+Li*?H&x<+#f?iLpQFQVeDG+~`- zV^*402|E}}EiYlY$x_TLFH@B?Gs{b-l4fRk2`f-mrGxc2Yyea z3H7{B#zyrH*F1TD?Zc)UdPxy-3+7TO}3>WF{9FLp1G zE{1;bfSMxoLU5UA5vlVz`h{_g z&>6^4Q;z27n_rIB4v?uAb4PdT;?|=*x|sFo&_Jn{XJiA_s(o#M@GNr!Ra%JFSLOx? z_RKY+PselBw}E;s)To7M=rK32NegY(LR-}k`&Gs2+rSPr1cJ@4hV^I9{d!mpsg

gZ0qV1X`qNSK1CYBvX_$D4|R5idVAX1T16tX zgmUx?in zW7s|^+p*08Kc1)4XSpHVKKe`nERLBGj@l;*2{%-yh`|$35iPJ>{GDKd%vEVde8X*! z8Ls(flmf27o{BWDVud{8tbf;?L%irMJRr3;JmMl_?vkjxWX_XX?mBCqahBZ95o$07#Zi%91SeK6wUAyuy~{*fWLf{ zs${8Uw|#=Y>T+wWYIC$|bF6B6v}*fw)sE@1o%Ho$EZv^G3&pWD4N?5L8x|oQZ^IHx z{;aS3@<6P5d$fA{8+A9UTcf^KNG#JlZ#%1#NqOK(zE1cNSZ+6c8!4)pN9%!ne~iL@C>_$)4lQe?eiD_jc1zrir4;#hfbkk`vL5 zn$Sc=E~5yuM#)`sA@{QV755c)%zt0hf8Y0a#&#Tx?l?H@4^HRwGUeF|kH7fj^G`x~ zb}KvYg~z{f|CHMgmD&rB$MV-j^ViL!ucP?SO=s6YV8jy4vy+-_CcTC-pZ><~DR%|W z`^584%%oRR>|@{9Ipr>c(b5a|J@fe~XQ@6D(PHNA0wa7C1(ziQMZyTJ)F(9Rfr1wW zJCS?vuH~|a-VW9XctqPVj6k^Fh!H>+P#Y7R*@law7ABvD*atJ1Fbs40$V*ntV;#4g zu1i|SIo}_rA&yAHlZ{xNo7rMYhHJFLIxJ^cY7zPdHwfjEde z9Ghqx!KRBX?4h>7YTB`h@CO<*;MjE4jwl6_jbF>da?`1SaVWUvdn;LK;Bf>aVgh25rlxAk;Vmi86 z_Hq*KQAk2Y2g75skG~(E5J$1wR|=1bR%C;0g2aik%RtZ>Fq>FHRdE(mAA@?{{RGgX zr~n3-P9ZDr#p36SW0~utnd@KejMeXp*6)jD25w{q&NaUko<(x~^YyW;`e;`Ds{^ry z{n3W~v8)GfWIb^1;MBn&w#el5S+HG|Ox$A`)zOUV%a7d5*f{0fs1FT%k2!#hlPSbq zEJvm!o3)F-i6W~oXD1ZZOTUj6REDC`9WVfcccq%-Vl1e)44IhIl&RP8y40XmhXp@F zfNYeOKw>6DjlNLsRH(_pb;W%p zv#S9Uvm?mP-|$PvhyAMYJg3e5Ds5ixhoGBG=l!E2{*&Y3N5i9IByVl(65mJTcF}
(SZk2{o*r(Gi((MC2rgSDDEia>zBm! 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 I+%nRC2JhXh!~g&Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1191972b5a2a9a4a5c4cc3120b846dc5984e51e3 GIT binary patch literal 10982 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 E2fu@^2LJ#7 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..74809ae83d909b85e641a2198b8cd20fe741de37 GIT binary patch literal 13319 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 ChH6{@ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a63400e8e16c104858600ac285bb5862450f53ee GIT binary patch literal 75864 zcmeHw34B~vb+JDYf(g4(|g>DWsIrRtc0+O6m7M=iIl<=)IYF zl8C?G@2h{wowwX`?z!il?Vfw@({5jFbrJp@YTEP2zkR*9=zC6cvSv z9xv+l1b%gGQFn1_YF|;RC{UCtK3^0l?)CJPq)Ijy;o8$%+EbP)
  • %@}7!RMNeg_ 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

    KbJ72OE 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 O4_$rrp9C@XBL4<0cKMY6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..47df76255b056f93bf2f1958bf7209dc91c22822 GIT binary patch literal 4548 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..76f3c808fcddbc054264947af17d311ca8836ba0 GIT binary patch literal 5042 zcmcgwZ%kX)6~E8#`PmrT7%-3!2!o|)vu5Chlsg`bwXl|0D{JNDx_Ddc06V~WPyAr4owaw7RDBysdD}=Xzz(&L zZj$mr*?*<|iaa2ht7jdlh9e{#SZ~&~CmtYM4$aG>{86BIA zkBlktQNzp2Gh>OF>C}wjPUchwi&%h;zF1*F8`kx#&eS}Eiss==hEJ5$^bs|eyQZe! zUK5NuKHYbhiW;ofWsW>!$fMI!!|?=;GZh~l8@8sLfr@&*LvzV#0W^VsOn@;F;lF(m z$O@SyR~@hXl#mS6b&%JE8$wE$1BsbZjycB;FV0ySB_t)KoKXq1Du01;L%PBBl;&Dc zG*pApr0Prgw4!D*SdnG0Aw#AbTcr6!SfusPkGha}fxwDoREf%TTxgGRDrycEO)u)K zu%M(13&lbn2GXMoh@GOQm7JQ7>KxNZamny1iki-_Xi93kY5#d9FC$|ne!!-K%n8<*^L6zgW?WT#zU&1dUu z?RAlfI3ac9Fqw69lUcc1pMq0AC}xGQbGdy+W1+=j$UK5N_bVChHX??U$)=g%-Z}UZ z1Jg}}8$h5k)ex_1j3TNOl}b^|hO1Cy*+O2Yby$VRh&EzHUHv~(S8yJ#9eJq7jL{Qd~c7ho-$UeefE33QhN-Ic(xQsCIH z^5wwbO8l{}@q@SCf9n@FH++Xby0zguUh$nM`A*!Amwjhf#vcbmm0({f*jEYmmxBGj znR|G;9K5hH_1GU+x%A|4&ttd$iMwTWzS7!PYVF%_hws0%;U3&}ie2(EnYg?^*51AT zcbC}ZFS*;_^MHYSn^t>&e&M4xf2n+^JgV=1(EjM<$tNwLyRGGxf%nEAdz(K{wcI?9i@m17dGjjE-qaL6va*4>G!#%#^}TM*MTxHExDn3?EOJlW4-N(Z6#G)nL^Sny47 z_$GwX;V9xaAyiibjdqk8tr%X*aq_w~(MHbni?j#U zLSF?^gAct3<=wE(t`t7^Fj5Z3%MF*;-IuF6un|Fn z<3<+&=G%-8A~0UAti(-FtePNP87Zps#W;NjTIm=oEu4}Zr=(EoJo!M{=o~<>lK*G0 znk~bEJ2TTloJN-0OdrAT$^6z+Q@^W zzrD=a>e`?sPyb76I%EN~n9s6MVLr5=srn+-7QhommOZc|8yg8*>caT<%aX??(TCY~Za4CB$uNYJ^y7+E~9`cWX3{Y`QEn-cD^wUOU7-;e$J zM-Qcq;~ej)eaz7E;NIjR^!zB{ba6)kl| z*Bef)yC?tD1B3&)MiOD4;Z~Hqx&Ti>!=oq*h0J0Od3YrLa8b=!k_JVY&r+S`vhcDj zfY_xdnL-*Ecaau~8f8lqO^Z8pb7NCjd>x3<0@oFAKv{U=+JRe#o{cpZnzWY7DGKO> z$3Z+>7tN~;YMkG;!ocVh$TvTO9eySq{fpE0%z?L=ZIQ_SP0r%O)Q`0j)!GSRr#4xw zO;&4%b_N*QX*p%K{2GR`4hvsIBHT&g5d|*_cw=Pq*ZI*j<6PGb_&x#GGxPgIr1}bv znC}4|A}QXJcswyW_O{2*BK%DN&wu{hH=3Vo0uF({h4{mVKW&=!wxJsTU|<~EEx4iO z03|(QJy?p?hZX)wVeeY(w$>i;9%eWpio@*q{^Bn&!?(acE|}q3Hy>bLv(B7@HeuUS zNH~Zy$aNs$Pa*e^dDJe@d8k9gb+m^~Q4oaB$f3_k$1_P199sm)R~{k^{+W!I$oNyz n|CEe9C8s|pJx@vBQ*!Jp8NNe&+1D;i39BdW-2R#%$0zzHqZKj( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..22b0869aa1520ca3cd0ecc196729e4e6bff2f1ab GIT binary patch literal 3858 zcmc&%O>7&-6`tiTmrIJIs2@uHku6G!6){#Q8?kJ+H4F>-A(_~+x-#elmAze)OL=RG zWM@aVW%R%{1sWd;t3VG95FiN9gH-g8V~+)j-b1LioPmu7Xp0~>xoYF|*7s(2Mf#^L z&`Sr{H*ep}yq$U9dvD$zjmIJc+GOAO5B{hU@@IVU8@!9Q`)lYLL??_~CAyTCasd{| z$xI%^_ds6B1zAw=a$e1aSSY74Ef;2CAye{^TnFnAd@vu)#aK-6YCfLpWSziEdMKaB zb+N8oH|rMfTD~XO%X$SL&L?wytZ$I$k$$3g^pi@0^(V;?(WCGlyV7?hc0zGC%J%8; z5t8n_lU%%Iv*PtsxkghL?2=`s&YRUtI>6Ka#yro++_@0hMt%l7hYmC?1d%;VD+ z=jV)>*+PDL*2vHBn0Q~joWC@kn_cAELd9gDa0OPza%uacqT?9dg*s!aqD8a$- z!KT3@!?Sx1NP`r})xhbW5>f)$0Qs(TUDBl`7;&2(SPJa%@{(sDLUctBPAFg`^(!3b z%5~Fmc(f=iVOVGi-fcQ-)uLgRN+_@yoPn#T5h|7-qFvVqrTfrM5cyYy9GD#&|ZP+)%| zDE+l=|Eg6>QGZT`obd3j2Q#FuE_M1N;1y_2(F)2!x6ilw_PyzW_BUVWLb5g6=T3M` zg3M(JxdmURw+ossx36F7^Jg#Y^SM@k`dvbnh4;QQLdfOSZ^4lFew2`%{enev^?{IY* zsDTG>GQar8nMqpiS-u z$e{&sed|X$)8MzW{C9rxQw*#4WLDk?lr^= z+inSn38%h7m;@JI;Oc8k?jtq0_Ly=dSRijndH}8<@N>L~{1{-W7*WJXP+UIy_ZZj`a;TTB#7vJYU|7A-VCG(C zZ4LQR?dJtN^PK@*Tu~`TSFMy?_M$swJE>YVLnmN$S`op?LxvYdF$iYEWQ^L^R++^+ z-1|-h4;s#~DrIr2jk@6yv>l^X0Ci5kpB*o0I&_Ftbi#iebR$4Wu=W?gem)ff+^v` zRQ4k+Ge^(DvF7O%o^Tq-a{y?rt;=QJ`!WLM^4?kh9RxuE!JL3#lgy_(xMmnta|LQf z9x;rST4}X{JXDiETs14+NT*?xZR)U!4JBv|hC_x?suh9J>aotHdMfVW^iDBS6k*RT9t}nq$M}R&==0RIdlZIAi+3x@Ot>>XAmZzD(;&`=-C{#o#(*5EbYKaP;zo7rRRRL=`8MnNkVcgV_=hr?z#i)D#OX4?y z2VsNob*z^}<-}tv;%~ss#Ex4qMO~TqjX2K5&=jViZzG4dj2qC}85i=i6#se<0|$eC zN0ub%b8_%YGVoV&;!h;oB-tlq{0W(SLT0}t!%xWA6LRDqD*ix>#igvYe)Rs2z9z^C G<^KZUaymW$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..73b94434b0bb9ca3b9ef677cd1ba44ae3e7a1bd8 GIT binary patch literal 32610 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 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))

  • 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 M#6Jowz*gt~2XF#@@Bjb+ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0c9cf19ff374b8040c1ef23d4116665bb0e84103 GIT binary patch literal 45427 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