Skip to content
Draft
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: 1 addition & 1 deletion homeassistant/components/actron_air/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
ActronAirSystemCoordinator,
)

PLATFORMS = [Platform.CLIMATE, Platform.SWITCH]
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.SWITCH]


async def async_setup_entry(hass: HomeAssistant, entry: ActronAirConfigEntry) -> bool:
Expand Down
71 changes: 71 additions & 0 deletions homeassistant/components/actron_air/entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Sensor platform for Actron Air integration."""

from actron_neo_api import ActronAirPeripheral

from homeassistant.const import EntityCategory
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .coordinator import ActronAirSystemCoordinator


class ActronAirAcSensor(CoordinatorEntity[ActronAirSystemCoordinator]):
Copy link
Member

Choose a reason for hiding this comment

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

Why is this only a base class for the sensor and why don't we create a base for all other entity types?

"""Base class for Actron Air sensors."""

_attr_entity_category: EntityCategory | None = EntityCategory.DIAGNOSTIC
_attr_has_entity_name = True

def __init__(self, coordinator: ActronAirSystemCoordinator) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self._status = self.coordinator.data
self._serial_number = coordinator.serial_number

self._attr_device_info: DeviceInfo = DeviceInfo(
identifiers={(DOMAIN, self._serial_number)},
name=self._status.ac_system.system_name,
manufacturer="Actron Air",
model_id=self._status.ac_system.master_wc_model,
sw_version=self._status.ac_system.master_wc_firmware_version,
serial_number=self._serial_number,
)

@property
def available(self) -> bool:
"""Return True if entity is available."""
return not self.coordinator.is_device_stale()


class ActronAirPeripheralSensor(ActronAirAcSensor):
"""Base class for Actron Air peripheral sensors."""

_attr_entity_category = None

def __init__(
self, coordinator: ActronAirSystemCoordinator, peripheral: ActronAirPeripheral
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self._ac_serial = coordinator.serial_number
self._peripheral = peripheral
self._serial_number = peripheral.serial_number

suggested_area = None
if hasattr(peripheral, "zones") and len(peripheral.zones) == 1:
zone = peripheral.zones[0]
if hasattr(zone, "title") and zone.title:
Comment on lines +55 to +57
Copy link
Member

Choose a reason for hiding this comment

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

why would we use hasattr here?

suggested_area = zone.title

self._attr_device_info: DeviceInfo = DeviceInfo(
identifiers={(DOMAIN, self._serial_number)},
name=f"{peripheral.device_type} {peripheral.logical_address}",
model=peripheral.device_type,
suggested_area=suggested_area,
via_device=(DOMAIN, self._ac_serial),
)

@property
def available(self) -> bool:
"""Return True if entity is available."""
return not self.coordinator.is_device_stale()
24 changes: 11 additions & 13 deletions homeassistant/components/actron_air/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ rules:
unique-config-entry: done

# Silver
action-exceptions: todo
action-exceptions:
status: exempt
comment: This integration does not have custom service actions.
Comment on lines +29 to +31
Copy link
Member

Choose a reason for hiding this comment

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

No you do have climate and switch actions, which are also considered actions

config-entry-unloading: done
docs-configuration-parameters:
status: exempt
Expand Down Expand Up @@ -54,19 +56,15 @@ rules:
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: todo
entity-category:
status: exempt
comment: This integration does not use entity categories.
entity-device-class:
status: exempt
comment: This integration does not use entity device classes.
entity-disabled-by-default:
entity-category: done
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow:
status: exempt
comment: Not required for this integration at this stage.
entity-translations: todo
exception-translations: todo
icon-translations: todo
reconfiguration-flow: todo
comment: This integration does not have settings in the configuration flow.
repair-issues:
status: exempt
comment: This integration does not have any known issues that require repair.
Expand Down
185 changes: 185 additions & 0 deletions homeassistant/components/actron_air/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
"""Sensor platform for Actron Air integration."""

from dataclasses import dataclass

from actron_neo_api import ActronAirPeripheral

from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import PERCENTAGE, UnitOfPower, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .coordinator import ActronAirConfigEntry, ActronAirSystemCoordinator
from .entity import ActronAirAcSensor, ActronAirPeripheralSensor

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class ActronAirSensorEntityDescription(SensorEntityDescription):
"""Describes Actron Air sensor entity."""

attribute_name: str | None = None


AC_SENSORS: tuple[ActronAirSensorEntityDescription, ...] = (
ActronAirSensorEntityDescription(
key="clean_filter",
translation_key="clean_filter",
),
Comment on lines +31 to +34
Copy link
Member

Choose a reason for hiding this comment

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

What can the state be?

ActronAirSensorEntityDescription(
key="defrost_mode",
translation_key="defrost_mode",
entity_registry_enabled_default=False,
),
Comment on lines +35 to +39
Copy link
Member

Choose a reason for hiding this comment

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

What can the state be?

ActronAirSensorEntityDescription(
key="compressor_chasing_temperature",
translation_key="compressor_chasing_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
ActronAirSensorEntityDescription(
key="compressor_live_temperature",
translation_key="compressor_live_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
ActronAirSensorEntityDescription(
key="compressor_mode",
translation_key="compressor_mode",
entity_registry_enabled_default=False,
),
Comment on lines +56 to +60
Copy link
Member

Choose a reason for hiding this comment

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

What can the state be?

ActronAirSensorEntityDescription(
key="compressor_speed",
translation_key="compressor_speed",
native_unit_of_measurement="RPM",
Copy link
Member

Choose a reason for hiding this comment

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

I believe we have a constant for this unit of measurement

state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
ActronAirSensorEntityDescription(
key="compressor_power",
translation_key="compressor_power",
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
ActronAirSensorEntityDescription(
key="outdoor_temperature",
translation_key="outdoor_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
),
)

PERIPHERAL_SENSORS: tuple[ActronAirSensorEntityDescription, ...] = (
ActronAirSensorEntityDescription(
key="battery",
attribute_name="battery_level",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
ActronAirSensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
ActronAirSensorEntityDescription(
key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: ActronAirConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Actron Air sensor platform entities."""
system_coordinators = entry.runtime_data.system_coordinators
entities: list[SensorEntity] = []

for coordinator in system_coordinators.values():
status = coordinator.data

# Add AC system sensors
entities.extend(
ActronAirSensor(coordinator, description) for description in AC_SENSORS
)

# Add peripheral sensors
for peripheral in status.peripherals:
entities.extend(
ActronAirPeripheralSensorEntity(coordinator, peripheral, description)
for description in PERIPHERAL_SENSORS
)

# Register all sensors
async_add_entities(entities)


class ActronAirSensor(ActronAirAcSensor, SensorEntity):
"""Representation of an Actron Air sensor."""

entity_description: ActronAirSensorEntityDescription

def __init__(
self,
coordinator: ActronAirSystemCoordinator,
description: ActronAirSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{self._serial_number}-{description.key}"

@property
def native_value(self) -> str | int | float | bool | None:
"""Return the state of the sensor."""
return getattr(
self.coordinator.data,
self.entity_description.attribute_name or self.entity_description.key,
None,
)
Comment on lines +155 to +159
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using getattr, can we extend the SensorEntityDescription and add a value_fn to fetch the data? That way it's also type safe and we don't have to use an or here



class ActronAirPeripheralSensorEntity(ActronAirPeripheralSensor, SensorEntity):
"""Representation of an Actron Air peripheral sensor."""

entity_description: ActronAirSensorEntityDescription

def __init__(
self,
coordinator: ActronAirSystemCoordinator,
peripheral: ActronAirPeripheral,
description: ActronAirSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator, peripheral)
self.entity_description = description
self._attr_unique_id = f"{peripheral.serial_number}_{description.key}"

@property
def native_value(self) -> str | int | float | None:
"""Return the state of the sensor."""
return getattr(
self._peripheral,
self.entity_description.attribute_name or self.entity_description.key,
None,
)
29 changes: 29 additions & 0 deletions homeassistant/components/actron_air/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,35 @@
}
},
"entity": {
"sensor": {
"clean_filter": {
"name": "Clean filter"
},
"compressor_chasing_temperature": {
"name": "Compressor chasing temperature"
},
"compressor_coil_inlet_temperature": {
"name": "Compressor coil inlet temperature"
},
"compressor_live_temperature": {
"name": "Compressor live temperature"
},
"compressor_mode": {
"name": "Compressor mode"
},
"compressor_power": {
"name": "Compressor power"
},
"compressor_speed": {
"name": "Compressor speed"
},
"defrost_mode": {
"name": "Defrost mode"
},
"outdoor_temperature": {
"name": "Outdoor temperature"
}
},
"switch": {
"away_mode": {
"name": "Away mode"
Expand Down
Loading
Loading