1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-18 07:56:03 +01:00

Add fan speed percentage control to SwitchBot Air Purifier (#166953)

This commit is contained in:
Retha Runolfsson
2026-04-01 22:08:40 +08:00
committed by GitHub
parent 783e2f0a00
commit c1a9f293a7
6 changed files with 75 additions and 38 deletions

View File

@@ -114,7 +114,10 @@ PLATFORMS_BY_TYPE = {
SupportedModels.AIR_PURIFIER_US.value: [Platform.FAN, Platform.SENSOR], SupportedModels.AIR_PURIFIER_US.value: [Platform.FAN, Platform.SENSOR],
SupportedModels.AIR_PURIFIER_TABLE_JP.value: [Platform.FAN, Platform.SENSOR], SupportedModels.AIR_PURIFIER_TABLE_JP.value: [Platform.FAN, Platform.SENSOR],
SupportedModels.AIR_PURIFIER_TABLE_US.value: [Platform.FAN, Platform.SENSOR], SupportedModels.AIR_PURIFIER_TABLE_US.value: [Platform.FAN, Platform.SENSOR],
SupportedModels.EVAPORATIVE_HUMIDIFIER: [Platform.HUMIDIFIER, Platform.SENSOR], SupportedModels.EVAPORATIVE_HUMIDIFIER.value: [
Platform.HUMIDIFIER,
Platform.SENSOR,
],
SupportedModels.FLOOR_LAMP.value: [Platform.LIGHT, Platform.SENSOR], SupportedModels.FLOOR_LAMP.value: [Platform.LIGHT, Platform.SENSOR],
SupportedModels.STRIP_LIGHT_3.value: [Platform.LIGHT, Platform.SENSOR], SupportedModels.STRIP_LIGHT_3.value: [Platform.LIGHT, Platform.SENSOR],
SupportedModels.RGBICWW_FLOOR_LAMP.value: [Platform.LIGHT, Platform.SENSOR], SupportedModels.RGBICWW_FLOOR_LAMP.value: [Platform.LIGHT, Platform.SENSOR],
@@ -171,7 +174,7 @@ CLASS_BY_DEVICE = {
SupportedModels.AIR_PURIFIER_US.value: switchbot.SwitchbotAirPurifier, SupportedModels.AIR_PURIFIER_US.value: switchbot.SwitchbotAirPurifier,
SupportedModels.AIR_PURIFIER_TABLE_JP.value: switchbot.SwitchbotAirPurifier, SupportedModels.AIR_PURIFIER_TABLE_JP.value: switchbot.SwitchbotAirPurifier,
SupportedModels.AIR_PURIFIER_TABLE_US.value: switchbot.SwitchbotAirPurifier, SupportedModels.AIR_PURIFIER_TABLE_US.value: switchbot.SwitchbotAirPurifier,
SupportedModels.EVAPORATIVE_HUMIDIFIER: switchbot.SwitchbotEvaporativeHumidifier, SupportedModels.EVAPORATIVE_HUMIDIFIER.value: switchbot.SwitchbotEvaporativeHumidifier,
SupportedModels.FLOOR_LAMP.value: switchbot.SwitchbotStripLight3, SupportedModels.FLOOR_LAMP.value: switchbot.SwitchbotStripLight3,
SupportedModels.STRIP_LIGHT_3.value: switchbot.SwitchbotStripLight3, SupportedModels.STRIP_LIGHT_3.value: switchbot.SwitchbotStripLight3,
SupportedModels.RGBICWW_FLOOR_LAMP.value: switchbot.SwitchbotRgbicLight, SupportedModels.RGBICWW_FLOOR_LAMP.value: switchbot.SwitchbotRgbicLight,

View File

@@ -206,3 +206,16 @@ CONF_KEY_ID = "key_id"
CONF_ENCRYPTION_KEY = "encryption_key" CONF_ENCRYPTION_KEY = "encryption_key"
CONF_LOCK_NIGHTLATCH = "lock_force_nightlatch" CONF_LOCK_NIGHTLATCH = "lock_force_nightlatch"
CONF_CURTAIN_SPEED = "curtain_speed" CONF_CURTAIN_SPEED = "curtain_speed"
AIRPURIFIER_BASIC_MODELS = {
SwitchbotModel.AIR_PURIFIER_JP,
SwitchbotModel.AIR_PURIFIER_US,
}
AIRPURIFIER_TABLE_MODELS = {
SwitchbotModel.AIR_PURIFIER_TABLE_JP,
SwitchbotModel.AIR_PURIFIER_TABLE_US,
}
AIRPURIFIER_PM25_MODELS = {
SwitchbotModel.AIR_PURIFIER_US,
SwitchbotModel.AIR_PURIFIER_TABLE_US,
}

View File

@@ -131,6 +131,7 @@ class SwitchBotAirPurifierEntity(SwitchbotEntity, FanEntity):
_device: switchbot.SwitchbotAirPurifier _device: switchbot.SwitchbotAirPurifier
_attr_supported_features = ( _attr_supported_features = (
FanEntityFeature.PRESET_MODE FanEntityFeature.PRESET_MODE
| FanEntityFeature.SET_SPEED
| FanEntityFeature.TURN_OFF | FanEntityFeature.TURN_OFF
| FanEntityFeature.TURN_ON | FanEntityFeature.TURN_ON
) )
@@ -148,6 +149,11 @@ class SwitchBotAirPurifierEntity(SwitchbotEntity, FanEntity):
"""Return the current preset mode.""" """Return the current preset mode."""
return self._device.get_current_mode() return self._device.get_current_mode()
@property
def percentage(self) -> int | None:
"""Return the speed percentage of the air purifier."""
return self._device.get_current_percentage()
@exception_handler @exception_handler
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the air purifier.""" """Set the preset mode of the air purifier."""
@@ -160,6 +166,16 @@ class SwitchBotAirPurifierEntity(SwitchbotEntity, FanEntity):
self._last_run_success = bool(await self._device.set_preset_mode(preset_mode)) self._last_run_success = bool(await self._device.set_preset_mode(preset_mode))
self.async_write_ha_state() self.async_write_ha_state()
@exception_handler
async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the air purifier."""
_LOGGER.debug(
"Switchbot air purifier to set percentage %d %s", percentage, self._address
)
await self._device.set_percentage(percentage)
self.async_write_ha_state()
@exception_handler @exception_handler
async def async_turn_on( async def async_turn_on(
self, self,

View File

@@ -785,8 +785,8 @@ LOCK_ULTRA_SERVICE_INFO = BluetoothServiceInfoBleak(
) )
AIR_PURIFIER_TABLE_PM25_SERVICE_INFO = BluetoothServiceInfoBleak( AIR_PURIFIER_TABLE_US_SERVICE_INFO = BluetoothServiceInfoBleak(
name="Air Purifier Table PM25", name="Air Purifier Table US",
manufacturer_data={ manufacturer_data={
2409: b"\xf0\x9e\x9e\x96j\xd6\xa1\x81\x88\xe4\x00\x01\x95\x00\x00", 2409: b"\xf0\x9e\x9e\x96j\xd6\xa1\x81\x88\xe4\x00\x01\x95\x00\x00",
}, },
@@ -796,22 +796,22 @@ AIR_PURIFIER_TABLE_PM25_SERVICE_INFO = BluetoothServiceInfoBleak(
rssi=-60, rssi=-60,
source="local", source="local",
advertisement=generate_advertisement_data( advertisement=generate_advertisement_data(
local_name="Air Purifier Table PM25", local_name="Air Purifier Table US",
manufacturer_data={ manufacturer_data={
2409: b"\xf0\x9e\x9e\x96j\xd6\xa1\x81\x88\xe4\x00\x01\x95\x00\x00", 2409: b"\xf0\x9e\x9e\x96j\xd6\xa1\x81\x88\xe4\x00\x01\x95\x00\x00",
}, },
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"7\x00\x00\x95-\x00"}, service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"7\x00\x00\x95-\x00"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"], service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
), ),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier Table PM25"), device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier Table US"),
time=0, time=0,
connectable=True, connectable=True,
tx_power=-127, tx_power=-127,
) )
AIR_PURIFIER_PM25_SERVICE_INFO = BluetoothServiceInfoBleak( AIR_PURIFIER_US_SERVICE_INFO = BluetoothServiceInfoBleak(
name="Air Purifier PM25", name="Air Purifier US",
manufacturer_data={ manufacturer_data={
2409: b'\xcc\x8d\xa2\xa7\x92>\t"\x80\x000\x00\x0f\x00\x00', 2409: b'\xcc\x8d\xa2\xa7\x92>\t"\x80\x000\x00\x0f\x00\x00',
}, },
@@ -821,22 +821,22 @@ AIR_PURIFIER_PM25_SERVICE_INFO = BluetoothServiceInfoBleak(
rssi=-60, rssi=-60,
source="local", source="local",
advertisement=generate_advertisement_data( advertisement=generate_advertisement_data(
local_name="Air Purifier PM25", local_name="Air Purifier US",
manufacturer_data={ manufacturer_data={
2409: b'\xcc\x8d\xa2\xa7\x92>\t"\x80\x000\x00\x0f\x00\x00', 2409: b'\xcc\x8d\xa2\xa7\x92>\t"\x80\x000\x00\x0f\x00\x00',
}, },
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"*\x00\x00\x15\x04\x00"}, service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"*\x00\x00\x15\x04\x00"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"], service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
), ),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier PM25"), device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier US"),
time=0, time=0,
connectable=True, connectable=True,
tx_power=-127, tx_power=-127,
) )
AIR_PURIFIER_VOC_SERVICE_INFO = BluetoothServiceInfoBleak( AIR_PURIFIER_JP_SERVICE_INFO = BluetoothServiceInfoBleak(
name="Air Purifier VOC", name="Air Purifier JP",
manufacturer_data={ manufacturer_data={
2409: b"\xcc\x8d\xa2\xa7\xe4\xa6\x0b\x83\x88d\x00\xea`\x00\x00", 2409: b"\xcc\x8d\xa2\xa7\xe4\xa6\x0b\x83\x88d\x00\xea`\x00\x00",
}, },
@@ -846,22 +846,22 @@ AIR_PURIFIER_VOC_SERVICE_INFO = BluetoothServiceInfoBleak(
rssi=-60, rssi=-60,
source="local", source="local",
advertisement=generate_advertisement_data( advertisement=generate_advertisement_data(
local_name="Air Purifier VOC", local_name="Air Purifier JP",
manufacturer_data={ manufacturer_data={
2409: b"\xcc\x8d\xa2\xa7\xe4\xa6\x0b\x83\x88d\x00\xea`\x00\x00", 2409: b"\xcc\x8d\xa2\xa7\xe4\xa6\x0b\x83\x88d\x00\xea`\x00\x00",
}, },
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"+\x00\x00\x15\x04\x00"}, service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"+\x00\x00\x15\x04\x00"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"], service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
), ),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier VOC"), device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier JP"),
time=0, time=0,
connectable=True, connectable=True,
tx_power=-127, tx_power=-127,
) )
AIR_PURIFIER_TABLE_VOC_SERVICE_INFO = BluetoothServiceInfoBleak( AIR_PURIFIER_TABLE_JP_SERVICE_INFO = BluetoothServiceInfoBleak(
name="Air Purifier Table VOC", name="Air Purifier Table JP",
manufacturer_data={ manufacturer_data={
2409: b"\xcc\x8d\xa2\xa7\xc1\xae\x9b\x81\x8c\xb2\x00\x01\x94\x00\x00", 2409: b"\xcc\x8d\xa2\xa7\xc1\xae\x9b\x81\x8c\xb2\x00\x01\x94\x00\x00",
}, },
@@ -871,14 +871,14 @@ AIR_PURIFIER_TABLE_VOC_SERVICE_INFO = BluetoothServiceInfoBleak(
rssi=-60, rssi=-60,
source="local", source="local",
advertisement=generate_advertisement_data( advertisement=generate_advertisement_data(
local_name="Air Purifier Table VOC", local_name="Air Purifier Table JP",
manufacturer_data={ manufacturer_data={
2409: b"\xcc\x8d\xa2\xa7\xc1\xae\x9b\x81\x8c\xb2\x00\x01\x94\x00\x00", 2409: b"\xcc\x8d\xa2\xa7\xc1\xae\x9b\x81\x8c\xb2\x00\x01\x94\x00\x00",
}, },
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"8\x00\x00\x95-\x00"}, service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"8\x00\x00\x95-\x00"},
service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"], service_uuids=["cba20d00-224d-11e6-9fb8-0002a5d5c51b"],
), ),
device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier Table VOC"), device=generate_ble_device("AA:BB:CC:DD:EE:FF", "Air Purifier Table JP"),
time=0, time=0,
connectable=True, connectable=True,
tx_power=-127, tx_power=-127,

View File

@@ -21,10 +21,10 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from . import ( from . import (
AIR_PURIFIER_PM25_SERVICE_INFO, AIR_PURIFIER_JP_SERVICE_INFO,
AIR_PURIFIER_TABLE_PM25_SERVICE_INFO, AIR_PURIFIER_TABLE_JP_SERVICE_INFO,
AIR_PURIFIER_TABLE_VOC_SERVICE_INFO, AIR_PURIFIER_TABLE_US_SERVICE_INFO,
AIR_PURIFIER_VOC_SERVICE_INFO, AIR_PURIFIER_US_SERVICE_INFO,
CIRCULATOR_FAN_SERVICE_INFO, CIRCULATOR_FAN_SERVICE_INFO,
) )
@@ -103,10 +103,10 @@ async def test_circulator_fan_controlling(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service_info", "sensor_type"), ("service_info", "sensor_type"),
[ [
(AIR_PURIFIER_VOC_SERVICE_INFO, "air_purifier_jp"), (AIR_PURIFIER_JP_SERVICE_INFO, "air_purifier_jp"),
(AIR_PURIFIER_TABLE_VOC_SERVICE_INFO, "air_purifier_table_jp"), (AIR_PURIFIER_TABLE_JP_SERVICE_INFO, "air_purifier_table_jp"),
(AIR_PURIFIER_PM25_SERVICE_INFO, "air_purifier_us"), (AIR_PURIFIER_US_SERVICE_INFO, "air_purifier_us"),
(AIR_PURIFIER_TABLE_PM25_SERVICE_INFO, "air_purifier_table_us"), (AIR_PURIFIER_TABLE_US_SERVICE_INFO, "air_purifier_table_us"),
], ],
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -117,6 +117,11 @@ async def test_circulator_fan_controlling(
{ATTR_PRESET_MODE: "sleep"}, {ATTR_PRESET_MODE: "sleep"},
"set_preset_mode", "set_preset_mode",
), ),
(
SERVICE_SET_PERCENTAGE,
{ATTR_PERCENTAGE: 27},
"set_percentage",
),
( (
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
{}, {},
@@ -169,10 +174,10 @@ async def test_air_purifier_controlling(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("service_info", "sensor_type"), ("service_info", "sensor_type"),
[ [
(AIR_PURIFIER_VOC_SERVICE_INFO, "air_purifier_jp"), (AIR_PURIFIER_JP_SERVICE_INFO, "air_purifier_jp"),
(AIR_PURIFIER_TABLE_VOC_SERVICE_INFO, "air_purifier_table_jp"), (AIR_PURIFIER_TABLE_JP_SERVICE_INFO, "air_purifier_table_jp"),
(AIR_PURIFIER_PM25_SERVICE_INFO, "air_purifier_us"), (AIR_PURIFIER_US_SERVICE_INFO, "air_purifier_us"),
(AIR_PURIFIER_TABLE_PM25_SERVICE_INFO, "air_purifier_table_us"), (AIR_PURIFIER_TABLE_US_SERVICE_INFO, "air_purifier_table_us"),
], ],
) )
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@@ -20,10 +20,10 @@ from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_SENSOR_TYPE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from . import ( from . import (
AIR_PURIFIER_PM25_SERVICE_INFO, AIR_PURIFIER_JP_SERVICE_INFO,
AIR_PURIFIER_TABLE_PM25_SERVICE_INFO, AIR_PURIFIER_TABLE_JP_SERVICE_INFO,
AIR_PURIFIER_TABLE_VOC_SERVICE_INFO, AIR_PURIFIER_TABLE_US_SERVICE_INFO,
AIR_PURIFIER_VOC_SERVICE_INFO, AIR_PURIFIER_US_SERVICE_INFO,
HUBMINI_MATTER_SERVICE_INFO, HUBMINI_MATTER_SERVICE_INFO,
LOCK_SERVICE_INFO, LOCK_SERVICE_INFO,
WOCURTAIN_SERVICE_INFO, WOCURTAIN_SERVICE_INFO,
@@ -253,22 +253,22 @@ async def test_migrate_entry_fails_for_future_version(
[ [
( (
DEPRECATED_SENSOR_TYPE_AIR_PURIFIER, DEPRECATED_SENSOR_TYPE_AIR_PURIFIER,
AIR_PURIFIER_VOC_SERVICE_INFO, AIR_PURIFIER_JP_SERVICE_INFO,
"air_purifier_jp", "air_purifier_jp",
), ),
( (
DEPRECATED_SENSOR_TYPE_AIR_PURIFIER, DEPRECATED_SENSOR_TYPE_AIR_PURIFIER,
AIR_PURIFIER_PM25_SERVICE_INFO, AIR_PURIFIER_US_SERVICE_INFO,
"air_purifier_us", "air_purifier_us",
), ),
( (
DEPRECATED_SENSOR_TYPE_AIR_PURIFIER_TABLE, DEPRECATED_SENSOR_TYPE_AIR_PURIFIER_TABLE,
AIR_PURIFIER_TABLE_VOC_SERVICE_INFO, AIR_PURIFIER_TABLE_JP_SERVICE_INFO,
"air_purifier_table_jp", "air_purifier_table_jp",
), ),
( (
DEPRECATED_SENSOR_TYPE_AIR_PURIFIER_TABLE, DEPRECATED_SENSOR_TYPE_AIR_PURIFIER_TABLE,
AIR_PURIFIER_TABLE_PM25_SERVICE_INFO, AIR_PURIFIER_TABLE_US_SERVICE_INFO,
"air_purifier_table_us", "air_purifier_table_us",
), ),
], ],