Skip to content

Commit

Permalink
update fan to support HA base fan changes and support UI config flow
Browse files Browse the repository at this point in the history
  • Loading branch information
quielb committed Aug 25, 2024
1 parent 1c833e2 commit 514a370
Show file tree
Hide file tree
Showing 16 changed files with 741 additions and 325 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/hacs.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
name: HACS Action
name: HACS Validate

on:
pull_request:

jobs:
hacs:
name: HACS Action
name: HACS Validate
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v2"
- name: HACS Action
- uses: "actions/checkout@v3"
- name: HACS Validate
uses: "hacs/action@main"
with:
ignore: "brands"
Expand Down
70 changes: 48 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,56 @@
# AirScape Whole House Fan [![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs)
# AirScape Whole House Fan [![hacs][hacsbadge]][hacs]

A Home Assistant custom component to control Airscape Whole House Fans with Gen2 controls.
[![Github Release][release-shield]][releases]
![issues]

To Add a fan update your configuration.yaml:
A Home Assistant custom component to integrate [Airscape Whole House Fans][airscape-url] with Gen2 controls.

```yaml
fan:
- platform: airscape
name: Whole House
host: "192.168.10.249"
```
## Installation via HACS (recommended)

There is one other attribute supported. Setting a minimum speed value will start up the WHF with that speed and prevent any automation from reducing the speed below the minimum.
[![Open your Home Assistant instance and open a repository inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=quielb&repository=hass-airscape)

```yaml
fan:
- platform: airscape
name: Whole House
host: "192.168.10.249"
minimum: 4
```
1. Follow the link [here](https://hacs.xyz/docs/faq/custom_repositories/)
2. Use the custom repo link https://github.com/quielb/hass-airscape
3. Select the category type `integration`
4. Then once it's there (still in HACS) click the INSTALL button
5. Restart Home Assistant
6. Once restarted, in the HA UI go to `Configuration` (the ⚙️ in the lower left) -> `Devices and Services` click `+ Add Integration` and search for `airscape`

This component adds one custom service:
## Using the Airscape component

```
airscape.add_time
```
The basic component operation of the component implements all the features of the fan component. This integration extends the basic fan adding additional functionality specific to an Airscape whole house fan.

This allows for adding an hour of time to the timer on the fan.
**Services**

The Airscape component has several custom services that are controlled by the minimum speed configuration option:

Service | Description
-- | --
`airscape.turn_on_to_minimum` | Starts the fan and sets the speed to the minimum defined configuration option
`airscape.speed_up` | Increase the fan speed by one step
`airscape.slow_down` | Slow down the fan speed by one step. Will not go below the defined minimum fan speed configuration option

**Controls**

In addition to the standard fan controls there is one addition button. When used, the button will add one hour to the timer.

**Sensors**

The Airscape component adds several sensors to represent the data from the fan:

Sensor | Description
-- | --
Attic Temperature | Readings from Attic temp sensor
CFM | Current CFM of fan
Power | Power usage in Watts
Timer Remaining | Number of minutes remaining on shutoff timer
Inside | Reading from control panel wall plate (disabled by default)
Outside | Readings from deprecated outdoor data collection kit (disabled by default)
Door | Binary sensor to indicate if fan doors are in motion (disabled by default)

[airscape-url]: https://airscapefans.com/
[hacs]: https://github.com/custom-components/hacs
[hacsbadge]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge
[releases]: https://github.com/quielb/hass-airscape/releases
[release-shield]: https://img.shields.io/github/v/release/quielb/hass-airscape
[issues]: https://img.shields.io/github/issues/quielb/hass-airscape
98 changes: 95 additions & 3 deletions custom_components/airscape/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,96 @@
"""The Airscape component."""
"""The airscape integration."""

DOMAIN = "airscape"
AIRSCAPE_DOMAIN = "airscape"
from __future__ import annotations

from dataclasses import dataclass
import logging

import airscape as whf

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.device_registry import DeviceInfo, format_mac
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .coordinator import AirscapeCoordinator

PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.FAN,
Platform.SENSOR,
]


@dataclass
class AirscapeData:
"""AirScape Data Class."""

type AirscapeConfigEntry = ConfigEntry[AirscapeData] # noqa: F821


_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, entry: AirscapeConfigEntry) -> bool:
"""Set up airscape from a config entry."""
try:
device = await hass.async_add_executor_job(whf.Fan, entry.data[CONF_HOST])
except (whf.exceptions.ConnectionError, whf.exceptions.Timeout) as err:
raise ConfigEntryNotReady from err
dataCoordinator = AirscapeCoordinator(hass, device)
await dataCoordinator.async_config_entry_first_refresh()
entry.runtime_data = dataCoordinator

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_reload_entry))

return True


async def async_unload_entry(hass: HomeAssistant, entry: AirscapeConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)


async def async_reload_entry(hass: HomeAssistant, entry: AirscapeConfigEntry) -> None:
"""Handle an options update."""
await hass.config_entries.async_reload(entry.entry_id)


class AirscapeDeviceEntity(CoordinatorEntity[AirscapeCoordinator]):
"""Define an Airscape device entity."""

_attr_has_entity_name = True

def __init__(
self, entry: ConfigEntry, entity_description, enabled_default: bool = True
) -> None:
"""Initialize the Airscape Entity."""
self._entry_id = entry.entry_id
self._attr_enabled_default = enabled_default
self._coordinator = entry.runtime_data
self._attr_device_info = DeviceInfo(
configuration_url=f"http://{self._coordinator.data.get('ipaddr')}",
identifiers={(DOMAIN, format_mac(self._coordinator.data.get("macaddr")))},
name=f"AirScape {self._coordinator.data['model']}",
manufacturer="AirScape",
model=self._coordinator.data["model"],
sw_version=self._coordinator.data["softver"],
connections={(DOMAIN, entry.entry_id)},
)
self.entity_description = entity_description
self._name = entity_description.name
self._type = entity_description.key
self._attr_unique_id = (
f"{format_mac(entry.runtime_data.data.get("macaddr"))}_{self._type}"
)
super().__init__(self._coordinator)

@property
def device_info(self) -> DeviceInfo:
"""Retrun device information."""
return self._attr_device_info
47 changes: 47 additions & 0 deletions custom_components/airscape/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""Platform for AirScpae Fan Binary Sensor."""

import logging

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import AirscapeDeviceEntity
from .const import BINARYSENSOR_ENTITY

_LOGGER = logging.getLogger(__name__)
PLATFORM = "binary_sensor"


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Handle creation of entity."""

buttons = [
AirscapeBinarySensor(config_entry, BINARYSENSOR_ENTITY[binary_sensor])
for binary_sensor in BINARYSENSOR_ENTITY
]
async_add_entities(buttons, False)


class AirscapeBinarySensor(BinarySensorEntity, AirscapeDeviceEntity):
"""Implementation of Airscape Whole House Fan Sensor."""

def __init__(
self, entry: ConfigEntry, entity_description: BinarySensorEntityDescription
) -> None:
"""Initilize an AirScape Binary Sensor."""
super().__init__(entry, entity_description)

@property
def is_on(self) -> bool:
"""Return the statof of the Damper Doors."""
return bool(self._coordinator.data["doorinprocess"])
43 changes: 43 additions & 0 deletions custom_components/airscape/button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Platform for AirScape Fan Button."""

import logging

from homeassistant.components.button import ButtonEntity, ButtonEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import AirscapeDeviceEntity
from .const import BUTTON_ENTITY

_LOGGER = logging.getLogger(__name__)
PLATFORM = "button"


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Handle creation of entity."""

buttons = [
AirscapeButton(config_entry, BUTTON_ENTITY[button]) for button in BUTTON_ENTITY
]
async_add_entities(buttons, False)


class AirscapeButton(ButtonEntity, AirscapeDeviceEntity):
"""Implementation of Airscape Whole House Fan Sensor."""

def __init__(
self, entry: ConfigEntry, entity_description: ButtonEntityDescription
) -> None:
"""Initilize an AirScape button."""
super().__init__(entry, entity_description)

def press(self) -> None:
"""Press the button."""
if bool(self._coordinator.data["fanspd"]):
self._coordinator.fan_api.add_time()
Loading

0 comments on commit 514a370

Please sign in to comment.