8000 Add integration: Aeroflex adjustable bed by iddora · Pull Request #140696 · home-assistant/core · GitHub
[go: up one dir, main page]

Skip to content

Add integration: Aeroflex adjustable bed #140696

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

Closed
wants to merge 3 commits into from
Closed
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
2 changes: 2 additions & 0 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

83 changes: 83 additions & 0 deletions homeassistant/components/aeroflex/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""The Aeroflex Adjustable Bed integration."""

from __future__ import annotations

import logging

from bleak import BleakClient
from bleak.exc import BleakError

from homeassistant.components.bluetooth import async_ble_device_from_address
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import CONF_DEVICE_ADDRESS, CONF_DEVICE_NAME, DOMAIN, SERVICE_UUID

_LOGGER = logging.getLogger(__name__)
_PLATFORMS: list[Platform] = [Platform.NUMBER]


async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Aeroflex Adjustable Bed from a config entry."""
address = entry.data.get(CONF_DEVICE_ADDRESS)
name = entry.data.get(CONF_DEVICE_NAME)

if not address:
_LOGGER.error("No device address found in config entry")
return False

ble_device = async_ble_device_from_address(hass, address, connectable=True)

if not ble_device:
raise ConfigEntryNotReady(
f"Could not find BLE device with name '{name}' at address {address}"
)

# Verify we can connect to the device
try:
async with BleakClient(ble_device) as client:
if not client.is_connected:
raise ConfigEntryNotReady(
f"Failed to connect to device '{name}' at address {address}"
)

# Check if the device has the required service
if not any(
service.uuid.lower() == SERVICE_UUID.lower()
for service in client.services
):
_LOGGER.error(
"Device '%s' at address %s does not have the required service UUID %s",
name,
address,
SERVICE_UUID,
)
return False
except BleakError as err:
raise ConfigEntryNotReady(
f"Failed to connect to device '{name}' at address {address}: {err}"
) from err

# Store BLE device in runtime data for use by the entities
entry.runtime_data = ble_device

# Store entry for later use
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = ble_device

await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)

if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
if not hass.data[DOMAIN]:
hass.data.pop(DOMAIN)

return unload_ok
75 changes: 75 additions & 0 deletions homeassistant/components/aeroflex/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""Config flow for the Aeroflex Adjustable Bed integration."""

from __future__ import annotations

from collections.abc import Mapping
import logging
from typing import Any

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.const import CONF_NAME

from .const import CONF_DEVICE_ADDRESS, CONF_DEVICE_NAME, DOMAIN

_LOGGER = logging.getLogger(__name__)


class AeroflexConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Aeroflex."""

VERSION = 1
MINOR_VERSION = 0

def __init__(self) -> None:
"""Initialize the config flow."""
self._discovery_info: BluetoothServiceInfoBleak | None = None
self._selected_device_address: str | None = None
self._selected_device_name: str | None = None

async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfoBleak
) -> ConfigFlowResult:
"""Handle the bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address)
self._abort_if_unique_id_configured()
self._discovery_info = discovery_info
self._selected_device_address = discovery_info.address
self._selected_device_name = (
discovery_info.name or f"Aeroflex Bed {discovery_info.address}"
)
return await self.async_step_bluetooth_confirm()

async def async_step_bluetooth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm discovery."""
assert self._discovery_info is not None
discovery_info = self._discovery_info
suggested_name = str(self._selected_device_name or "Aeroflex Bed")
placeholders: Mapping[str, str] = {"name": suggested_name}

if user_input is not None:
device_name = user_input.get(CONF_NAME, suggested_name)
return self.async_create_entry(
title=device_name,
data={
CONF_DEVICE_ADDRESS: discovery_info.address,
CONF_DEVICE_NAME: device_name,
},
)

self._set_confirm_only()
self.context["title_placeholders"] = placeholders
return self.async_show_form(
step_id="bluetooth_confirm",
description_placeholders=placeholders,
data_schema=vol.Schema(
{
vol.Required(CONF_NAME, default=suggested_name): str,
}
),
)
30 changes: 30 additions & 0 deletions homeassistant/components/aeroflex/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""Constants for the Aeroflex Adjustable Bed integration."""

from enum import IntEnum

DOMAIN = "aeroflex"
CONF_DEVICE_ADDRESS = "address"
CONF_DEVICE_NAME = "name"

# BLE Service UUID for Aeroflex devices
SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
RX_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
TX_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"

MIN_ANGLE = 0
MAX_HEAD_ANGLE = 60 # degrees
MAX_FEET_ANGLE = 30 # degrees
STEP_DURATION = 0.15 # seconds
HEAD_MOTION_TIME = 20 # seconds
FEET_MOTION_TIME = 15 # seconds


class BedCommand(IntEnum):
"""Commands for controlling the bed."""

HEAD_UP = 0x34
FEET_UP = 0x36
HEAD_DOWN = 0x37
BOTH_UP = 0x38
BOTH_DOWN = 0x39
FEET_DOWN = 0x41
44 changes: 44 additions & 0 deletions homeassistant/components/aeroflex/entity.py
6D40
Original file line number Diff line numberDiff line change
@@ -0,0 +1,44 @@
"""Aeroflex adjustable bed entity implementation."""

from __future__ import annotations

import asyncio
import logging

from bleak.backends.device import BLEDevice

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity

from .const import CONF_DEVICE_NAME, DOMAIN

_LOGGER = logging.getLogger(__name__)


class AeroflexBedEntity(Entity):
"""Base entity for Aeroflex bed."""

def __init__(
self, hass: HomeAssistant, entry: ConfigEntry, ble_device: BLEDevice
) -> None:
"""Initialize the bed entity."""
self.hass = hass
self.entry = entry
self._ble_device = ble_device
self._attr_has_entity_name = True
Comment on lines +22 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def __init__(
self, hass: HomeAssistant, entry: ConfigEntry, ble_device: BLEDevice
) -> None:
"""Initialize the bed entity."""
self.hass = hass
self.entry = entry
self._ble_device = ble_device
self._attr_has_entity_name = True
_attr_has_entity_name = True
def __init__(
self, hass: HomeAssistant, entry: ConfigEntry, ble_device: BLEDevice
) -> None:
"""Initialize the bed entity."""
self.hass = hass
self.entry = entry
self._ble_device = ble_device


# Get the device name from the config entry
device_name = entry.data.get(
CONF_DEVICE_NAME, f"Aeroflex Bed {ble_device.address}"
)

self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, ble_device.address)},
name=device_name,
manufacturer="Aeroflex",
model="Adjustable Bed",
)
# Create a lock specific to this bed instance
self._command_lock = asyncio.Lock()
15 changes: 15 additions & 0 deletions homeassistant/components/aeroflex/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"domain": "aeroflex",
"name": "Aeroflex Adjustable Bed",
"bluetooth": [
{
"service_uuid": "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
}
],
"codeowners": ["@iddora"],
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/aeroflex",
"iot_class": "calculated",
"quality_scale": "bronze"
}
Loading
Loading
0