Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to use async_forward_entry_setups #20

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions custom_components/blueair/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["sensor", "fan"]
PLATFORMS = ["binary_sensor", "fan", "sensor", "light"]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
Expand All @@ -36,7 +36,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

devices = await hass.async_add_executor_job(lambda: client.get_devices())
hass.data[DOMAIN][entry.entry_id]["devices"] = [
BlueairDataUpdateCoordinator(hass, client, device["uuid"], device["name"])
BlueairDataUpdateCoordinator(hass, client, device["uuid"], device["name"], device["mac"])
for device in devices
]
_LOGGER.debug(f"BlueAir Devices {devices}")
Expand All @@ -47,7 +47,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
]
await asyncio.gather(*tasks)

hass.config_entries.async_setup_platforms(entry, PLATFORMS)
try:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
except AttributeError:
hass.config_entries.async_setup_platforms(entry, PLATFORMS)

return True

Expand Down
82 changes: 82 additions & 0 deletions custom_components/blueair/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from __future__ import annotations

from .const import DOMAIN
from .device import BlueairDataUpdateCoordinator
from .entity import BlueairEntity

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity
)

async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Blueair sensors from config entry."""
devices: list[BlueairDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]["devices"]
entities = []
for device in devices:
# Don't add sensors to classic models
if (
device.model.startswith("classic") and not device.model.endswith("i")
) or device.model == "foobot":
pass
else:
entities.extend(
[
BlueairFilterExpiredSensor(f"{device.device_name}_filter_expired", device),
BlueairChildLockSensor(f"{device.device_name}_child_lock", device),
BlueairOnlineSensor(f"{device.device_name}_online", device),
]
)
async_add_entities(entities)


class BlueairFilterExpiredSensor(BlueairEntity, BinarySensorEntity):
"""Monitors the status of the Filter"""

def __init__(self, name, device):
"""Initialize the filter_status sensor."""
super().__init__("filter_expired", name, device)
self._state: bool = None
self._attr_icon = "mdi:air-filter"
self._attr_device_class = BinarySensorDeviceClass.PROBLEM

@property
def is_on(self) -> bool | None:
"""Return the current filter_status."""
return self._device.filter_expired


class BlueairChildLockSensor(BlueairEntity, BinarySensorEntity):

def __init__(self, name, device):
super().__init__("child_Lock", name, device)
self._state: bool = None
self._attr_icon = "mdi:account-child-outline"

@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self._device.child_lock


class BlueairOnlineSensor(BlueairEntity, BinarySensorEntity):
def __init__(self, name, device):
"""Initialize the online sensor."""
super().__init__("online", name, device)
self._state: bool = None
self._attr_icon = "mdi:wifi-check"
self._attr_device_class = BinarySensorDeviceClass.CONNECTIVITY,

@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
return self._device.wifi_working

@property
def icon(self) -> str | None:
if self.is_on:
return self._attr_icon
else:
return "mdi:wifi-strength-outline"
20 changes: 20 additions & 0 deletions custom_components/blueair/blueair/blueair.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,26 @@ def set_fan_speed(self, device_uuid, new_speed):
},
)

def set_brightness(self, device_uuid, brightness):
"""
Set the brightness per @spikeyGG comment at https://community.home-assistant.io/t/blueair-purifier-addon/154456/14
"""
res = requests.post(
f"https://{self.home_host}/v2/device/{device_uuid}/attribute/brightness/",
headers={
"Content-Type": "application/json",
"X-API-KEY-TOKEN": API_KEY,
"X-AUTH-TOKEN": self.auth_token,
},
json={
"currentValue": brightness,
"scope": "device",
"defaultValue": brightness,
"name": "brightness",
"uuid": device_uuid,
},
)

def set_fan_mode(self, device_uuid, new_mode):
"""
Set the fan mode to automatic
Expand Down
57 changes: 43 additions & 14 deletions custom_components/blueair/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from typing import Any
from async_timeout import timeout


from . import blueair

API = blueair.BlueAir
Expand All @@ -20,13 +19,15 @@ class BlueairDataUpdateCoordinator(DataUpdateCoordinator):
"""Blueair device object."""

def __init__(
self, hass: HomeAssistant, api_client: API, uuid: str, device_name: str
self, hass: HomeAssistant, api_client: API, uuid: str, device_name: str,
mac: str = None,
) -> None:
"""Initialize the device."""
self.hass: HomeAssistant = hass
self.api_client: API = api_client
self._uuid: str = uuid
self._name: str = device_name
self.mac = mac
self._manufacturer: str = "BlueAir"
self._device_information: dict[str, Any] = {}
self._datapoint: dict[str, Any] = {}
Expand Down Expand Up @@ -68,63 +69,63 @@ def model(self) -> str:
return self._device_information.get("compatibility", self.id)

@property
def temperature(self) -> float:
def temperature(self) -> float | None:
"""Return the current temperature in degrees C."""
if "temperature" not in self._datapoint:
return None
return self._datapoint["temperature"]

@property
def humidity(self) -> float:
def humidity(self) -> float | None:
"""Return the current relative humidity percentage."""
if "humidity" not in self._datapoint:
return None
return self._datapoint["humidity"]

@property
def co2(self) -> float:
def co2(self) -> float | None:
"""Return the current co2."""
if "co2" not in self._datapoint:
return None
return self._datapoint["co2"]

@property
def voc(self) -> float:
def voc(self) -> float | None:
"""Return the current voc."""
if "voc" not in self._datapoint:
return None
return self._datapoint["voc"]

@property
def pm1(self) -> float:
def pm1(self) -> float | None:
"""Return the current pm1."""
if "pm1" not in self._datapoint:
return None
return self._datapoint["pm1"]

@property
def pm10(self) -> float:
def pm10(self) -> float | None:
"""Return the current pm10."""
if "pm10" not in self._datapoint:
return None
return self._datapoint["pm10"]

@property
def pm25(self) -> float:
def pm25(self) -> float | None:
"""Return the current pm25."""
if "pm25" not in self._datapoint:
return None
return self._datapoint["pm25"]

@property
def all_pollution(self) -> float:
def all_pollution(self) -> float | None:
"""Return all pollution"""
if "all_pollution" not in self._datapoint:
return None
return self._datapoint["all_pollution"]

@property
def fan_speed(self) -> int:
def fan_speed(self) -> int | None:
"""Return the current fan speed."""
if "fan_speed" not in self._attribute:
return None
Expand All @@ -140,7 +141,7 @@ def is_on(self) -> bool():
return True

@property
def fan_mode(self) -> str:
def fan_mode(self) -> str | None:
"""Return the current fan mode"""
if self._attribute["mode"] == "manual":
return None
Expand All @@ -155,11 +156,39 @@ def fan_mode_supported(self) -> bool():
return False

@property
def filter_status(self) -> str:
def filter_expired(self) -> bool | None:
"""Return the current filter status."""
if "filter_status" not in self._attribute:
return None
return self._attribute["filter_status"]
return self._attribute["filter_status"] != "OK"

@property
def child_lock(self) -> bool | None:
"""Return the current filter status."""
if "child_lock" not in self._attribute:
return None
return bool(self._attribute["child_lock"])

@property
def wifi_working(self) -> bool | None:
"""Return the current filter status."""
if "wifi_status" not in self._attribute:
return None
return self._attribute["wifi_status"] == "1"

@property
def brightness(self) -> int | None:
"""Return the current filter status."""
if "brightness" not in self._attribute:
return None
return int(self._attribute["brightness"])

async def set_brightness(self, brightness) -> None:
await self.hass.async_add_executor_job(
lambda: self.api_client.set_brightness(self.id, brightness)
)
self._attribute["brightness"] = brightness
await self.async_refresh()

async def set_fan_speed(self, new_speed) -> None:
await self.hass.async_add_executor_job(
Expand Down
3 changes: 3 additions & 0 deletions custom_components/blueair/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ def __init__(
@property
def device_info(self) -> DeviceInfo:
"""Return a device description for device registry."""
connections = {(CONNECTION_NETWORK_MAC, self._device.mac)}
return {
"connections": connections,
"identifiers": {(DOMAIN, self._device.id)},
"manufacturer": self._device.manufacturer,
"model": self._device.model,
Expand All @@ -41,6 +43,7 @@ def device_info(self) -> DeviceInfo:
async def async_update(self):
"""Update Blueair entity."""
await self._device.async_request_refresh()
self._attr_available = self._device.wifi_working

async def async_added_to_hass(self):
"""When entity is added to hass."""
Expand Down
60 changes: 60 additions & 0 deletions custom_components/blueair/light.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from .const import DOMAIN
from .device import BlueairDataUpdateCoordinator
from .entity import BlueairEntity
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ColorMode,
LightEntity,
)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Blueair sensors from config entry."""
devices: list[BlueairDataUpdateCoordinator] = hass.data[DOMAIN][
config_entry.entry_id
]["devices"]
entities = []
for device in devices:
# Don't add sensors to classic models
if (
device.model.startswith("classic") and not device.model.endswith("i")
) or device.model == "foobot":
pass
else:
entities.extend(
[
BlueairLightEntity(f"{device.device_name}_light", device),
]
)
async_add_entities(entities)


class BlueairLightEntity(BlueairEntity, LightEntity):
_attr_color_mode = ColorMode.BRIGHTNESS
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}

def __init__(self, name, device):
super().__init__("LED Light", name, device)

@property
def brightness(self) -> int | None:
"""Return the brightness of this light between 0..255."""
return round(self._device.brightness / 100 * 255.0, 0)

@property
def is_on(self) -> bool:
"""Return True if the entity is on."""
return self._device.brightness != 0

async def async_turn_on(self, **kwargs):
if ATTR_BRIGHTNESS in kwargs:
# Convert Home Assistant brightness (0-255) to Abode brightness (0-99)
# If 100 is sent to Abode, response is 99 causing an error
await self._device.set_brightness(
round(kwargs[ATTR_BRIGHTNESS] * 100 / 255.0)
)
else:
await self._device.set_brightness(100)

async def async_turn_off(self, **kwargs):
await self._device.set_brightness(0)
Loading