diff --git a/README-zh.md b/README-zh.md index 1f66be8..95daad6 100644 --- a/README-zh.md +++ b/README-zh.md @@ -76,6 +76,10 @@ _完整的實體清單請見 [可用的實體](#可用的實體)_ | | sensor | 運轉狀態偵測器 | | | sensor | 洗衣模式偵測器 | | | sensor | 洗衣行程偵測器 | +| 空氣清淨機 | switch | 電源開關 | +| | select | 風量設定\* | +| | switch | nanoeX 開關\* | +| | sensor | PM2.5 偵測器 | \*僅在裝置支援的情況下可用 diff --git a/README.md b/README.md index 010e565..8e6d9f6 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,10 @@ See [支援的裝置 / Supported devices](https://github.com/osk2/panasonic_smar | | sensor | Device status sensor | | | sensor | Washing mode sensor | | | sensor | Washing cycle sensor | +| Purifier | switch | Power switch | +| | select | Fan level\* | +| | switch | nanoeX switch\* | +| | sensor | PM2.5 sensor | \*Only available if the feature is supported. diff --git a/custom_components/panasonic_smart_app/const.py b/custom_components/panasonic_smart_app/const.py index e27bd5e..0fd76e8 100644 --- a/custom_components/panasonic_smart_app/const.py +++ b/custom_components/panasonic_smart_app/const.py @@ -25,6 +25,7 @@ DEVICE_TYPE_AC = 1 DEVICE_TYPE_WASHING_MACHINE = 3 DEVICE_TYPE_DEHUMIDIFIER = 4 +DEVICE_TYPE_PURIFIER = 8 DATA_CLIENT = "client" DATA_COORDINATOR = "coordinator" @@ -85,6 +86,12 @@ "0x61", # Washing machine dryer delay "0x64", # Washing machine cycle ], + DEVICE_TYPE_PURIFIER: [ + "0x00", # Purifier power status + "0x01", # Purifier fan level + "0x07", # Purifier nanoeX + "0x50", # Purifier PM 2.5 + ], } DEHUMIDIFIER_MAX_HUMD = 70 @@ -135,6 +142,7 @@ ICON_THERMOMETER = "mdi:thermometer" ICON_PM25 = "mdi:dots-hexagon" ICON_NANOE = "mdi:atom" +ICON_NANOEX = "mdi:atom" ICON_ECONAVI = "mdi:leaf" ICON_BUZZER = "mdi:volume-high" ICON_TURBO = "mdi:clock-fast" @@ -150,6 +158,7 @@ ICON_INFO = "mdi:information" ICON_WASHING_MACHINE = "mdi:washing-machine" ICON_LIST = "mdi:order-bool-descending-variant" +ICON_PURIFIER = "mdi:air-purifier" LABEL_DEHUMIDIFIER = "" LABEL_CLIMATE = "" @@ -172,12 +181,15 @@ LABEL_WASHING_MACHINE_CYCLE = "目前行程" LABEL_WASHING_MACHINE_MODE = "目前模式" LABEL_OUTDOOR_TEMPERATURE = "室外溫度" +LABEL_PURIFIER_FAN_LEVEL = "風量設定" LABEL_PM25 = "PM2.5" LABEL_NANOE = "nanoe" +LABEL_NANOEX = "nanoeX" LABEL_ECONAVI = "ECONAVI" LABEL_BUZZER = "操作提示音" LABEL_TURBO = "急速" LABEL_ENERGY = "本月耗電量" +LABEL_POWER = "電源" UNIT_HOUR = "小時" UNIT_MINUTE = "分鐘" diff --git a/custom_components/panasonic_smart_app/select.py b/custom_components/panasonic_smart_app/select.py index 0a8673e..2aa9e49 100644 --- a/custom_components/panasonic_smart_app/select.py +++ b/custom_components/panasonic_smart_app/select.py @@ -7,9 +7,11 @@ DOMAIN, DEVICE_TYPE_AC, DEVICE_TYPE_DEHUMIDIFIER, + DEVICE_TYPE_PURIFIER, DATA_CLIENT, DATA_COORDINATOR, LABEL_DEHUMIDIFIER_FAN_MODE, + LABEL_PURIFIER_FAN_LEVEL, LABEL_CLIMATE_FAN_POSITION, LABEL_CLIMATE_MOTION_DETECTION, LABEL_CLIMATE_INDICATOR, @@ -73,6 +75,17 @@ async def async_setup_entry(hass, entry, async_add_entities) -> bool: ) ) + if device_type == DEVICE_TYPE_PURIFIER: + if "0x01" in command_types: + select.append( + PanasonoicPurifierFanLevelSelect( + coordinator, + index, + client, + device, + ) + ) + async_add_entities(select, True) return True @@ -235,4 +248,59 @@ async def async_select_option(self, option: str) -> None: await self.client.set_command(self.auth, 159, target_option[0][1]) await self.coordinator.async_request_refresh() else: - return \ No newline at end of file + return + + +class PanasonoicPurifierFanLevelSelect(PanasonicBaseEntity, SelectEntity): + _attr_has_entity_name = True + + @property + def available(self) -> bool: + status = self.coordinator.data[self.index]["status"] + _is_on_status = bool(int(status.get("0x00", 0))) + return _is_on_status + + @property + def label(self) -> str: + return LABEL_PURIFIER_FAN_LEVEL + + @property + def icon(self) -> str: + return ICON_FAN + + @property + def options(self) -> list: + raw_mode_list = list( + filter(lambda c: c["CommandType"] == "0x01", self.commands) + )[0]["Parameters"] + + def mode_extractor(mode): + return mode[0] + + mode_list = list(map(mode_extractor, raw_mode_list)) + return mode_list + + @property + def current_option(self) -> bool: + status = self.coordinator.data[self.index]["status"] + raw_mode_list = list( + filter(lambda c: c["CommandType"] == "0x01", self.commands) + )[0]["Parameters"] + target_option = list( + filter(lambda m: m[1] == int(status.get("0x01") or 0), raw_mode_list) + )[0] + _current_option = target_option[0] if len(target_option) > 0 else "" + _LOGGER.debug(f"[{self.label}] current_option: {_current_option}") + return _current_option + + async def async_select_option(self, option: str) -> None: + raw_mode_list = list( + filter(lambda c: c["CommandType"] == "0x01", self.commands) + )[0]["Parameters"] + target_option = list(filter(lambda m: m[0] == option, raw_mode_list)) + if len(target_option) > 0: + _LOGGER.debug(f"[{self.label}] Set fan mode to {option}") + await self.client.set_command(self.auth, 129, target_option[0][1]) + await self.coordinator.async_request_refresh() + else: + return diff --git a/custom_components/panasonic_smart_app/sensor.py b/custom_components/panasonic_smart_app/sensor.py index 7189bed..ca9fab4 100644 --- a/custom_components/panasonic_smart_app/sensor.py +++ b/custom_components/panasonic_smart_app/sensor.py @@ -21,6 +21,7 @@ DEVICE_TYPE_DEHUMIDIFIER, DEVICE_TYPE_AC, DEVICE_TYPE_WASHING_MACHINE, + DEVICE_TYPE_PURIFIER, DATA_CLIENT, DATA_COORDINATOR, LABEL_PM25, @@ -134,6 +135,16 @@ async def async_setup_entry(hass, entry, async_add_entities) -> bool: ) ) + if device_type == DEVICE_TYPE_PURIFIER: + sensors.append( + PanasonicPurifierPM25Sensor( + coordinator, + index, + client, + device, + ) + ) + async_add_entities(sensors, True) return True @@ -222,6 +233,15 @@ class PanasonicACPM25Sensor(PanasonicPM25Sensor): def command_type(self) -> str: return "0x37" + +class PanasonicPurifierPM25Sensor(PanasonicPM25Sensor): + """ Panasonic Purifier PM2.5 sensor """ + + @property + def command_type(self) -> str: + return "0x50" + + class PanasonicOutdoorTemperatureSensor(PanasonicBaseEntity, SensorEntity): """ Panasonic AC outdoor temperature sensor """ diff --git a/custom_components/panasonic_smart_app/switch.py b/custom_components/panasonic_smart_app/switch.py index 10dd47c..a878e0b 100644 --- a/custom_components/panasonic_smart_app/switch.py +++ b/custom_components/panasonic_smart_app/switch.py @@ -7,23 +7,28 @@ from .const import ( DOMAIN, DEVICE_TYPE_AC, + DEVICE_TYPE_PURIFIER, DATA_CLIENT, DATA_COORDINATOR, DEVICE_CLASS_SWITCH, LABEL_NANOE, + LABEL_NANOEX, LABEL_ECONAVI, LABEL_BUZZER, LABEL_TURBO, LABEL_CLIMATE_MOLD_PREVENTION, LABEL_CLIMATE_SLEEP, LABEL_CLIMATE_CLEAN, + LABEL_POWER, ICON_NANOE, + ICON_NANOEX, ICON_ECONAVI, ICON_BUZZER, ICON_TURBO, ICON_SLEEP, ICON_MOLD_PREVENTION, ICON_CLEAN, + ICON_PURIFIER, ) _LOGGER = logging.getLogger(__package__) @@ -113,6 +118,26 @@ async def async_setup_entry(hass, entry, async_add_entities) -> bool: ) ) + if device_type == DEVICE_TYPE_PURIFIER: + if "0x00" in command_types: + switches.append( + PanasonicPurifierPower( + coordinator, + index, + client, + device, + ) + ) + if "0x07" in command_types: + switches.append( + PanasonicPurifierNanoeX( + coordinator, + index, + client, + device, + ) + ) + async_add_entities(switches, True) return True @@ -410,3 +435,87 @@ async def async_turn_off(self) -> None: _LOGGER.debug(f"[{self.label}] Turning off self clean") await self.client.set_command(self.auth, 24, 0) await self.coordinator.async_request_refresh() + + +class PanasonicPurifierPower(PanasonicBaseEntity, SwitchEntity): + """ Panasonic Purifier power """ + + @property + def available(self) -> bool: + status = self.coordinator.data[self.index]["status"] + return status.get("0x00", None) != None + + @property + def label(self): + return LABEL_POWER + + @property + def icon(self) -> str: + return ICON_PURIFIER + + @property + def device_class(self) -> str: + return DEVICE_CLASS_SWITCH + + @property + def is_on(self) -> int: + status = self.coordinator.data[self.index]["status"] + _power_status = status.get("0x00") + if _power_status == None: + return STATE_UNAVAILABLE + _is_on = bool(int(_power_status)) + _LOGGER.debug(f"[{self.label}] is_on: {_is_on}") + return _is_on + + async def async_turn_on(self) -> None: + _LOGGER.debug(f"[{self.label}] Turning on nanoeX") + await self.client.set_command(self.auth, 128, 1) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self) -> None: + _LOGGER.debug(f"[{self.label}] Turning off nanoeX") + await self.client.set_command(self.auth, 128, 0) + await self.coordinator.async_request_refresh() + + + +class PanasonicPurifierNanoeX(PanasonicBaseEntity, SwitchEntity): + """ Panasonic Purifier nanoeX switch """ + + @property + def available(self) -> bool: + status = self.coordinator.data[self.index]["status"] + _is_on_status = bool(int(status.get("0x00", 0))) + return _is_on_status + + @property + def label(self): + return f"{self.nickname} {LABEL_NANOEX}" + + @property + def icon(self) -> str: + return ICON_NANOEX + + @property + def device_class(self) -> str: + return DEVICE_CLASS_SWITCH + + @property + def is_on(self) -> int: + status = self.coordinator.data[self.index]["status"] + _nanoe_status = status.get("0x07") + if _nanoe_status == None: + return STATE_UNAVAILABLE + _is_on = bool(int(_nanoe_status)) + _LOGGER.debug(f"[{self.label}] is_on: {_is_on}") + return _is_on + + async def async_turn_on(self) -> None: + _LOGGER.debug(f"[{self.label}] Turning on nanoeX") + await self.client.set_command(self.auth, 135, 1) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self) -> None: + _LOGGER.debug(f"[{self.label}] Turning off nanoeX") + await self.client.set_command(self.auth, 135, 0) + await self.coordinator.async_request_refresh()