"""Support for Homekit sensors."""
from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass

from aiohomekit.model import Accessory, Transport
from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities, ThreadStatus
from aiohomekit.model.services import Service, ServicesTypes

from homeassistant.components.bluetooth import (
    async_ble_device_from_address,
    async_last_service_info,
)
from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
    CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    CONCENTRATION_PARTS_PER_MILLION,
    ELECTRIC_CURRENT_AMPERE,
    ELECTRIC_POTENTIAL_VOLT,
    ENERGY_KILO_WATT_HOUR,
    LIGHT_LUX,
    PERCENTAGE,
    POWER_WATT,
    PRESSURE_HPA,
    SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
    TEMP_CELSIUS,
    Platform,
    UnitOfSoundPressure,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType

from . import KNOWN_DEVICES
from .connection import HKDevice
from .entity import CharacteristicEntity, HomeKitEntity
from .utils import folded_name


@dataclass
class HomeKitSensorEntityDescription(SensorEntityDescription):
    """Describes Homekit sensor."""

    probe: Callable[[Characteristic], bool] | None = None
    format: Callable[[Characteristic], str] | None = None


def thread_node_capability_to_str(char: Characteristic) -> str:
    """
    Return the thread device type as a string.

    The underlying value is a bitmask, but we want to turn that to
    a human readable string. Some devices will have multiple capabilities.
    For example, an NL55 is SLEEPY | MINIMAL. In that case we return the
    "best" capability.

    https://openthread.io/guides/thread-primer/node-roles-and-types
    """

    val = ThreadNodeCapabilities(char.value)

    if val & ThreadNodeCapabilities.BORDER_ROUTER_CAPABLE:
        # can act as a bridge between thread network and e.g. WiFi
        return "border_router_capable"

    if val & ThreadNodeCapabilities.ROUTER_ELIGIBLE:
        # radio always on, can be a router
        return "router_eligible"

    if val & ThreadNodeCapabilities.FULL:
        # radio always on, but can't be a router
        return "full"

    if val & ThreadNodeCapabilities.MINIMAL:
        # transceiver always on, does not need to poll for messages from its parent
        return "minimal"

    if val & ThreadNodeCapabilities.SLEEPY:
        # normally disabled, wakes on occasion to poll for messages from its parent
        return "sleepy"

    # Device has no known thread capabilities
    return "none"


def thread_status_to_str(char: Characteristic) -> str:
    """
    Return the thread status as a string.

    The underlying value is a bitmask, but we want to turn that to
    a human readable string. So we check the flags in order. E.g. BORDER_ROUTER implies
    ROUTER, so its more important to show that value.
    """

    val = ThreadStatus(char.value)

    if val & ThreadStatus.BORDER_ROUTER:
        # Device has joined the Thread network and is participating
        # in routing between mesh nodes.
        # It's also the border router - bridging the thread network
        # to WiFI/Ethernet/etc
        return "border_router"

    if val & ThreadStatus.LEADER:
        # Device has joined the Thread network and is participating
        # in routing between mesh nodes.
        # It's also the leader. There's only one leader and it manages
        # which nodes are routers.
        return "leader"

    if val & ThreadStatus.ROUTER:
        # Device has joined the Thread network and is participating
        # in routing between mesh nodes.
        return "router"

    if val & ThreadStatus.CHILD:
        # Device has joined the Thread network as a child
        # It's not participating in routing between mesh nodes
        return "child"

    if val & ThreadStatus.JOINING:
        # Device is currently joining its Thread network
        return "joining"

    if val & ThreadStatus.DETACHED:
        # Device is currently unable to reach its Thread network
        return "detached"

    # Must be ThreadStatus.DISABLED
    # Device is not currently connected to Thread and will not try to.
    return "disabled"


SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
    CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_WATT,
        name="Power",
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=POWER_WATT,
    ),
    CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS,
        name="Current",
        device_class=SensorDeviceClass.CURRENT,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
    ),
    CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS_20: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_AMPS_20,
        name="Current",
        device_class=SensorDeviceClass.CURRENT,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
    ),
    CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_CONNECTSENSE_ENERGY_KW_HOUR,
        name="Energy kWh",
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
    ),
    CharacteristicsTypes.VENDOR_EVE_ENERGY_WATT: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_EVE_ENERGY_WATT,
        name="Power",
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=POWER_WATT,
    ),
    CharacteristicsTypes.VENDOR_EVE_ENERGY_KW_HOUR: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_EVE_ENERGY_KW_HOUR,
        name="Energy kWh",
        device_class=SensorDeviceClass.ENERGY,
        state_class=SensorStateClass.TOTAL_INCREASING,
        native_unit_of_measurement=ENERGY_KILO_WATT_HOUR,
    ),
    CharacteristicsTypes.VENDOR_EVE_ENERGY_VOLTAGE: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_EVE_ENERGY_VOLTAGE,
        name="Volts",
        device_class=SensorDeviceClass.VOLTAGE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
    ),
    CharacteristicsTypes.VENDOR_EVE_ENERGY_AMPERE: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_EVE_ENERGY_AMPERE,
        name="Amps",
        device_class=SensorDeviceClass.CURRENT,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=ELECTRIC_CURRENT_AMPERE,
    ),
    CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY,
        name="Power",
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=POWER_WATT,
    ),
    CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY_2: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_KOOGEEK_REALTIME_ENERGY_2,
        name="Power",
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=POWER_WATT,
    ),
    CharacteristicsTypes.VENDOR_EVE_DEGREE_AIR_PRESSURE: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_EVE_DEGREE_AIR_PRESSURE,
        name="Air Pressure",
        device_class=SensorDeviceClass.PRESSURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=PRESSURE_HPA,
    ),
    CharacteristicsTypes.VENDOR_VOCOLINC_OUTLET_ENERGY: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_VOCOLINC_OUTLET_ENERGY,
        name="Power",
        device_class=SensorDeviceClass.POWER,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=POWER_WATT,
    ),
    CharacteristicsTypes.TEMPERATURE_CURRENT: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.TEMPERATURE_CURRENT,
        name="Current Temperature",
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=TEMP_CELSIUS,
        # This sensor is only for temperature characteristics that are not part
        # of a temperature sensor service.
        probe=(lambda char: char.service.type != ServicesTypes.TEMPERATURE_SENSOR),
    ),
    CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT,
        name="Current Humidity",
        device_class=SensorDeviceClass.HUMIDITY,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=PERCENTAGE,
        # This sensor is only for humidity characteristics that are not part
        # of a humidity sensor service.
        probe=(lambda char: char.service.type != ServicesTypes.HUMIDITY_SENSOR),
    ),
    CharacteristicsTypes.AIR_QUALITY: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.AIR_QUALITY,
        name="Air Quality",
        device_class=SensorDeviceClass.AQI,
        state_class=SensorStateClass.MEASUREMENT,
    ),
    CharacteristicsTypes.DENSITY_PM25: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.DENSITY_PM25,
        name="PM2.5 Density",
        device_class=SensorDeviceClass.PM25,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    CharacteristicsTypes.DENSITY_PM10: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.DENSITY_PM10,
        name="PM10 Density",
        device_class=SensorDeviceClass.PM10,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    CharacteristicsTypes.DENSITY_OZONE: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.DENSITY_OZONE,
        name="Ozone Density",
        device_class=SensorDeviceClass.OZONE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    CharacteristicsTypes.DENSITY_NO2: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.DENSITY_NO2,
        name="Nitrogen Dioxide Density",
        device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    CharacteristicsTypes.DENSITY_SO2: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.DENSITY_SO2,
        name="Sulphur Dioxide Density",
        device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    CharacteristicsTypes.DENSITY_VOC: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.DENSITY_VOC,
        name="Volatile Organic Compound Density",
        device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
    ),
    CharacteristicsTypes.THREAD_NODE_CAPABILITIES: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.THREAD_NODE_CAPABILITIES,
        name="Thread Capabilities",
        entity_category=EntityCategory.DIAGNOSTIC,
        format=thread_node_capability_to_str,
        device_class=SensorDeviceClass.ENUM,
        options=[
            "border_router_capable",
            "full",
            "minimal",
            "none",
            "router_eligible",
            "sleepy",
        ],
        translation_key="thread_node_capabilities",
    ),
    CharacteristicsTypes.THREAD_STATUS: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.THREAD_STATUS,
        name="Thread Status",
        entity_category=EntityCategory.DIAGNOSTIC,
        format=thread_status_to_str,
        device_class=SensorDeviceClass.ENUM,
        options=[
            "border_router",
            "child",
            "detached",
            "disabled",
            "joining",
            "leader",
            "router",
        ],
        translation_key="thread_status",
    ),
    CharacteristicsTypes.VENDOR_NETATMO_NOISE: HomeKitSensorEntityDescription(
        key=CharacteristicsTypes.VENDOR_NETATMO_NOISE,
        name="Noise",
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
        device_class=SensorDeviceClass.SOUND_PRESSURE,
    ),
}


class HomeKitSensor(HomeKitEntity, SensorEntity):
    """Representation of a HomeKit sensor."""

    _attr_state_class = SensorStateClass.MEASUREMENT

    @property
    def name(self) -> str | None:
        """Return the name of the device."""
        full_name = super().name
        default_name = self.default_name
        if (
            default_name
            and full_name
            and folded_name(default_name) not in folded_name(full_name)
        ):
            return f"{full_name} {default_name}"
        return full_name


class HomeKitHumiditySensor(HomeKitSensor):
    """Representation of a Homekit humidity sensor."""

    _attr_device_class = SensorDeviceClass.HUMIDITY
    _attr_native_unit_of_measurement = PERCENTAGE

    def get_characteristic_types(self) -> list[str]:
        """Define the homekit characteristics the entity is tracking."""
        return [CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT]

    @property
    def default_name(self) -> str:
        """Return the default name of the device."""
        return "Humidity"

    @property
    def native_value(self) -> float:
        """Return the current humidity."""
        return self.service.value(CharacteristicsTypes.RELATIVE_HUMIDITY_CURRENT)


class HomeKitTemperatureSensor(HomeKitSensor):
    """Representation of a Homekit temperature sensor."""

    _attr_device_class = SensorDeviceClass.TEMPERATURE
    _attr_native_unit_of_measurement = TEMP_CELSIUS

    def get_characteristic_types(self) -> list[str]:
        """Define the homekit characteristics the entity is tracking."""
        return [CharacteristicsTypes.TEMPERATURE_CURRENT]

    @property
    def default_name(self) -> str:
        """Return the default name of the device."""
        return "Temperature"

    @property
    def native_value(self) -> float:
        """Return the current temperature in Celsius."""
        return self.service.value(CharacteristicsTypes.TEMPERATURE_CURRENT)


class HomeKitLightSensor(HomeKitSensor):
    """Representation of a Homekit light level sensor."""

    _attr_device_class = SensorDeviceClass.ILLUMINANCE
    _attr_native_unit_of_measurement = LIGHT_LUX

    def get_characteristic_types(self) -> list[str]:
        """Define the homekit characteristics the entity is tracking."""
        return [CharacteristicsTypes.LIGHT_LEVEL_CURRENT]

    @property
    def default_name(self) -> str:
        """Return the default name of the device."""
        return "Light Level"

    @property
    def native_value(self) -> int:
        """Return the current light level in lux."""
        return self.service.value(CharacteristicsTypes.LIGHT_LEVEL_CURRENT)


class HomeKitCarbonDioxideSensor(HomeKitSensor):
    """Representation of a Homekit Carbon Dioxide sensor."""

    _attr_device_class = SensorDeviceClass.CO2
    _attr_native_unit_of_measurement = CONCENTRATION_PARTS_PER_MILLION

    def get_characteristic_types(self) -> list[str]:
        """Define the homekit characteristics the entity is tracking."""
        return [CharacteristicsTypes.CARBON_DIOXIDE_LEVEL]

    @property
    def default_name(self) -> str:
        """Return the default name of the device."""
        return "Carbon Dioxide"

    @property
    def native_value(self) -> int:
        """Return the current CO2 level in ppm."""
        return self.service.value(CharacteristicsTypes.CARBON_DIOXIDE_LEVEL)


class HomeKitBatterySensor(HomeKitSensor):
    """Representation of a Homekit battery sensor."""

    _attr_device_class = SensorDeviceClass.BATTERY
    _attr_native_unit_of_measurement = PERCENTAGE
    _attr_entity_category = EntityCategory.DIAGNOSTIC

    def get_characteristic_types(self) -> list[str]:
        """Define the homekit characteristics the entity is tracking."""
        return [
            CharacteristicsTypes.BATTERY_LEVEL,
            CharacteristicsTypes.STATUS_LO_BATT,
            CharacteristicsTypes.CHARGING_STATE,
        ]

    @property
    def default_name(self) -> str:
        """Return the default name of the device."""
        return "Battery"

    @property
    def icon(self) -> str:
        """Return the sensor icon."""
        if not self.available or self.state is None:
            return "mdi:battery-unknown"

        # This is similar to the logic in helpers.icon, but we have delegated the
        # decision about what mdi:battery-alert is to the device.
        icon = "mdi:battery"
        if self.is_charging and self.state > 10:
            percentage = int(round(self.state / 20 - 0.01)) * 20
            icon += f"-charging-{percentage}"
        elif self.is_charging:
            icon += "-outline"
        elif self.is_low_battery:
            icon += "-alert"
        elif self.state < 95:
            percentage = max(int(round(self.state / 10 - 0.01)) * 10, 10)
            icon += f"-{percentage}"

        return icon

    @property
    def is_low_battery(self) -> bool:
        """Return true if battery level is low."""
        return self.service.value(CharacteristicsTypes.STATUS_LO_BATT) == 1

    @property
    def is_charging(self) -> bool:
        """Return true if currently charing."""
        # 0 = not charging
        # 1 = charging
        # 2 = not chargeable
        return self.service.value(CharacteristicsTypes.CHARGING_STATE) == 1

    @property
    def native_value(self) -> int:
        """Return the current battery level percentage."""
        return self.service.value(CharacteristicsTypes.BATTERY_LEVEL)


class SimpleSensor(CharacteristicEntity, SensorEntity):
    """
    A simple sensor for a single characteristic.

    This may be an additional secondary entity that is part of another service. An
    example is a switch that has an energy sensor.

    These *have* to have a different unique_id to the normal sensors as there could
    be multiple entities per HomeKit service (this was not previously the case).
    """

    entity_description: HomeKitSensorEntityDescription

    def __init__(
        self,
        conn: HKDevice,
        info: ConfigType,
        char: Characteristic,
        description: HomeKitSensorEntityDescription,
    ) -> None:
        """Initialise a secondary HomeKit characteristic sensor."""
        self.entity_description = description
        super().__init__(conn, info, char)

    def get_characteristic_types(self) -> list[str]:
        """Define the homekit characteristics the entity is tracking."""
        return [self._char.type]

    @property
    def name(self) -> str:
        """Return the name of the device if any."""
        if name := self.accessory.name:
            return f"{name} {self.entity_description.name}"
        return f"{self.entity_description.name}"

    @property
    def native_value(self) -> str | int | float:
        """Return the current sensor value."""
        val = self._char.value
        if self.entity_description.format:
            return self.entity_description.format(val)
        return val


ENTITY_TYPES = {
    ServicesTypes.HUMIDITY_SENSOR: HomeKitHumiditySensor,
    ServicesTypes.TEMPERATURE_SENSOR: HomeKitTemperatureSensor,
    ServicesTypes.LIGHT_SENSOR: HomeKitLightSensor,
    ServicesTypes.CARBON_DIOXIDE_SENSOR: HomeKitCarbonDioxideSensor,
    ServicesTypes.BATTERY_SERVICE: HomeKitBatterySensor,
}

# Only create the entity if it has the required characteristic
REQUIRED_CHAR_BY_TYPE = {
    ServicesTypes.BATTERY_SERVICE: CharacteristicsTypes.BATTERY_LEVEL,
}


class RSSISensor(HomeKitEntity, SensorEntity):
    """HomeKit Controller RSSI sensor."""

    _attr_device_class = SensorDeviceClass.SIGNAL_STRENGTH
    _attr_entity_category = EntityCategory.DIAGNOSTIC
    _attr_entity_registry_enabled_default = False
    _attr_has_entity_name = True
    _attr_native_unit_of_measurement = SIGNAL_STRENGTH_DECIBELS_MILLIWATT
    _attr_should_poll = False

    def get_characteristic_types(self) -> list[str]:
        """Define the homekit characteristics the entity cares about."""
        return []

    @property
    def available(self) -> bool:
        """Return if the bluetooth device is available."""
        address = self._accessory.pairing_data["AccessoryAddress"]
        return async_ble_device_from_address(self.hass, address) is not None

    @property
    def name(self) -> str:
        """Return the name of the sensor."""
        return "Signal strength"

    @property
    def old_unique_id(self) -> str:
        """Return the old ID of this device."""
        serial = self.accessory_info.value(CharacteristicsTypes.SERIAL_NUMBER)
        return f"homekit-{serial}-rssi"

    @property
    def unique_id(self) -> str:
        """Return the ID of this device."""
        return f"{self._accessory.unique_id}_rssi"

    @property
    def native_value(self) -> int | None:
        """Return the current rssi value."""
        address = self._accessory.pairing_data["AccessoryAddress"]
        last_service_info = async_last_service_info(self.hass, address)
        return last_service_info.rssi if last_service_info else None


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up Homekit sensors."""
    hkid = config_entry.data["AccessoryPairingID"]
    conn: HKDevice = hass.data[KNOWN_DEVICES][hkid]

    @callback
    def async_add_service(service: Service) -> bool:
        if not (entity_class := ENTITY_TYPES.get(service.type)):
            return False
        if (
            required_char := REQUIRED_CHAR_BY_TYPE.get(service.type)
        ) and not service.has(required_char):
            return False
        info = {"aid": service.accessory.aid, "iid": service.iid}
        entity: HomeKitSensor = entity_class(conn, info)
        conn.async_migrate_unique_id(
            entity.old_unique_id, entity.unique_id, Platform.SENSOR
        )
        async_add_entities([entity])
        return True

    conn.add_listener(async_add_service)

    @callback
    def async_add_characteristic(char: Characteristic) -> bool:
        if not (description := SIMPLE_SENSOR.get(char.type)):
            return False
        if description.probe and not description.probe(char):
            return False
        info = {"aid": char.service.accessory.aid, "iid": char.service.iid}
        entity = SimpleSensor(conn, info, char, description)
        conn.async_migrate_unique_id(
            entity.old_unique_id, entity.unique_id, Platform.SENSOR
        )
        async_add_entities([entity])

        return True

    conn.add_char_factory(async_add_characteristic)

    @callback
    def async_add_accessory(accessory: Accessory) -> bool:
        if conn.pairing.transport != Transport.BLE:
            return False

        accessory_info = accessory.services.first(
            service_type=ServicesTypes.ACCESSORY_INFORMATION
        )
        info = {"aid": accessory.aid, "iid": accessory_info.iid}
        entity = RSSISensor(conn, info)
        conn.async_migrate_unique_id(
            entity.old_unique_id, entity.unique_id, Platform.SENSOR
        )
        async_add_entities([entity])
        return True

    conn.add_accessory_factory(async_add_accessory)