Add detailed status for UptimeRobot (#64879)

Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
This commit is contained in:
Simone Chemelli 2022-01-26 16:48:15 +01:00 committed by GitHub
parent eb5c6076af
commit 3f12ce06af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 178 additions and 23 deletions

View File

@ -999,8 +999,8 @@ homeassistant/components/updater/* @home-assistant/core
tests/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @StevenLooman @ehendrix23
tests/components/upnp/* @StevenLooman @ehendrix23
homeassistant/components/uptimerobot/* @ludeeus
tests/components/uptimerobot/* @ludeeus
homeassistant/components/uptimerobot/* @ludeeus @chemelli74
tests/components/uptimerobot/* @ludeeus @chemelli74
homeassistant/components/usb/* @bdraco
tests/components/usb/* @bdraco
homeassistant/components/usgs_earthquakes_feed/* @exxamalte

View File

@ -13,7 +13,7 @@ LOGGER: Logger = getLogger(__package__)
COORDINATOR_UPDATE_INTERVAL: timedelta = timedelta(seconds=10)
DOMAIN: Final = "uptimerobot"
PLATFORMS: Final = [Platform.BINARY_SENSOR]
PLATFORMS: Final = [Platform.BINARY_SENSOR, Platform.SENSOR]
ATTRIBUTION: Final = "Data provided by UptimeRobot"

View File

@ -6,7 +6,7 @@
"pyuptimerobot==21.11.0"
],
"codeowners": [
"@ludeeus"
"@ludeeus", "@chemelli74"
],
"quality_scale": "platinum",
"iot_class": "cloud_polling",

View File

@ -0,0 +1,68 @@
"""UptimeRobot sensor platform."""
from __future__ import annotations
from typing import TypedDict
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import UptimeRobotDataUpdateCoordinator
from .const import DOMAIN
from .entity import UptimeRobotEntity
class StatusValue(TypedDict):
"""Sensor details."""
value: str
icon: str
SENSORS_INFO = {
0: StatusValue(value="pause", icon="mdi:television-pause"),
1: StatusValue(value="not_checked_yet", icon="mdi:television"),
2: StatusValue(value="up", icon="mdi:television-shimmer"),
8: StatusValue(value="seems_down", icon="mdi:television-off"),
9: StatusValue(value="down", icon="mdi:television-off"),
}
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the UptimeRobot sensors."""
coordinator: UptimeRobotDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
[
UptimeRobotSensor(
coordinator,
SensorEntityDescription(
key=str(monitor.id),
name=monitor.friendly_name,
entity_category=EntityCategory.DIAGNOSTIC,
device_class="uptimerobot__monitor_status",
),
monitor=monitor,
)
for monitor in coordinator.data
],
)
class UptimeRobotSensor(UptimeRobotEntity, SensorEntity):
"""Representation of a UptimeRobot sensor."""
@property
def native_value(self) -> str:
"""Return the status of the monitor."""
return SENSORS_INFO[self.monitor.status]["value"]
@property
def icon(self) -> str:
"""Return the status of the monitor."""
return SENSORS_INFO[self.monitor.status]["icon"]

View File

@ -0,0 +1,11 @@
{
"state": {
"uptimerobot__monitor_status": {
"pause": "Pause",
"not_checked_yet": "Not checked yet",
"up": "Up",
"seems_down": "Seems down",
"down": "Down"
}
}
}

View File

@ -0,0 +1,11 @@
{
"state": {
"uptimerobot__monitor_status": {
"down": "Down",
"not_checked_yet": "Not checked yet",
"pause": "Pause",
"seems_down": "Seems down",
"up": "Up"
}
}
}

View File

@ -46,7 +46,10 @@ MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA = {
"source": config_entries.SOURCE_USER,
}
UPTIMEROBOT_TEST_ENTITY = "binary_sensor.test_monitor"
STATE_UP = "up"
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY = "binary_sensor.test_monitor"
UPTIMEROBOT_SENSOR_TEST_ENTITY = "sensor.test_monitor"
class MockApiResponseKey(str, Enum):
@ -94,7 +97,8 @@ async def setup_uptimerobot_integration(hass: HomeAssistant) -> MockConfigEntry:
assert await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY).state == STATE_UP
assert mock_entry.state == config_entries.ConfigEntryState.LOADED
return mock_entry

View File

@ -15,7 +15,7 @@ from homeassistant.util import dt
from .common import (
MOCK_UPTIMEROBOT_MONITOR,
UPTIMEROBOT_TEST_ENTITY,
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY,
setup_uptimerobot_integration,
)
@ -26,7 +26,7 @@ async def test_presentation(hass: HomeAssistant) -> None:
"""Test the presenstation of UptimeRobot binary_sensors."""
await setup_uptimerobot_integration(hass)
entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY)
entity = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)
assert entity.state == STATE_ON
assert entity.attributes["device_class"] == BinarySensorDeviceClass.CONNECTIVITY
@ -38,7 +38,7 @@ async def test_unaviable_on_update_failure(hass: HomeAssistant) -> None:
"""Test entity unaviable on update failure."""
await setup_uptimerobot_integration(hass)
entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY)
entity = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)
assert entity.state == STATE_ON
with patch(
@ -48,5 +48,5 @@ async def test_unaviable_on_update_failure(hass: HomeAssistant) -> None:
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
await hass.async_block_till_done()
entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY)
entity = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)
assert entity.state == STATE_UNAVAILABLE

View File

@ -20,7 +20,7 @@ from homeassistant.util import dt
from .common import (
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA,
MOCK_UPTIMEROBOT_MONITOR,
UPTIMEROBOT_TEST_ENTITY,
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY,
MockApiResponseKey,
mock_uptimerobot_api_response,
setup_uptimerobot_integration,
@ -68,7 +68,7 @@ async def test_reauthentication_trigger_after_setup(
"""Test reauthentication trigger."""
mock_config_entry = await setup_uptimerobot_integration(hass)
binary_sensor = hass.states.get(UPTIMEROBOT_TEST_ENTITY)
binary_sensor = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)
assert mock_config_entry.state == config_entries.ConfigEntryState.LOADED
assert binary_sensor.state == STATE_ON
@ -81,7 +81,10 @@ async def test_reauthentication_trigger_after_setup(
await hass.async_block_till_done()
flows = hass.config_entries.flow.async_progress()
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_UNAVAILABLE
assert (
hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state
== STATE_UNAVAILABLE
)
assert "Authentication failed while fetching uptimerobot data" in caplog.text
@ -107,7 +110,7 @@ async def test_integration_reload(hass: HomeAssistant):
entry = hass.config_entries.async_get_entry(mock_entry.entry_id)
assert entry.state == config_entries.ConfigEntryState.LOADED
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture):
@ -120,7 +123,10 @@ async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture):
):
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
await hass.async_block_till_done()
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_UNAVAILABLE
assert (
hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state
== STATE_UNAVAILABLE
)
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
@ -128,7 +134,7 @@ async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture):
):
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
await hass.async_block_till_done()
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
@ -136,7 +142,10 @@ async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture):
):
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
await hass.async_block_till_done()
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_UNAVAILABLE
assert (
hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state
== STATE_UNAVAILABLE
)
assert "Error fetching uptimerobot data: test error from API" in caplog.text
@ -152,8 +161,8 @@ async def test_device_management(hass: HomeAssistant):
assert devices[0].identifiers == {(DOMAIN, "1234")}
assert devices[0].name == "Test monitor"
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_TEST_ENTITY}_2") is None
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2") is None
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
@ -169,8 +178,10 @@ async def test_device_management(hass: HomeAssistant):
assert devices[0].identifiers == {(DOMAIN, "1234")}
assert devices[1].identifiers == {(DOMAIN, "12345")}
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_TEST_ENTITY}_2").state == STATE_ON
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert (
hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2").state == STATE_ON
)
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
@ -183,5 +194,5 @@ async def test_device_management(hass: HomeAssistant):
assert len(devices) == 1
assert devices[0].identifiers == {(DOMAIN, "1234")}
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_TEST_ENTITY}_2") is None
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2") is None

View File

@ -0,0 +1,50 @@
"""Test UptimeRobot sensor."""
from unittest.mock import patch
from pyuptimerobot import UptimeRobotAuthenticationException
from homeassistant.components.uptimerobot.const import COORDINATOR_UPDATE_INTERVAL
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.util import dt
from .common import (
MOCK_UPTIMEROBOT_MONITOR,
STATE_UP,
UPTIMEROBOT_SENSOR_TEST_ENTITY,
setup_uptimerobot_integration,
)
from tests.common import async_fire_time_changed
SENSOR_ICON = "mdi:television-shimmer"
async def test_presentation(hass: HomeAssistant) -> None:
"""Test the presenstation of UptimeRobot sensors."""
await setup_uptimerobot_integration(hass)
entity = hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY)
assert entity.state == STATE_UP
assert entity.attributes["icon"] == SENSOR_ICON
assert entity.attributes["target"] == MOCK_UPTIMEROBOT_MONITOR["url"]
async def test_unaviable_on_update_failure(hass: HomeAssistant) -> None:
"""Test entity unaviable on update failure."""
await setup_uptimerobot_integration(hass)
entity = hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY)
assert entity.state == STATE_UP
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
side_effect=UptimeRobotAuthenticationException,
):
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
await hass.async_block_till_done()
entity = hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY)
assert entity.state == STATE_UNAVAILABLE