1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00

Add temperature sensors for Asuswrt (#58303)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Chen-IL 2021-12-03 21:27:17 +02:00 committed by GitHub
parent 0dfc86956b
commit ac26c2378b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 113 additions and 3 deletions

View File

@ -25,3 +25,4 @@ SENSORS_BYTES = ["sensor_rx_bytes", "sensor_tx_bytes"]
SENSORS_CONNECTED_DEVICE = ["sensor_connected_device"]
SENSORS_LOAD_AVG = ["sensor_load_avg1", "sensor_load_avg5", "sensor_load_avg15"]
SENSORS_RATES = ["sensor_rx_rates", "sensor_tx_rates"]
SENSORS_TEMPERATURES = ["2.4GHz", "5.0GHz", "CPU"]

View File

@ -47,6 +47,7 @@ from .const import (
SENSORS_CONNECTED_DEVICE,
SENSORS_LOAD_AVG,
SENSORS_RATES,
SENSORS_TEMPERATURES,
)
CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP]
@ -60,6 +61,7 @@ SENSORS_TYPE_BYTES = "sensors_bytes"
SENSORS_TYPE_COUNT = "sensors_count"
SENSORS_TYPE_LOAD_AVG = "sensors_load_avg"
SENSORS_TYPE_RATES = "sensors_rates"
SENSORS_TYPE_TEMPERATURES = "sensors_temperatures"
_LOGGER = logging.getLogger(__name__)
@ -114,6 +116,15 @@ class AsusWrtSensorDataHandler:
return _get_dict(SENSORS_LOAD_AVG, avg)
async def _get_temperatures(self):
"""Fetch temperatures information from the router."""
try:
temperatures = await self._api.async_get_temperature()
except (OSError, ValueError) as exc:
raise UpdateFailed(exc) from exc
return temperatures
def update_device_count(self, conn_devices: int):
"""Update connected devices attribute."""
if self._connected_devices == conn_devices:
@ -131,6 +142,8 @@ class AsusWrtSensorDataHandler:
method = self._get_load_avg
elif sensor_type == SENSORS_TYPE_RATES:
method = self._get_rates
elif sensor_type == SENSORS_TYPE_TEMPERATURES:
method = self._get_temperatures
else:
raise RuntimeError(f"Invalid sensor type: {sensor_type}")
@ -349,9 +362,14 @@ class AsusWrtRouter:
SENSORS_TYPE_COUNT: SENSORS_CONNECTED_DEVICE,
SENSORS_TYPE_LOAD_AVG: SENSORS_LOAD_AVG,
SENSORS_TYPE_RATES: SENSORS_RATES,
SENSORS_TYPE_TEMPERATURES: SENSORS_TEMPERATURES,
}
for sensor_type, sensor_names in sensors_types.items():
if sensor_type == SENSORS_TYPE_TEMPERATURES:
sensor_names = await self._get_available_temperature_sensors()
if not sensor_names:
continue
coordinator = await self._sensors_data_handler.get_coordinator(
sensor_type, sensor_type != SENSORS_TYPE_COUNT
)
@ -370,6 +388,23 @@ class AsusWrtRouter:
if self._sensors_data_handler.update_device_count(self._connected_devices):
await coordinator.async_refresh()
async def _get_available_temperature_sensors(self):
"""Check which temperature information is available on the router."""
try:
availability = await self._api.async_find_temperature_commands()
available_sensors = [
SENSORS_TEMPERATURES[i] for i in range(3) if availability[i]
]
except Exception as exc: # pylint: disable=broad-except
_LOGGER.debug(
"Failed checking temperature sensor availability for ASUS router %s. Exception: %s",
self._host,
exc,
)
return []
return available_sensors
async def close(self) -> None:
"""Close the connection."""
if self._api is not None and self._protocol == PROTOCOL_TELNET:

View File

@ -5,12 +5,17 @@ from dataclasses import dataclass
from numbers import Real
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND
from homeassistant.const import (
DATA_GIGABYTES,
DATA_RATE_MEGABITS_PER_SECOND,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.update_coordinator import (
@ -25,6 +30,7 @@ from .const import (
SENSORS_CONNECTED_DEVICE,
SENSORS_LOAD_AVG,
SENSORS_RATES,
SENSORS_TEMPERATURES,
)
from .router import KEY_COORDINATOR, KEY_SENSORS, AsusWrtRouter
@ -114,6 +120,39 @@ CONNECTION_SENSORS: tuple[AsusWrtSensorEntityDescription, ...] = (
factor=1,
precision=1,
),
AsusWrtSensorEntityDescription(
key=SENSORS_TEMPERATURES[0],
name="2.4GHz Temperature",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
factor=1,
precision=1,
),
AsusWrtSensorEntityDescription(
key=SENSORS_TEMPERATURES[1],
name="5GHz Temperature",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
factor=1,
precision=1,
),
AsusWrtSensorEntityDescription(
key=SENSORS_TEMPERATURES[2],
name="CPU Temperature",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
factor=1,
precision=1,
),
)

View File

@ -40,6 +40,7 @@ CONFIG_DATA = {
MOCK_BYTES_TOTAL = [60000000000, 50000000000]
MOCK_CURRENT_TRANSFER_RATES = [20000000, 10000000]
MOCK_LOAD_AVG = [1.1, 1.2, 1.3]
MOCK_TEMPERATURES = {"2.4GHz": 40, "5.0GHz": 0, "CPU": 71.2}
SENSOR_NAMES = [
"Devices Connected",
@ -50,6 +51,9 @@ SENSOR_NAMES = [
"Load Avg (1m)",
"Load Avg (5m)",
"Load Avg (15m)",
"2.4GHz Temperature",
"5GHz Temperature",
"CPU Temperature",
]
@ -62,8 +66,16 @@ def mock_devices_fixture():
}
@pytest.fixture(name="mock_available_temps")
def mock_available_temps_list():
"""Mock a list of available temperature sensors."""
# Only length of 3 booleans is valid. First checking the exception handling.
return [True, False]
@pytest.fixture(name="connect")
def mock_controller_connect(mock_devices):
def mock_controller_connect(mock_devices, mock_available_temps):
"""Mock a successful connection."""
with patch("homeassistant.components.asuswrt.router.AsusWrt") as service_mock:
service_mock.return_value.connection.async_connect = AsyncMock()
@ -88,10 +100,16 @@ def mock_controller_connect(mock_devices):
service_mock.return_value.async_get_loadavg = AsyncMock(
return_value=MOCK_LOAD_AVG
)
service_mock.return_value.async_get_temperature = AsyncMock(
return_value=MOCK_TEMPERATURES
)
service_mock.return_value.async_find_temperature_commands = AsyncMock(
return_value=mock_available_temps
)
yield service_mock
async def test_sensors(hass, connect, mock_devices):
async def test_sensors(hass, connect, mock_devices, mock_available_temps):
"""Test creating an AsusWRT sensor."""
entity_reg = er.async_get(hass)
@ -137,6 +155,11 @@ async def test_sensors(hass, connect, mock_devices):
assert hass.states.get(f"{sensor_prefix}_load_avg_15m").state == "1.3"
assert hass.states.get(f"{sensor_prefix}_devices_connected").state == "2"
# assert temperature availability exception is handled correctly
assert not hass.states.get(f"{sensor_prefix}_2_4ghz_temperature")
assert not hass.states.get(f"{sensor_prefix}_5ghz_temperature")
assert not hass.states.get(f"{sensor_prefix}_cpu_temperature")
# add one device and remove another
mock_devices.pop("a1:b1:c1:d1:e1:f1")
mock_devices["a3:b3:c3:d3:e3:f3"] = Device(
@ -161,3 +184,15 @@ async def test_sensors(hass, connect, mock_devices):
# consider home option not set, device "test" not home
assert hass.states.get(f"{device_tracker.DOMAIN}.test").state == STATE_NOT_HOME
# checking temperature sensors without exceptions
mock_available_temps.append(True)
await hass.config_entries.async_reload(config_entry.entry_id)
await hass.async_block_till_done()
async_fire_time_changed(hass, utcnow() + timedelta(seconds=30))
await hass.async_block_till_done()
assert hass.states.get(f"{sensor_prefix}_load_avg_15m").state == "1.3"
assert hass.states.get(f"{sensor_prefix}_2_4ghz_temperature").state == "40.0"
assert not hass.states.get(f"{sensor_prefix}_5ghz_temperature")
assert hass.states.get(f"{sensor_prefix}_cpu_temperature").state == "71.2"