Skip to content
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Changelog

### OCT 2025 [2.1.x]

- Migrated to a test instance for Core (not to be used on a regular basis!)

### JUL 2025 [0.1.0]

- Functional device reconnect and align with potential Core PR
Expand Down
52 changes: 6 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Ubiquiti airOS Custom Component for Home Assistant
# Ubiquiti airOS Custom TESTING Component for Home Assistant

**:warning::warning:Only -temporarily- install this component if needed, airOS is a HA Core component - this repo is just for testing things :warning::warning::warning**

**:warning::warning::warning:Read the [release notes](https://github.com/CoMPaTech/hAirOS/releases) before upgrading, in case there are BREAKING changes! :warning: :warning: :warning:**

[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://github.com/CoMPaTech/hAirOS/)
[![Maintenance](https://img.shields.io/badge/Maintained%3F-no-red.svg)](https://github.com/CoMPaTech/hAirOS/)
[![CodeFactor](https://www.codefactor.io/repository/github/CoMPaTech/hAirOS/badge)](https://www.codefactor.io/repository/github/CoMPaTech/hAirOS)
[![HASSfest](https://github.com/CoMPaTech/hAirOS/workflows/Validate%20with%20hassfest/badge.svg)](https://github.com/CoMPaTech/hAirOS/actions)
[![Generic badge](https://img.shields.io/github/v/release/CoMPaTech/hAirOS)](https://github.com/CoMPaTech/hAirOS)
Expand All @@ -12,50 +14,8 @@
[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=CoMPaTech_hAirOS&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=CoMPaTech_hAirOS)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=CoMPaTech_hAirOS&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=CoMPaTech_hAirOS)

## Requirements

Only tested/confirmed with airOS 8 on:

- [x] Nanostation 5AC (LOCO5AC) by @CoMPaTech
- [x] PowerBeam 5AC gen2 by @exico91

## Installation

[![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=CoMPaTech&repository=hAirOShAirOhAirOSntegrations)

## Configuration

Configure this integration the usual way, requiring your username (`ubnt`), password and IP address of the airOS device.

## What it provides

In the current state it retrieves some information and should display the 'other device connected', connection mode, SSID and both actual data being transferred and the maximum capacity. These are displayed as `sensor`s or `binary_sensor`s though most `binary_sensor`s are disabled by default. Additionally for stations connected, child-devices are displayed with both a `binary_sensor` indicating connection and a `button` to force reconnect on the connected device (i.e. the same as the reconnect button on the default homepage of airOS
## More/older information

## State: BETA

Even though available does not mean it's stable yet, the HA part is solid but the class used to interact with the API is in need of improvement (e.g. better overall handling). This might also warrant having the class available as a module from pypi.

## How to install?

- Use [HACS](https://hacs.xyz)
- Navigate to the `Integrations` page and use the three-dots icon on the top right to add a custom repository.
- Use the link to this page as the URL and select 'Integrations' as the category.
- Look for `airOS` in `Integrations` and install it!

### How to add the integration to HA Core

For each device you will have to add it as an integration.

- [ ] In Home Assistant click on `Configuration`
- [ ] Click on `Integrations`
- [ ] Hit the `+` button in the right lower corner
- [ ] Search or browse for 'Ubiquiti airOS' and click it
- [ ] Enter your details

### Is it tested?

It works on my bike and Home Assistant installation :) Let me know if it works on yours!
See the README_BEFORE.md in this repository

[![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-black.svg)](https://sonarcloud.io/summary/new_code?id=CoMPaTech_hAirOS)

And [Home-Assistant Hassfest](https://github.com/home-assistant/actions) and [HACS validation](https://github.com/hacs/action)
61 changes: 61 additions & 0 deletions README_BEFORE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Unmaintained README

**:warning::warning::warning:Read the [release notes](https://github.com/CoMPaTech/hAirOS/releases) before upgrading, in case there are BREAKING changes! :warning: :warning: :warning:**

[![Maintenance](https://img.shields.io/badge/Maintained%3F-no-red.svg)](https://github.com/CoMPaTech/hAirOS/)
[![CodeFactor](https://www.codefactor.io/repository/github/CoMPaTech/hAirOS/badge)](https://www.codefactor.io/repository/github/CoMPaTech/hAirOS)
[![HASSfest](https://github.com/CoMPaTech/hAirOS/workflows/Validate%20with%20hassfest/badge.svg)](https://github.com/CoMPaTech/hAirOS/actions)
[![Generic badge](https://img.shields.io/github/v/release/CoMPaTech/hAirOS)](https://github.com/CoMPaTech/hAirOS)

[![CodeRabbit.ai is Awesome](https://img.shields.io/badge/AI-orange?label=CodeRabbit&color=orange&link=https%3A%2F%2Fcoderabbit.ai)](https://coderabbit.ai)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=CoMPaTech_hAirOS&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=CoMPaTech_hAirOS)
[![Technical Debt](https://sonarcloud.io/api/project_badges/measure?project=CoMPaTech_hAirOS&metric=sqale_index)](https://sonarcloud.io/summary/new_code?id=CoMPaTech_hAirOS)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=CoMPaTech_hAirOS&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=CoMPaTech_hAirOS)

## Requirements

Only tested/confirmed with airOS 8 on:

- [x] Nanostation 5AC (LOCO5AC) by @CoMPaTech
- [x] PowerBeam 5AC gen2 by @exico91

## Installation

[![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=CoMPaTech&repository=hAirOShAirOhAirOSntegrations)
Comment thread
CoMPaTech marked this conversation as resolved.

## Configuration

Configure this integration the usual way, requiring your username (`ubnt`), password and IP address of the airOS device.

## What it provides

In the current state it retrieves some information and should display the 'other device connected', connection mode, SSID and both actual data being transferred and the maximum capacity. These are displayed as `sensor`s or `binary_sensor`s though most `binary_sensor`s are disabled by default. Additionally for stations connected, child-devices are displayed with both a `binary_sensor` indicating connection and a `button` to force reconnect on the connected device (i.e. the same as the reconnect button on the default homepage of airOS

## State: BETA

Even though available does not mean it's stable yet, the HA part is solid but the class used to interact with the API is in need of improvement (e.g. better overall handling). This might also warrant having the class available as a module from pypi.

## How to install?

- Use [HACS](https://hacs.xyz)
- Navigate to the `Integrations` page and use the three-dots icon on the top right to add a custom repository.
- Use the link to this page as the URL and select 'Integrations' as the category.
- Look for `airOS` in `Integrations` and install it!

### How to add the integration to HA Core

For each device you will have to add it as an integration.

- [ ] In Home Assistant click on `Configuration`
- [ ] Click on `Integrations`
- [ ] Hit the `+` button in the right lower corner
- [ ] Search or browse for 'Ubiquiti airOS' and click it
- [ ] Enter your details

### Is it tested?

It works on my bike and Home Assistant installation :) Let me know if it works on yours!

[![SonarCloud](https://sonarcloud.io/images/project_badges/sonarcloud-black.svg)](https://sonarcloud.io/summary/new_code?id=CoMPaTech_hAirOS)

And [Home-Assistant Hassfest](https://github.com/home-assistant/actions) and [HACS validation](https://github.com/hacs/action)
129 changes: 117 additions & 12 deletions custom_components/airos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,62 @@

from __future__ import annotations

from airos.airos8 import AirOS
import logging

from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from airos.airos6 import AirOS6
from airos.airos8 import AirOS8
from airos.helpers import DetectDeviceData, async_get_firmware_data

from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_PLATFORM
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_SSL,
CONF_USERNAME,
CONF_VERIFY_SSL,
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import DEFAULT_SSL, DEFAULT_VERIFY_SSL, SECTION_ADVANCED_SETTINGS
from .coordinator import AirOSConfigEntry, AirOSDataUpdateCoordinator

_PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.BUTTON]
_PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
Platform.SENSOR,
]

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> bool:
"""Set up Ubiquiti airOS from a config entry."""

# By default airOS 8 comes with self-signed SSL certificates,
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(hass, verify_ssl=False)

airos_device = AirOS(
host=entry.data[CONF_HOST],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
session=session,
session = async_get_clientsession(
hass, verify_ssl=entry.data[SECTION_ADVANCED_SETTINGS][CONF_VERIFY_SSL]
)

coordinator = AirOSDataUpdateCoordinator(hass, entry, airos_device)
conn_data = {
CONF_HOST: entry.data[CONF_HOST],
CONF_USERNAME: entry.data[CONF_USERNAME],
CONF_PASSWORD: entry.data[CONF_PASSWORD],
"use_ssl": entry.data[SECTION_ADVANCED_SETTINGS][CONF_SSL],
"session": session,
}

# Determine firmware version before creating the device instance
device_data: DetectDeviceData = await async_get_firmware_data(**conn_data)
airos_class: type[AirOS8 | AirOS6] = AirOS8
if device_data["fw_major"] == 6:
airos_class = AirOS6

airos_device = airos_class(**conn_data)

coordinator = AirOSDataUpdateCoordinator(hass, entry, device_data, airos_device)
await coordinator.async_config_entry_first_refresh()

entry.runtime_data = coordinator
Expand All @@ -37,6 +67,81 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
return True


async def async_migrate_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> bool:
"""Migrate old config entry."""

if entry.version > 1:
# This means the user has downgraded from a future version
return False

if entry.version == 1 and entry.minor_version == 1:
new_data = {**entry.data}
advanced_data = {
CONF_SSL: DEFAULT_SSL,
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
}
new_data[SECTION_ADVANCED_SETTINGS] = advanced_data

hass.config_entries.async_update_entry(
entry,
data=new_data,
minor_version=2,
)

# As v6 has no device_id use mac_address in binary_sensor
if entry.version == 1 and entry.minor_version == 2:
device_registry = dr.async_get(hass)
device_entries = dr.async_entries_for_config_entry(
device_registry, entry.entry_id
)

if not device_entries:
_LOGGER.debug("Nothing in device registry, assuming migration succeeded")
hass.config_entries.async_update_entry(entry, minor_version=3)
return True # Nothing to migrate, complete version bump

device_entry = device_entries[0]
mac_address = None

for connection_type, value in device_entry.connections:
if connection_type == dr.CONNECTION_NETWORK_MAC:
mac_address = dr.format_mac(value)
break

if not mac_address: # pragma: no cover
_LOGGER.error(
"No MAC address found for device %s, unable to migrate binary_sensors appropriately. Please remove and re-add the integration to avoid duplicate entities",
device_entry.name,
)
hass.config_entries.async_update_entry(entry, minor_version=3)
return True

@callback
def update_unique_id(entity_entry: er.RegistryEntry) -> dict[str, str] | None:
"""Update unique id from device_id to mac address."""
if (euid := entry.unique_id) is not None:
if (
entity_entry.platform == BINARY_SENSOR_PLATFORM
and entity_entry.unique_id.startswith(euid)
):
suffix = entity_entry.unique_id.removeprefix(euid)
new_unique_id = f"{mac_address}{suffix}"
_LOGGER.info(
"Migrating entity %s unique_id to %s",
entity_entry.entity_id,
new_unique_id,
)
return {"new_unique_id": new_unique_id}
return None # pragma: no cover

await er.async_migrate_entries(hass, entry.entry_id, update_unique_id)

hass.config_entries.async_update_entry(entry, minor_version=3)

Comment thread
CoMPaTech marked this conversation as resolved.

return True


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