diff --git a/README.md b/README.md
index fb70b77..f37acd1 100644
--- a/README.md
+++ b/README.md
@@ -74,10 +74,14 @@ Note: `configuration.yaml` is no longer supported and your configuration is not
- AC3854/51
- AC3858/50
- AC3858/51
+- AC3858/86
- AC4236
- AC4550
- AC4558
- AC5659
+- AMF765
+- AMF870
+- CX5120
## Is your model not supported yet?
@@ -170,31 +174,41 @@ The `fan` entity has some additional attributes not captured with sensors. Speci
The integration also provides the original Philips icons for your use in the frontend. The icons can be accessed with the prefix `pap:` and should be visible in the icon picker. Credit for this part of the code goes to @thomasloven
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/power_button.svg) power_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/child_lock_button.svg) child_lock_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/auto_mode_button.svg) auto_mode_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/fan_speed_button.svg) fan_speed_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/humidity_button.svg) humidity_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/light_dimming_button.svg) light_dimming_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/two_in_one_mode_button.svg) two_in_one_mode_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/timer_reset_button.svg) timer_reset_button
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/sleep_mode.svg) sleep_mode
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/auto_mode.svg) auto_mode
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/speed_1.svg) speed_1
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/speed_2.svg) speed_2
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/speed_3.svg) speed_3
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/allergen_mode.svg) allergen_mode
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/purification_only_mode.svg) purification_only_mode
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/two_in_one_mode.svg) two_in_one_mode
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/bacteria_virus_mode.svg) bacteria_virus_mode
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/nanoprotect_filter.svg) nanoprotect_filter
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/filter_replacement.svg) filter_replacement
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/water_refill.svg) water_refill
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/prefilter_cleaning.svg) prefilter_clearning
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/prefilter_wick_cleaning.svg) prefilter_wick_cleaning
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/pm25.svg) pm25
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/iai.svg) iai
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/wifi.svg) wifi
-![Preview](./custom_components/philips_airpurifier_coap/icons/pap/reset.svg) reset
+| icon | name |
+|------------------------------------------------------------------------------------------------|-------------------------|
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/power_button.svg) | power_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/child_lock_button.svg) | child_lock_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/auto_mode_button.svg) | auto_mode_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/fan_speed_button.svg) | fan_speed_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/humidity_button.svg) | humidity_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/light_dimming_button.svg) | light_dimming_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/two_in_one_mode_button.svg) | two_in_one_mode_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/timer_reset_button.svg) | timer_reset_button |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/sleep_mode.svg) | sleep_mode |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/auto_mode.svg) | auto_mode |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/speed_1.svg) | speed_1 |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/speed_2.svg) | speed_2 |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/speed_3.svg) | speed_3 |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/allergen_mode.svg) | allergen_mode |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/purification_only_mode.svg) | purification_only_mode |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/two_in_one_mode.svg) | two_in_one_mode |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/bacteria_virus_mode.svg) | bacteria_virus_mode |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/nanoprotect_filter.svg) | nanoprotect_filter |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/filter_replacement.svg) | filter_replacement |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/water_refill.svg) | water_refill |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/prefilter_cleaning.svg) | prefilter_clearning |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/prefilter_wick_cleaning.svg) | prefilter_wick_cleaning |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/pm25.svg) | pm25 |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/iai.svg) | iai |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/wifi.svg) | wifi |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/reset.svg) | reset |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/circulate.svg) | circulate |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/clean.svg) | clean |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/mode.svg) | mode |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/pm25b.svg) | pm25b |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/rotate.svg) | rotate |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/oscillate.svg) | oscillate |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/heating.svg) | heating |
+| ![Preview](./custom_components/philips_airpurifier_coap/icons/pap/gas.svg) | gas |
Note: you might have to clear your browser cache after installation to see the icons.
diff --git a/custom_components/philips_airpurifier_coap/__init__.py b/custom_components/philips_airpurifier_coap/__init__.py
index ac48208..51b8e60 100644
--- a/custom_components/philips_airpurifier_coap/__init__.py
+++ b/custom_components/philips_airpurifier_coap/__init__.py
@@ -32,7 +32,7 @@
_LOGGER = logging.getLogger(__name__)
-PLATFORMS = ["fan", "sensor", "switch", "light", "select"]
+PLATFORMS = ["fan", "sensor", "switch", "light", "select", "number"]
# icons code thanks to Thomas Loven:
diff --git a/custom_components/philips_airpurifier_coap/config_flow.py b/custom_components/philips_airpurifier_coap/config_flow.py
index 06ac9f8..a90017a 100644
--- a/custom_components/philips_airpurifier_coap/config_flow.py
+++ b/custom_components/philips_airpurifier_coap/config_flow.py
@@ -91,14 +91,34 @@ async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowRes
_LOGGER.warning(r"Failed to connect: %s", ex)
raise exceptions.ConfigEntryNotReady from ex
- # autodetect model and name
- self._model = list(
+ # autodetect model
+ model_map = map(
+ status.get,
+ [
+ PhilipsApi.MODEL_ID,
+ PhilipsApi.NEW_MODEL_ID,
+ PhilipsApi.NEW2_MODEL_ID,
+ ],
+ )
+ _LOGGER.debug("model_map retrieved: %s", model_map)
+ model_filter = filter(None, model_map)
+ _LOGGER.debug("model_filter applied: %s", model_filter)
+ model_list = list(model_filter)
+ _LOGGER.debug("model_list built: %s", model_list)
+ first_model = model_list[0]
+ _LOGGER.debug("first model selected: %s", first_model)
+ self._model = first_model[:9]
+ _LOGGER.debug("model type extracted: %s", self._model)
+
+ # autodetect name
+ self._name = list(
filter(
- None, map(status.get, [PhilipsApi.MODEL_ID, PhilipsApi.NEW_MODEL_ID])
+ None,
+ map(
+ status.get,
+ [PhilipsApi.NAME, PhilipsApi.NEW_NAME, PhilipsApi.NEW2_NAME],
+ ),
)
- )[0][:9]
- self._name = list(
- filter(None, map(status.get, [PhilipsApi.NAME, PhilipsApi.NEW_NAME]))
)[0]
self._device_id = status[PhilipsApi.DEVICE_ID]
_LOGGER.debug(
@@ -215,16 +235,37 @@ async def async_step_user(self, user_input: dict[str, Any] = None) -> FlowResult
_LOGGER.warning(r"Failed to connect: %s", ex)
raise exceptions.ConfigEntryNotReady from ex
- # autodetect model and name
- self._model = list(
- filter(
- None,
- map(status.get, [PhilipsApi.MODEL_ID, PhilipsApi.NEW_MODEL_ID]),
- )
- )[0][:9]
+ # autodetect model
+ model_map = map(
+ status.get,
+ [
+ PhilipsApi.MODEL_ID,
+ PhilipsApi.NEW_MODEL_ID,
+ PhilipsApi.NEW2_MODEL_ID,
+ ],
+ )
+ _LOGGER.debug("model_map retrieved: %s", model_map)
+ model_filter = filter(None, model_map)
+ _LOGGER.debug("model_filter applied: %s", model_filter)
+ model_list = list(model_filter)
+ _LOGGER.debug("model_list built: %s", model_list)
+ first_model = model_list[0]
+ _LOGGER.debug("first model selected: %s", first_model)
+ self._model = first_model[:9]
+ _LOGGER.debug("model type extracted: %s", self._model)
+
+ # autodetect name
self._name = list(
filter(
- None, map(status.get, [PhilipsApi.NAME, PhilipsApi.NEW_NAME])
+ None,
+ map(
+ status.get,
+ [
+ PhilipsApi.NAME,
+ PhilipsApi.NEW_NAME,
+ PhilipsApi.NEW2_NAME,
+ ],
+ ),
)
)[0]
self._device_id = status[PhilipsApi.DEVICE_ID]
diff --git a/custom_components/philips_airpurifier_coap/const.py b/custom_components/philips_airpurifier_coap/const.py
index 90e8709..e894310 100644
--- a/custom_components/philips_airpurifier_coap/const.py
+++ b/custom_components/philips_airpurifier_coap/const.py
@@ -3,6 +3,7 @@
from enum import StrEnum
+from homeassistant.components.number import NumberDeviceClass
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
SensorDeviceClass,
@@ -17,13 +18,13 @@
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
UnitOfTemperature,
- UnitOfTime,
)
from homeassistant.helpers.entity import EntityCategory
from .model import (
FilterDescription,
LightDescription,
+ NumberDescription,
SelectDescription,
SensorDescription,
SwitchDescription,
@@ -62,8 +63,16 @@ class ICON(StrEnum):
WATER_REFILL = "pap:water_refill"
PREFILTER_CLEANING = "pap:prefilter_cleaning"
PREFILTER_WICK_CLEANING = "pap:prefilter_wick_cleaning"
- PM25 = "pap:pm25"
+ PM25 = "pap:pm25b"
IAI = "pap:iai"
+ # PM25B = "pap:pm25b"
+ CIRCULATE = "pap:circulate"
+ CLEAN = "pap:clean"
+ MODE = "pap:mode"
+ ROTATE = "pap:rotate"
+ OSCILLATE = "pap:oscillate"
+ GAS = "pap:gas"
+ HEATING = "pap:heating"
DATA_EXTRA_MODULE_URL = "frontend_extra_module_url"
@@ -109,10 +118,14 @@ class FanModel(StrEnum):
AC3854_51 = "AC3854/51"
AC3858_50 = "AC3858/50"
AC3858_51 = "AC3858/51"
+ AC3858_86 = "AC3858/86"
AC4236 = "AC4236"
AC4550 = "AC4550"
AC4558 = "AC4558"
AC5659 = "AC5659"
+ AMF765 = "AMF765"
+ AMF870 = "AMF870"
+ CX5120 = "CX5120"
class PresetMode:
@@ -122,9 +135,17 @@ class PresetMode:
SPEED_GENTLE_1 = "gentle/speed 1"
SPEED_2 = "speed 2"
SPEED_3 = "speed 3"
+ SPEED_4 = "speed 4"
+ SPEED_5 = "speed 5"
+ SPEED_6 = "speed 6"
+ SPEED_7 = "speed 7"
+ SPEED_8 = "spped 8"
+ SPEED_9 = "spped 9"
+ SPEED_10 = "speed 10"
ALLERGEN = "allergen"
AUTO = "auto"
AUTO_GENERAL = "auto general"
+ AUTO_PLUS = "auto+"
BACTERIA = "bacteria"
GENTLE = "gentle"
NIGHT = "night"
@@ -133,6 +154,9 @@ class PresetMode:
TURBO = "turbo"
GAS = "gas"
POLLUTION = "pollution"
+ LOW = "low"
+ HIGH = "high"
+ VENTILATION = "ventilation"
ICON_MAP = {
ALLERGEN: ICON.ALLERGEN_MODE,
@@ -177,8 +201,10 @@ class FanService(StrEnum):
class FanAttributes(StrEnum):
"""The attributes of a fan."""
+ ACTUAL_FAN_SPEED = "actual_fan_speed"
AIR_QUALITY_INDEX = "air_quality_index"
CHILD_LOCK = "child_lock"
+ BEEP = "beep"
DEVICE_ID = "device_id"
DEVICE_VERSION = "device_version"
DISPLAY_BACKLIGHT = "display_backlight"
@@ -207,6 +233,7 @@ class FanAttributes(StrEnum):
MODEL_ID = "model_id"
NAME = "name"
PM25 = "PM2.5"
+ GAS = "gas_level"
PREFERRED_INDEX = "preferred_index"
PRODUCT_ID = "product_id"
RUNTIME = "runtime"
@@ -221,6 +248,18 @@ class FanAttributes(StrEnum):
WARN_VALUE = "warn_value"
WARN_ICON = "warn_icon"
RSSI = "rssi"
+ SWING = "swing"
+ TURBO = "turbo"
+ OSCILLATION = "oscillation"
+ VALUE_LIST = "value_list"
+ OFF = "off"
+ MIN = "min"
+ MAX = "max"
+ STEP = "step"
+ TIMER = "timer"
+ TARGET_TEMP = "target_temperature"
+ STANDBY_SENSORS = "standby_sensors"
+ AUTO_PLUS = "auto_plus"
class FanUnits(StrEnum):
@@ -293,6 +332,12 @@ class PhilipsApi:
SWITCH_ON: "1",
SWITCH_OFF: "0",
}
+
+ OSCILLATION_MAP = {
+ SWITCH_ON: "17920",
+ SWITCH_OFF: "0",
+ }
+
# the AC1715 seems to follow a new scheme, this should later be refactored
NEW_NAME = "D01-03"
NEW_MODEL_ID = "D01-05"
@@ -305,14 +350,48 @@ class PhilipsApi:
NEW_PM25 = "D03-33"
NEW_PREFERRED_INDEX = "D03-42"
+ # there is a third generation of devices with yet another scheme
+ NEW2_NAME = "D01S03"
+ NEW2_MODEL_ID = "D01S05"
+ NEW2_POWER = "D03102"
+ NEW2_DISPLAY_BACKLIGHT = "D0312D"
+ NEW2_DISPLAY_BACKLIGHT2 = "D03105"
+ NEW2_TEMPERATURE = "D03224"
+ NEW2_SOFTWARE_VERSION = "D01S12"
+ NEW2_CHILD_LOCK = "D03103"
+ NEW2_BEEP = "D03130"
+ NEW2_INDOOR_ALLERGEN_INDEX = "D03120"
+ NEW2_PM25 = "D03221"
+ NEW2_GAS = "D03122"
+ NEW2_HUMIDITY = "D03125"
+ NEW2_FILTER_NANOPROTECT_PREFILTER = "D0520D"
+ NEW2_FILTER_NANOPROTECT = "D0540E"
+ NEW2_FILTER_NANOPROTECT_PREFILTER_TOTAL = "D05207"
+ NEW2_FILTER_NANOPROTECT_TOTAL = "D05408"
+ NEW2_FAN_SPEED = "D0310D"
+ NEW2_SWING = "D0320F"
+ NEW2_CIRCULATION = "D0310A#1"
+ NEW2_HEATING = "D0310A#2"
+ NEW2_OSCILLATION = "D0320F"
+ NEW2_MODE_A = "D0310A"
+ NEW2_MODE_B = "D0310C"
+ NEW2_MODE_C = "D0310D"
+ NEW2_TIMER = "D03110#1"
+ NEW2_TIMER2 = "D03110#2"
+ NEW2_TARGET_TEMP = "D0310E"
+ NEW2_STANDBY_SENSORS = "D03134"
+ NEW2_AUTO_PLUS_AI = "D03180"
+ NEW2_PREFERRED_INDEX = "D0312A#1"
+ NEW2_GAS_PREFERRED_INDEX = "D0312A#2"
+
PREFERRED_INDEX_MAP = {
- "0": ("Indoor Allergen Index", ICON.IAI),
- "1": ("PM2.5", ICON.PM25),
+ 0: ("Indoor Allergen Index", ICON.IAI),
+ 1: ("PM2.5", ICON.PM25),
}
GAS_PREFERRED_INDEX_MAP = {
- "0": ("Indoor Allergen Index", ICON.IAI),
- "1": ("PM2.5", ICON.PM25),
- "2": ("Gas", "mdi:air"),
+ 0: ("Indoor Allergen Index", ICON.IAI),
+ 1: ("PM2.5", ICON.PM25),
+ 2: ("Gas", ICON.GAS),
}
NEW_PREFERRED_INDEX_MAP = {
"IAI": ("Indoor Allergen Index", ICON.IAI),
@@ -322,6 +401,46 @@ class PhilipsApi:
"P": ("Purification", ICON.PURIFICATION_ONLY_MODE),
"PH": ("Purification and Humidification", ICON.TWO_IN_ONE_MODE),
}
+ CIRCULATION_MAP = {
+ 1: ("Fan", ICON.CLEAN),
+ 2: ("Circulation", ICON.CIRCULATE),
+ }
+ HEATING_MAP = {
+ 1: ("Fan", ICON.CLEAN),
+ 2: ("Circulation", ICON.CIRCULATE),
+ 3: ("Heating", ICON.HEATING),
+ }
+ TIMER_MAP = {
+ 0: ("Off", "mdi:clock-plus"),
+ 1: ("0.5h", "mdi:clock-time-one"),
+ 2: ("1h", "mdi:clock-time-one"),
+ 3: ("2h", "mdi:clock-time-two"),
+ 4: ("3h", "mdi:clock-time-three"),
+ 5: ("4h", "mdi:clock-time-four"),
+ 6: ("5h", "mdi:clock-time-five"),
+ 7: ("6h", "mdi:clock-time-six"),
+ 8: ("7h", "mdi:clock-time-seven"),
+ 9: ("8h", "mdi:clock-time-eight"),
+ 10: ("9h", "mdi:clock-time-nine"),
+ 11: ("10h", "mdi:clock-time-ten"),
+ 12: ("11h", "mdi:clock-time-eleven"),
+ 13: ("12h", "mdi:clock-time-twelve"),
+ }
+ TIMER2_MAP = {
+ 0: ("Off", "mdi:clock-plus"),
+ 2: ("1h", "mdi:clock-time-one"),
+ 3: ("2h", "mdi:clock-time-two"),
+ 4: ("3h", "mdi:clock-time-three"),
+ 5: ("4h", "mdi:clock-time-four"),
+ 6: ("5h", "mdi:clock-time-five"),
+ 7: ("6h", "mdi:clock-time-six"),
+ 8: ("7h", "mdi:clock-time-seven"),
+ 9: ("8h", "mdi:clock-time-eight"),
+ 10: ("9h", "mdi:clock-time-nine"),
+ 11: ("10h", "mdi:clock-time-ten"),
+ 12: ("11h", "mdi:clock-time-eleven"),
+ 13: ("12h", "mdi:clock-time-twelve"),
+ }
HUMIDITY_TARGET_MAP = {
40: ("40%", ICON.HUMIDITY_BUTTON),
50: ("50%", ICON.HUMIDITY_BUTTON),
@@ -354,6 +473,11 @@ class PhilipsApi:
FanAttributes.LABEL: FanAttributes.INDOOR_ALLERGEN_INDEX,
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
},
+ PhilipsApi.NEW2_INDOOR_ALLERGEN_INDEX: {
+ FanAttributes.ICON_MAP: {0: ICON.IAI},
+ FanAttributes.LABEL: FanAttributes.INDOOR_ALLERGEN_INDEX,
+ ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
+ },
PhilipsApi.PM25: {
ATTR_DEVICE_CLASS: SensorDeviceClass.PM25,
FanAttributes.ICON_MAP: {0: ICON.PM25},
@@ -368,6 +492,18 @@ class PhilipsApi:
FanAttributes.UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
},
+ PhilipsApi.NEW2_PM25: {
+ ATTR_DEVICE_CLASS: SensorDeviceClass.PM25,
+ FanAttributes.ICON_MAP: {0: ICON.PM25},
+ FanAttributes.LABEL: FanAttributes.PM25,
+ FanAttributes.UNIT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
+ ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
+ },
+ PhilipsApi.NEW2_GAS: {
+ FanAttributes.ICON_MAP: {0: ICON.GAS},
+ FanAttributes.LABEL: FanAttributes.GAS,
+ ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
+ },
PhilipsApi.TOTAL_VOLATILE_ORGANIC_COMPOUNDS: {
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
FanAttributes.ICON_MAP: {0: "mdi:blur"},
@@ -382,6 +518,13 @@ class PhilipsApi:
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
FanAttributes.UNIT: PERCENTAGE,
},
+ PhilipsApi.NEW2_HUMIDITY: {
+ ATTR_DEVICE_CLASS: SensorDeviceClass.HUMIDITY,
+ FanAttributes.ICON_MAP: {0: "mdi:water-percent"},
+ FanAttributes.LABEL: FanAttributes.HUMIDITY,
+ ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
+ FanAttributes.UNIT: PERCENTAGE,
+ },
PhilipsApi.TEMPERATURE: {
ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
FanAttributes.ICON_MAP: {
@@ -393,6 +536,31 @@ class PhilipsApi:
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
FanAttributes.UNIT: UnitOfTemperature.CELSIUS,
},
+ PhilipsApi.NEW2_TEMPERATURE: {
+ ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
+ FanAttributes.ICON_MAP: {
+ 0: "mdi:thermometer-low",
+ 17: "mdi:thermometer",
+ 23: "mdi:thermometer-high",
+ },
+ FanAttributes.LABEL: ATTR_TEMPERATURE,
+ FanAttributes.VALUE: lambda value, _: value / 10,
+ ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
+ FanAttributes.UNIT: UnitOfTemperature.CELSIUS,
+ },
+ # PhilipsApi.NEW2_FAN_SPEED: {
+ # FanAttributes.ICON_MAP: {
+ # 0: ICON.FAN_SPEED_BUTTON,
+ # 1: ICON.SPEED_1,
+ # 6: ICON.SPEED_2,
+ # 18: ICON.SPEED_3,
+ # },
+ # FanAttributes.VALUE: lambda value, _: value
+ # if int(value) < 18
+ # else FanAttributes.TURBO,
+ # FanAttributes.LABEL: FanAttributes.ACTUAL_FAN_SPEED,
+ # ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
+ # },
# diagnostic information
PhilipsApi.WATER_LEVEL: {
FanAttributes.ICON_MAP: {0: ICON.WATER_REFILL, 10: "mdi:water"},
@@ -472,6 +640,24 @@ class PhilipsApi:
FanAttributes.TOTAL: PhilipsApi.FILTER_NANOPROTECT_CLEAN_TOTAL,
FanAttributes.TYPE: "",
},
+ PhilipsApi.NEW2_FILTER_NANOPROTECT: {
+ FanAttributes.ICON_MAP: {
+ 0: ICON.FILTER_REPLACEMENT,
+ 10: ICON.NANOPROTECT_FILTER,
+ },
+ FanAttributes.LABEL: FanAttributes.FILTER_NANOPROTECT,
+ FanAttributes.TOTAL: PhilipsApi.NEW2_FILTER_NANOPROTECT_TOTAL,
+ FanAttributes.TYPE: "",
+ },
+ PhilipsApi.NEW2_FILTER_NANOPROTECT_PREFILTER: {
+ FanAttributes.ICON_MAP: {
+ 0: ICON.PREFILTER_CLEANING,
+ 10: ICON.NANOPROTECT_FILTER,
+ },
+ FanAttributes.LABEL: FanAttributes.FILTER_NANOPROTECT_CLEAN,
+ FanAttributes.TOTAL: PhilipsApi.NEW2_FILTER_NANOPROTECT_PREFILTER_TOTAL,
+ FanAttributes.TYPE: "",
+ },
}
SWITCH_TYPES: dict[str, SwitchDescription] = {
@@ -482,6 +668,41 @@ class PhilipsApi:
SWITCH_ON: True,
SWITCH_OFF: False,
},
+ PhilipsApi.NEW2_CHILD_LOCK: {
+ ATTR_ICON: ICON.CHILD_LOCK_BUTTON,
+ FanAttributes.LABEL: FanAttributes.CHILD_LOCK,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ SWITCH_ON: 1,
+ SWITCH_OFF: 0,
+ },
+ PhilipsApi.NEW2_BEEP: {
+ ATTR_ICON: "mdi:volume-high",
+ FanAttributes.LABEL: FanAttributes.BEEP,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ SWITCH_ON: 100,
+ SWITCH_OFF: 0,
+ },
+ # Oscillation is part of the fan model, so the switch is removed here
+ #
+ # PhilipsApi.NEW2_SWING: {
+ # ATTR_ICON: ICON.ROTATE,
+ # FanAttributes.LABEL: FanAttributes.SWING,
+ # CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ # SWITCH_ON: 17920,
+ # SWITCH_OFF: 0,
+ # },
+ PhilipsApi.NEW2_STANDBY_SENSORS: {
+ ATTR_ICON: "mdi:power-settings",
+ FanAttributes.LABEL: FanAttributes.STANDBY_SENSORS,
+ SWITCH_ON: 1,
+ SWITCH_OFF: 0,
+ },
+ PhilipsApi.NEW2_AUTO_PLUS_AI: {
+ ATTR_ICON: "mdi:format-annotation-plus",
+ FanAttributes.LABEL: FanAttributes.AUTO_PLUS,
+ SWITCH_ON: 1,
+ SWITCH_OFF: 0,
+ },
}
LIGHT_TYPES: dict[str, LightDescription] = {
@@ -507,6 +728,22 @@ class PhilipsApi:
SWITCH_ON: 100,
SWITCH_OFF: 0,
},
+ PhilipsApi.NEW2_DISPLAY_BACKLIGHT: {
+ ATTR_ICON: ICON.LIGHT_DIMMING_BUTTON,
+ FanAttributes.LABEL: FanAttributes.DISPLAY_BACKLIGHT,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ SWITCH_ON: 100,
+ SWITCH_OFF: 0,
+ DIMMABLE: True,
+ },
+ PhilipsApi.NEW2_DISPLAY_BACKLIGHT2: {
+ ATTR_ICON: ICON.LIGHT_DIMMING_BUTTON,
+ FanAttributes.LABEL: FanAttributes.DISPLAY_BACKLIGHT,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ SWITCH_ON: 100,
+ SWITCH_OFF: 0,
+ DIMMABLE: True,
+ },
}
SELECT_TYPES: dict[str, SelectDescription] = {
@@ -525,7 +762,12 @@ class PhilipsApi:
CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
OPTIONS: PhilipsApi.PREFERRED_INDEX_MAP,
},
- PhilipsApi.PREFERRED_INDEX: {
+ PhilipsApi.NEW_PREFERRED_INDEX: {
+ FanAttributes.LABEL: FanAttributes.PREFERRED_INDEX,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ OPTIONS: PhilipsApi.NEW_PREFERRED_INDEX_MAP,
+ },
+ PhilipsApi.NEW2_PREFERRED_INDEX: {
FanAttributes.LABEL: FanAttributes.PREFERRED_INDEX,
CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
OPTIONS: PhilipsApi.PREFERRED_INDEX_MAP,
@@ -535,9 +777,53 @@ class PhilipsApi:
CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
OPTIONS: PhilipsApi.GAS_PREFERRED_INDEX_MAP,
},
- PhilipsApi.NEW_PREFERRED_INDEX: {
+ PhilipsApi.NEW2_GAS_PREFERRED_INDEX: {
FanAttributes.LABEL: FanAttributes.PREFERRED_INDEX,
CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
- OPTIONS: PhilipsApi.NEW_PREFERRED_INDEX_MAP,
+ OPTIONS: PhilipsApi.GAS_PREFERRED_INDEX_MAP,
+ },
+ PhilipsApi.NEW2_CIRCULATION: {
+ FanAttributes.LABEL: FanAttributes.FUNCTION,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ OPTIONS: PhilipsApi.CIRCULATION_MAP,
+ },
+ PhilipsApi.NEW2_HEATING: {
+ FanAttributes.LABEL: FanAttributes.FUNCTION,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ OPTIONS: PhilipsApi.HEATING_MAP,
+ },
+ PhilipsApi.NEW2_TIMER: {
+ FanAttributes.LABEL: FanAttributes.TIMER,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ OPTIONS: PhilipsApi.TIMER_MAP,
+ },
+ PhilipsApi.NEW2_TIMER2: {
+ FanAttributes.LABEL: FanAttributes.TIMER,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ OPTIONS: PhilipsApi.TIMER2_MAP,
+ },
+}
+
+NUMBER_TYPES: dict[str, NumberDescription] = {
+ PhilipsApi.NEW2_OSCILLATION: {
+ FanAttributes.LABEL: FanAttributes.OSCILLATION,
+ ATTR_ICON: ICON.OSCILLATE,
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ FanAttributes.UNIT: "°",
+ FanAttributes.OFF: 0,
+ FanAttributes.MIN: 30,
+ FanAttributes.MAX: 350,
+ FanAttributes.STEP: 5,
+ },
+ PhilipsApi.NEW2_TARGET_TEMP: {
+ FanAttributes.LABEL: FanAttributes.TARGET_TEMP,
+ ATTR_ICON: "mdi:thermometer",
+ CONF_ENTITY_CATEGORY: EntityCategory.CONFIG,
+ ATTR_DEVICE_CLASS: NumberDeviceClass.TEMPERATURE,
+ FanAttributes.UNIT: "°C",
+ FanAttributes.OFF: 1,
+ FanAttributes.MIN: 1,
+ FanAttributes.MAX: 37,
+ FanAttributes.STEP: 1,
},
}
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/circulate.svg b/custom_components/philips_airpurifier_coap/icons/pap/circulate.svg
new file mode 100644
index 0000000..e2cc318
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/circulate.svg
@@ -0,0 +1,4 @@
+
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/clean.svg b/custom_components/philips_airpurifier_coap/icons/pap/clean.svg
new file mode 100644
index 0000000..ade5e26
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/clean.svg
@@ -0,0 +1,4 @@
+
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/gas.svg b/custom_components/philips_airpurifier_coap/icons/pap/gas.svg
new file mode 100644
index 0000000..1aa23ec
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/gas.svg
@@ -0,0 +1,4 @@
+
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/heating.svg b/custom_components/philips_airpurifier_coap/icons/pap/heating.svg
new file mode 100644
index 0000000..d161a9e
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/heating.svg
@@ -0,0 +1,4 @@
+
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/mode.svg b/custom_components/philips_airpurifier_coap/icons/pap/mode.svg
new file mode 100644
index 0000000..7095a29
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/mode.svg
@@ -0,0 +1,4 @@
+
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/oscillate.svg b/custom_components/philips_airpurifier_coap/icons/pap/oscillate.svg
new file mode 100644
index 0000000..ca7f6d3
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/oscillate.svg
@@ -0,0 +1,4 @@
+
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/pm25b.svg b/custom_components/philips_airpurifier_coap/icons/pap/pm25b.svg
new file mode 100644
index 0000000..b7acf73
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/pm25b.svg
@@ -0,0 +1,4 @@
+
diff --git a/custom_components/philips_airpurifier_coap/icons/pap/rotate.svg b/custom_components/philips_airpurifier_coap/icons/pap/rotate.svg
new file mode 100644
index 0000000..bf213ce
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/icons/pap/rotate.svg
@@ -0,0 +1,7 @@
+
diff --git a/custom_components/philips_airpurifier_coap/manifest.json b/custom_components/philips_airpurifier_coap/manifest.json
index bac990c..44fd951 100644
--- a/custom_components/philips_airpurifier_coap/manifest.json
+++ b/custom_components/philips_airpurifier_coap/manifest.json
@@ -1,23 +1,26 @@
{
- "domain": "philips_airpurifier_coap",
- "name": "Philips AirPurifier (with CoAP)",
- "codeowners": ["@kongo09"],
- "config_flow": true,
- "dependencies": ["frontend", "http"],
- "dhcp": [
- {
- "macaddress": "B0F893*"
- },
- {
- "macaddress": "047863*"
- },
- {
- "hostname": "mxchip*"
- }
- ],
- "documentation": "https://github.com/kongo09/philips-airpurifier-coap",
- "iot_class": "local_push",
- "issue_tracker": "https://github.com/kongo09/philips-airpurifier-coap/issues",
- "requirements": ["aioairctrl==0.2.5"],
- "version": "0.10.9"
+ "domain": "philips_airpurifier_coap",
+ "name": "Philips AirPurifier (with CoAP)",
+ "codeowners": ["@kongo09"],
+ "config_flow": true,
+ "dependencies": ["frontend", "http"],
+ "dhcp": [
+ {
+ "macaddress": "B0F893*"
+ },
+ {
+ "macaddress": "047863*"
+ },
+ {
+ "macaddress": "849DC2*"
+ },
+ {
+ "hostname": "mxchip*"
+ }
+ ],
+ "documentation": "https://github.com/kongo09/philips-airpurifier-coap",
+ "iot_class": "local_push",
+ "issue_tracker": "https://github.com/kongo09/philips-airpurifier-coap/issues",
+ "requirements": ["aioairctrl==0.2.5"],
+ "version": "0.10.9"
}
diff --git a/custom_components/philips_airpurifier_coap/model.py b/custom_components/philips_airpurifier_coap/model.py
index 069cefc..b43a0f4 100644
--- a/custom_components/philips_airpurifier_coap/model.py
+++ b/custom_components/philips_airpurifier_coap/model.py
@@ -65,3 +65,16 @@ class SelectDescription(TypedDict):
label: str
entity_category: str
options: dict[Any, tuple[str, str]]
+
+
+class NumberDescription(TypedDict):
+ """Number class."""
+
+ icon: str
+ label: str
+ entity_category: str
+ unit: str
+ off: int
+ min: int
+ max: int
+ step: int
diff --git a/custom_components/philips_airpurifier_coap/number.py b/custom_components/philips_airpurifier_coap/number.py
new file mode 100644
index 0000000..2ee11a8
--- /dev/null
+++ b/custom_components/philips_airpurifier_coap/number.py
@@ -0,0 +1,128 @@
+"""Philips Air Purifier & Humidifier Numbers."""
+from __future__ import annotations
+
+from collections.abc import Callable
+import logging
+from typing import Any
+
+from homeassistant.components.number import NumberEntity
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import (
+ ATTR_DEVICE_CLASS,
+ ATTR_ICON,
+ CONF_ENTITY_CATEGORY,
+ CONF_HOST,
+ CONF_NAME,
+)
+from homeassistant.core import HomeAssistant
+from homeassistant.exceptions import PlatformNotReady
+from homeassistant.helpers.entity import Entity
+
+from .const import (
+ CONF_MODEL,
+ DATA_KEY_COORDINATOR,
+ DOMAIN,
+ NUMBER_TYPES,
+ FanAttributes,
+ PhilipsApi,
+)
+from .philips import Coordinator, PhilipsEntity, model_to_class
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(
+ hass: HomeAssistant,
+ entry: ConfigEntry,
+ async_add_entities: Callable[[list[Entity], bool], None],
+) -> None:
+ """Set up the number platform."""
+ _LOGGER.debug("async_setup_entry called for platform number")
+
+ host = entry.data[CONF_HOST]
+ model = entry.data[CONF_MODEL]
+ name = entry.data[CONF_NAME]
+
+ data = hass.data[DOMAIN][host]
+
+ coordinator = data[DATA_KEY_COORDINATOR]
+
+ model_class = model_to_class.get(model)
+ if model_class:
+ available_numbers = []
+
+ for cls in reversed(model_class.__mro__):
+ cls_available_numbers = getattr(cls, "AVAILABLE_NUMBERS", [])
+ available_numbers.extend(cls_available_numbers)
+
+ numbers = []
+
+ for number in NUMBER_TYPES:
+ if number in available_numbers:
+ numbers.append(PhilipsNumber(coordinator, name, model, number))
+
+ async_add_entities(numbers, update_before_add=False)
+
+ else:
+ _LOGGER.error("Unsupported model: %s", model)
+ return
+
+
+class PhilipsNumber(PhilipsEntity, NumberEntity):
+ """Define a Philips AirPurifier number."""
+
+ def __init__( # noqa: D107
+ self, coordinator: Coordinator, name: str, model: str, number: str
+ ) -> None:
+ super().__init__(coordinator)
+ self._model = model
+ self._description = NUMBER_TYPES[number]
+ self._attr_device_class = self._description.get(ATTR_DEVICE_CLASS)
+ label = FanAttributes.LABEL
+ label = label.partition("#")[0]
+ self._attr_name = f"{name} {self._description[label].replace('_', ' ').title()}"
+ self._attr_entity_category = self._description.get(CONF_ENTITY_CATEGORY)
+ self._attr_icon = self._description.get(ATTR_ICON)
+ self._attr_mode = "slider" # hardwired for now
+ self._attr_native_unit_of_measurement = self._description.get(
+ FanAttributes.UNIT
+ )
+
+ self._attr_native_min_value = self._description.get(FanAttributes.OFF)
+ self._min = self._description.get(FanAttributes.MIN)
+ self._attr_native_max_value = self._description.get(FanAttributes.MAX)
+ self._attr_native_step = self._description.get(FanAttributes.STEP)
+
+ try:
+ device_id = self._device_status[PhilipsApi.DEVICE_ID]
+ self._attr_unique_id = f"{self._model}-{device_id}-{number.lower()}"
+ except Exception as e:
+ _LOGGER.error("Failed retrieving unique_id: %s", e)
+ raise PlatformNotReady
+ self._attrs: dict[str, Any] = {}
+ self.kind = number
+
+ @property
+ def native_value(self) -> float | None:
+ """Return the current number."""
+ value = self._device_status.get(self.kind)
+ return value
+
+ async def async_set_native_value(self, value: float) -> None:
+ """Select a number."""
+
+ _LOGGER.debug("async_set_native_value called with: %s", value)
+
+ # Catch the boundaries
+ if value is None or value < self._attr_native_min_value:
+ value = self._attr_native_min_value
+ if value % self._attr_native_step > 0:
+ value = value // self._attr_native_step * self._attr_native_step
+ if value > 0 and value < self._min:
+ value = self._min
+ if value > self._attr_native_max_value:
+ value = self._attr_native_max_value
+
+ _LOGGER.debug("setting number with: %s", value)
+
+ await self.coordinator.client.set_control_value(self.kind, int(value))
diff --git a/custom_components/philips_airpurifier_coap/philips.py b/custom_components/philips_airpurifier_coap/philips.py
index 842c353..ae6f9fc 100644
--- a/custom_components/philips_airpurifier_coap/philips.py
+++ b/custom_components/philips_airpurifier_coap/philips.py
@@ -23,6 +23,7 @@
from .const import (
DOMAIN,
ICON,
+ SWITCH_OFF,
SWITCH_ON,
FanAttributes,
FanModel,
@@ -183,7 +184,10 @@ def __init__(self, coordinator: Coordinator) -> None: # noqa: D107
self._name = list(
filter(
None,
- map(coordinator.status.get, [PhilipsApi.NAME, PhilipsApi.NEW_NAME]),
+ map(
+ coordinator.status.get,
+ [PhilipsApi.NAME, PhilipsApi.NEW_NAME, PhilipsApi.NEW2_NAME],
+ ),
)
)[0]
# self._modelName = coordinator.status["modelid"]
@@ -192,7 +196,11 @@ def __init__(self, coordinator: Coordinator) -> None: # noqa: D107
None,
map(
coordinator.status.get,
- [PhilipsApi.MODEL_ID, PhilipsApi.NEW_MODEL_ID],
+ [
+ PhilipsApi.MODEL_ID,
+ PhilipsApi.NEW_MODEL_ID,
+ PhilipsApi.NEW2_MODEL_ID,
+ ],
),
)
)[0]
@@ -272,15 +280,20 @@ class PhilipsGenericCoAPFanBase(PhilipsGenericFan):
"""Class as basis to manage a generic Philips CoAP fan."""
AVAILABLE_PRESET_MODES = {}
+ REPLACE_PRESET = None
AVAILABLE_SPEEDS = {}
+ REPLACE_SPEED = None
AVAILABLE_ATTRIBUTES = []
AVAILABLE_SWITCHES = []
AVAILABLE_LIGHTS = []
+ AVAILABLE_NUMBERS = []
KEY_PHILIPS_POWER = PhilipsApi.POWER
STATE_POWER_ON = "1"
STATE_POWER_OFF = "0"
+ KEY_OSCILLATION = None
+
def __init__( # noqa: D107
self,
coordinator: Coordinator,
@@ -366,6 +379,8 @@ def supported_features(self) -> int:
features = FanEntityFeature.PRESET_MODE
if self._speeds:
features |= FanEntityFeature.SET_SPEED
+ if self.KEY_OSCILLATION is not None:
+ features |= FanEntityFeature.OSCILLATE
return features
@property
@@ -378,7 +393,11 @@ def preset_mode(self) -> Optional[str]:
"""Return the selected preset mode."""
for preset_mode, status_pattern in self._available_preset_modes.items():
for k, v in status_pattern.items():
- if self._device_status.get(k) != v:
+ # check if the speed sensor also used for presets is different from the setting field
+ if self.REPLACE_PRESET is not None and k == self.REPLACE_PRESET[0]:
+ k = self.REPLACE_PRESET[1]
+ status = self._device_status.get(k)
+ if status != v:
break
else:
return preset_mode
@@ -394,16 +413,46 @@ def speed_count(self) -> int:
"""Return the number of speed options."""
return len(self._speeds)
+ @property
+ def oscillating(self) -> bool | None:
+ """Return if the fan is oscillating."""
+ if self.KEY_OSCILLATION is None:
+ return None
+
+ key = next(iter(self.KEY_OSCILLATION))
+ status = self._device_status.get(key)
+ on = self.KEY_OSCILLATION.get(key).get(SWITCH_ON)
+ return status is not None and status == on
+
+ async def async_oscillate(self, oscillating: bool) -> None:
+ """Osciallate the fan."""
+ if self.KEY_OSCILLATION is None:
+ return None
+
+ key = next(iter(self.KEY_OSCILLATION))
+ values = self.KEY_OSCILLATION.get(key)
+ on = values.get(SWITCH_ON)
+ off = values.get(SWITCH_OFF)
+ if oscillating:
+ await self.coordinator.client.set_control_value(key, on)
+ else:
+ await self.coordinator.client.set_control_value(key, off)
+
@property
def percentage(self) -> Optional[int]:
"""Return the speed percentages."""
for speed, status_pattern in self._available_speeds.items():
for k, v in status_pattern.items():
+ # check if the speed sensor is different from the speed setting field
+ if self.REPLACE_SPEED is not None and k == self.REPLACE_SPEED[0]:
+ k = self.REPLACE_SPEED[1]
if self._device_status.get(k) != v:
break
else:
return ordered_list_item_to_percentage(self._speeds, speed)
+ return None
+
async def async_set_percentage(self, percentage: int) -> None:
"""Return the selected speed percentage."""
if percentage == 0:
@@ -537,6 +586,45 @@ class PhilipsNewGenericCoAPFan(PhilipsGenericCoAPFanBase):
STATE_POWER_OFF = "OFF"
+class PhilipsNew2GenericCoAPFan(PhilipsGenericCoAPFanBase):
+ """Class to manage another new generic CoAP fan."""
+
+ AVAILABLE_PRESET_MODES = {}
+ AVAILABLE_SPEEDS = {}
+
+ AVAILABLE_ATTRIBUTES = [
+ # device information
+ (FanAttributes.NAME, PhilipsApi.NEW2_NAME),
+ (FanAttributes.MODEL_ID, PhilipsApi.NEW2_MODEL_ID),
+ (FanAttributes.PRODUCT_ID, PhilipsApi.PRODUCT_ID),
+ (FanAttributes.DEVICE_ID, PhilipsApi.DEVICE_ID),
+ (FanAttributes.SOFTWARE_VERSION, PhilipsApi.NEW2_SOFTWARE_VERSION),
+ (FanAttributes.WIFI_VERSION, PhilipsApi.WIFI_VERSION),
+ # (FanAttributes.ERROR_CODE, PhilipsApi.ERROR_CODE),
+ # (FanAttributes.ERROR, PhilipsApi.ERROR_CODE, PhilipsApi.ERROR_CODE_MAP),
+ # device configuration
+ (
+ FanAttributes.PREFERRED_INDEX,
+ PhilipsApi.NEW2_GAS_PREFERRED_INDEX,
+ PhilipsApi.GAS_PREFERRED_INDEX_MAP,
+ ),
+ # device sensors
+ (
+ FanAttributes.RUNTIME,
+ PhilipsApi.RUNTIME,
+ lambda x, _: str(timedelta(seconds=round(x / 1000))),
+ ),
+ ]
+
+ AVAILABLE_LIGHTS = []
+ AVAILABLE_SWITCHES = []
+ AVAILABLE_SELECTS = []
+
+ KEY_PHILIPS_POWER = PhilipsApi.NEW2_POWER
+ STATE_POWER_ON = 1
+ STATE_POWER_OFF = 0
+
+
class PhilipsHumidifierMixin(PhilipsGenericCoAPFanBase):
"""Mixin for humidifiers."""
@@ -1310,6 +1398,10 @@ class PhilipsAC385851(PhilipsAC385x51):
"""AC3858/51."""
+class PhilipsAC385886(PhilipsAC385x51):
+ """AC3858/86."""
+
+
class PhilipsAC4236(PhilipsGenericCoAPFan):
"""AC4236."""
@@ -1468,6 +1560,168 @@ class PhilipsAC5659(PhilipsGenericCoAPFan):
AVAILABLE_SELECTS = [PhilipsApi.PREFERRED_INDEX]
+class PhilipsAMFxxx(PhilipsNew2GenericCoAPFan):
+ """AMF family."""
+
+ # REPLACE_PRESET = [PhilipsApi.NEW2_MODE_B, PhilipsApi.NEW2_FAN_SPEED]
+ AVAILABLE_PRESET_MODES = {
+ # PresetMode.AUTO_PLUS: {
+ # PhilipsApi.NEW2_POWER: 1,
+ # PhilipsApi.NEW2_MODE_B: 0,
+ # PhilipsApi.NEW2_AUTO_PLUS_AI: 1,
+ # # PhilipsApi.NEW2_MODE_C: 3,
+ # },
+ PresetMode.AUTO: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 0,
+ # PhilipsApi.NEW2_AUTO_PLUS_AI: 0,
+ # PhilipsApi.NEW2_MODE_C: 3,
+ },
+ PresetMode.SLEEP: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 17,
+ # PhilipsApi.NEW2_MODE_C: 1,
+ },
+ PresetMode.TURBO: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 18,
+ # PhilipsApi.NEW2_MODE_C: 18,
+ },
+ }
+ # REPLACE_SPEED = [PhilipsApi.NEW2_MODE_B, PhilipsApi.NEW2_FAN_SPEED]
+ AVAILABLE_SPEEDS = {
+ PresetMode.SPEED_1: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 1,
+ # PhilipsApi.NEW2_MODE_C: 1,
+ },
+ PresetMode.SPEED_2: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 2,
+ # PhilipsApi.NEW2_MODE_C: 2,
+ },
+ PresetMode.SPEED_3: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 3,
+ # PhilipsApi.NEW2_MODE_C: 3,
+ },
+ PresetMode.SPEED_4: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 4,
+ # PhilipsApi.NEW2_MODE_C: 4,
+ },
+ PresetMode.SPEED_5: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 5,
+ # PhilipsApi.NEW2_MODE_C: 5,
+ },
+ PresetMode.SPEED_6: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 6,
+ # PhilipsApi.NEW2_MODE_C: 6,
+ },
+ PresetMode.SPEED_7: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 7,
+ # PhilipsApi.NEW2_MODE_C: 7,
+ },
+ PresetMode.SPEED_8: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 8,
+ # PhilipsApi.NEW2_MODE_C: 8,
+ },
+ PresetMode.SPEED_9: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 9,
+ # PhilipsApi.NEW2_MODE_C: 9,
+ },
+ PresetMode.SPEED_10: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_B: 10,
+ # PhilipsApi.NEW2_MODE_C: 10,
+ },
+ # PresetMode.TURBO: {
+ # PhilipsApi.NEW2_POWER: 1,
+ # PhilipsApi.NEW2_MODE_B: 18,
+ # },
+ }
+
+ AVAILABLE_LIGHTS = [PhilipsApi.NEW2_DISPLAY_BACKLIGHT]
+ AVAILABLE_SWITCHES = [
+ PhilipsApi.NEW2_CHILD_LOCK,
+ PhilipsApi.NEW2_BEEP,
+ PhilipsApi.NEW2_STANDBY_SENSORS,
+ PhilipsApi.NEW2_AUTO_PLUS_AI,
+ ]
+ AVAILABLE_SELECTS = [PhilipsApi.NEW2_TIMER]
+ AVAILABLE_NUMBERS = [PhilipsApi.NEW2_OSCILLATION]
+
+
+class PhilipsAMF765(PhilipsAMFxxx):
+ """AMF765."""
+
+ AVAILABLE_SELECTS = [PhilipsApi.NEW2_CIRCULATION]
+ UNAVAILABLE_SENSORS = [PhilipsApi.NEW2_GAS]
+
+
+class PhilipsAMF870(PhilipsAMFxxx):
+ """AMF870."""
+
+ AVAILABLE_SELECTS = [
+ PhilipsApi.NEW2_GAS_PREFERRED_INDEX,
+ PhilipsApi.NEW2_HEATING,
+ ]
+ AVAILABLE_NUMBERS = [PhilipsApi.NEW2_TARGET_TEMP]
+
+
+class PhilipsCX5120(PhilipsNew2GenericCoAPFan):
+ """CX5120."""
+
+ AVAILABLE_PRESET_MODES = {
+ PresetMode.AUTO: {
+ PhilipsApi.NEW2_POWER: 1,
+ PhilipsApi.NEW2_MODE_A: 3,
+ PhilipsApi.NEW2_MODE_B: 0,
+ },
+ PresetMode.HIGH: {
+ PhilipsApi.POWER: 1,
+ PhilipsApi.NEW2_MODE_A: 3,
+ PhilipsApi.NEW2_MODE_B: 65,
+ },
+ PresetMode.LOW: {
+ PhilipsApi.POWER: 1,
+ PhilipsApi.NEW2_MODE_A: 3,
+ PhilipsApi.NEW2_MODE_B: 66,
+ },
+ PresetMode.VENTILATION: {
+ PhilipsApi.POWER: 1,
+ PhilipsApi.NEW2_MODE_A: 1,
+ PhilipsApi.NEW2_MODE_B: -127,
+ },
+ }
+ AVAILABLE_SPEEDS = {
+ PresetMode.HIGH: {
+ PhilipsApi.POWER: 1,
+ PhilipsApi.NEW2_MODE_A: 3,
+ PhilipsApi.NEW2_MODE_B: 65,
+ },
+ PresetMode.LOW: {
+ PhilipsApi.POWER: 1,
+ PhilipsApi.NEW2_MODE_A: 3,
+ PhilipsApi.NEW2_MODE_B: 66,
+ },
+ }
+ KEY_OSCILLATION = {
+ PhilipsApi.NEW2_OSCILLATION: PhilipsApi.OSCILLATION_MAP,
+ }
+
+ AVAILABLE_LIGHTS = [PhilipsApi.NEW2_DISPLAY_BACKLIGHT2]
+ AVAILABLE_SWITCHES = [PhilipsApi.NEW2_BEEP]
+ UNAVAILABLE_SENSORS = [PhilipsApi.NEW2_FAN_SPEED, PhilipsApi.NEW2_GAS]
+ AVAILABLE_SELECTS = [PhilipsApi.NEW2_TIMER2]
+ AVAILABLE_NUMBERS = [PhilipsApi.NEW2_TARGET_TEMP]
+
+
model_to_class = {
FanModel.AC0850: PhilipsAC0850,
FanModel.AC1214: PhilipsAC1214,
@@ -1490,8 +1744,12 @@ class PhilipsAC5659(PhilipsGenericCoAPFan):
FanModel.AC3854_51: PhilipsAC385451,
FanModel.AC3858_50: PhilipsAC385850,
FanModel.AC3858_51: PhilipsAC385851,
+ FanModel.AC3858_86: PhilipsAC385886,
FanModel.AC4236: PhilipsAC4236,
FanModel.AC4550: PhilipsAC4550,
FanModel.AC4558: PhilipsAC4558,
FanModel.AC5659: PhilipsAC5659,
+ FanModel.AMF765: PhilipsAMF765,
+ FanModel.AMF870: PhilipsAMF870,
+ FanModel.CX5120: PhilipsCX5120,
}
diff --git a/custom_components/philips_airpurifier_coap/sensor.py b/custom_components/philips_airpurifier_coap/sensor.py
index 5709a42..ca05c88 100644
--- a/custom_components/philips_airpurifier_coap/sensor.py
+++ b/custom_components/philips_airpurifier_coap/sensor.py
@@ -51,19 +51,22 @@ async def async_setup_entry( # noqa: D103
coordinator = data[DATA_KEY_COORDINATOR]
status = coordinator.status
- sensors = []
-
- for sensor in SENSOR_TYPES:
- if sensor in status:
- sensors.append(PhilipsSensor(coordinator, name, model, sensor))
-
model_class = model_to_class.get(model)
unavailable_filters = []
+ unavailable_sensors = []
if model_class:
for cls in reversed(model_class.__mro__):
cls_unavailable_filters = getattr(cls, "UNAVAILABLE_FILTERS", [])
unavailable_filters.extend(cls_unavailable_filters)
+ cls_unavailable_sensors = getattr(cls, "UNAVAILABLE_SENSORS", [])
+ unavailable_sensors.extend(cls_unavailable_sensors)
+
+ sensors = []
+
+ for sensor in SENSOR_TYPES:
+ if sensor in status and sensor not in unavailable_sensors:
+ sensors.append(PhilipsSensor(coordinator, name, model, sensor))
for _filter in FILTER_TYPES:
if _filter in status and _filter not in unavailable_filters: