Add detailed status for UptimeRobot (#64879)
Co-authored-by: Joakim Sørensen <hi@ludeeus.dev>
This commit is contained in:
parent
eb5c6076af
commit
3f12ce06af
10 changed files with 178 additions and 23 deletions
|
@ -999,8 +999,8 @@ homeassistant/components/updater/* @home-assistant/core
|
||||||
tests/components/updater/* @home-assistant/core
|
tests/components/updater/* @home-assistant/core
|
||||||
homeassistant/components/upnp/* @StevenLooman @ehendrix23
|
homeassistant/components/upnp/* @StevenLooman @ehendrix23
|
||||||
tests/components/upnp/* @StevenLooman @ehendrix23
|
tests/components/upnp/* @StevenLooman @ehendrix23
|
||||||
homeassistant/components/uptimerobot/* @ludeeus
|
homeassistant/components/uptimerobot/* @ludeeus @chemelli74
|
||||||
tests/components/uptimerobot/* @ludeeus
|
tests/components/uptimerobot/* @ludeeus @chemelli74
|
||||||
homeassistant/components/usb/* @bdraco
|
homeassistant/components/usb/* @bdraco
|
||||||
tests/components/usb/* @bdraco
|
tests/components/usb/* @bdraco
|
||||||
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
|
||||||
|
|
|
@ -13,7 +13,7 @@ LOGGER: Logger = getLogger(__package__)
|
||||||
COORDINATOR_UPDATE_INTERVAL: timedelta = timedelta(seconds=10)
|
COORDINATOR_UPDATE_INTERVAL: timedelta = timedelta(seconds=10)
|
||||||
|
|
||||||
DOMAIN: Final = "uptimerobot"
|
DOMAIN: Final = "uptimerobot"
|
||||||
PLATFORMS: Final = [Platform.BINARY_SENSOR]
|
PLATFORMS: Final = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||||
|
|
||||||
ATTRIBUTION: Final = "Data provided by UptimeRobot"
|
ATTRIBUTION: Final = "Data provided by UptimeRobot"
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"pyuptimerobot==21.11.0"
|
"pyuptimerobot==21.11.0"
|
||||||
],
|
],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@ludeeus"
|
"@ludeeus", "@chemelli74"
|
||||||
],
|
],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
|
|
68
homeassistant/components/uptimerobot/sensor.py
Normal file
68
homeassistant/components/uptimerobot/sensor.py
Normal 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"]
|
11
homeassistant/components/uptimerobot/strings.sensor.json
Normal file
11
homeassistant/components/uptimerobot/strings.sensor.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"uptimerobot__monitor_status": {
|
||||||
|
"down": "Down",
|
||||||
|
"not_checked_yet": "Not checked yet",
|
||||||
|
"pause": "Pause",
|
||||||
|
"seems_down": "Seems down",
|
||||||
|
"up": "Up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,10 @@ MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA = {
|
||||||
"source": config_entries.SOURCE_USER,
|
"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):
|
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)
|
assert await hass.config_entries.async_setup(mock_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
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
|
assert mock_entry.state == config_entries.ConfigEntryState.LOADED
|
||||||
|
|
||||||
return mock_entry
|
return mock_entry
|
||||||
|
|
|
@ -15,7 +15,7 @@ from homeassistant.util import dt
|
||||||
|
|
||||||
from .common import (
|
from .common import (
|
||||||
MOCK_UPTIMEROBOT_MONITOR,
|
MOCK_UPTIMEROBOT_MONITOR,
|
||||||
UPTIMEROBOT_TEST_ENTITY,
|
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY,
|
||||||
setup_uptimerobot_integration,
|
setup_uptimerobot_integration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ async def test_presentation(hass: HomeAssistant) -> None:
|
||||||
"""Test the presenstation of UptimeRobot binary_sensors."""
|
"""Test the presenstation of UptimeRobot binary_sensors."""
|
||||||
await setup_uptimerobot_integration(hass)
|
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.state == STATE_ON
|
||||||
assert entity.attributes["device_class"] == BinarySensorDeviceClass.CONNECTIVITY
|
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."""
|
"""Test entity unaviable on update failure."""
|
||||||
await setup_uptimerobot_integration(hass)
|
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.state == STATE_ON
|
||||||
|
|
||||||
with patch(
|
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)
|
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
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
|
assert entity.state == STATE_UNAVAILABLE
|
||||||
|
|
|
@ -20,7 +20,7 @@ from homeassistant.util import dt
|
||||||
from .common import (
|
from .common import (
|
||||||
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA,
|
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA,
|
||||||
MOCK_UPTIMEROBOT_MONITOR,
|
MOCK_UPTIMEROBOT_MONITOR,
|
||||||
UPTIMEROBOT_TEST_ENTITY,
|
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY,
|
||||||
MockApiResponseKey,
|
MockApiResponseKey,
|
||||||
mock_uptimerobot_api_response,
|
mock_uptimerobot_api_response,
|
||||||
setup_uptimerobot_integration,
|
setup_uptimerobot_integration,
|
||||||
|
@ -68,7 +68,7 @@ async def test_reauthentication_trigger_after_setup(
|
||||||
"""Test reauthentication trigger."""
|
"""Test reauthentication trigger."""
|
||||||
mock_config_entry = await setup_uptimerobot_integration(hass)
|
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 mock_config_entry.state == config_entries.ConfigEntryState.LOADED
|
||||||
assert binary_sensor.state == STATE_ON
|
assert binary_sensor.state == STATE_ON
|
||||||
|
|
||||||
|
@ -81,7 +81,10 @@ async def test_reauthentication_trigger_after_setup(
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
flows = hass.config_entries.flow.async_progress()
|
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
|
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)
|
entry = hass.config_entries.async_get_entry(mock_entry.entry_id)
|
||||||
assert entry.state == config_entries.ConfigEntryState.LOADED
|
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):
|
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)
|
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
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(
|
with patch(
|
||||||
"pyuptimerobot.UptimeRobot.async_get_monitors",
|
"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)
|
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
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(
|
with patch(
|
||||||
"pyuptimerobot.UptimeRobot.async_get_monitors",
|
"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)
|
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
|
||||||
await hass.async_block_till_done()
|
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
|
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].identifiers == {(DOMAIN, "1234")}
|
||||||
assert devices[0].name == "Test monitor"
|
assert devices[0].name == "Test monitor"
|
||||||
|
|
||||||
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(f"{UPTIMEROBOT_TEST_ENTITY}_2") is None
|
assert hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2") is None
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"pyuptimerobot.UptimeRobot.async_get_monitors",
|
"pyuptimerobot.UptimeRobot.async_get_monitors",
|
||||||
|
@ -169,8 +178,10 @@ async def test_device_management(hass: HomeAssistant):
|
||||||
assert devices[0].identifiers == {(DOMAIN, "1234")}
|
assert devices[0].identifiers == {(DOMAIN, "1234")}
|
||||||
assert devices[1].identifiers == {(DOMAIN, "12345")}
|
assert devices[1].identifiers == {(DOMAIN, "12345")}
|
||||||
|
|
||||||
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(f"{UPTIMEROBOT_TEST_ENTITY}_2").state == STATE_ON
|
assert (
|
||||||
|
hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2").state == STATE_ON
|
||||||
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"pyuptimerobot.UptimeRobot.async_get_monitors",
|
"pyuptimerobot.UptimeRobot.async_get_monitors",
|
||||||
|
@ -183,5 +194,5 @@ async def test_device_management(hass: HomeAssistant):
|
||||||
assert len(devices) == 1
|
assert len(devices) == 1
|
||||||
assert devices[0].identifiers == {(DOMAIN, "1234")}
|
assert devices[0].identifiers == {(DOMAIN, "1234")}
|
||||||
|
|
||||||
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(f"{UPTIMEROBOT_TEST_ENTITY}_2") is None
|
assert hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2") is None
|
||||||
|
|
50
tests/components/uptimerobot/test_sensor.py
Normal file
50
tests/components/uptimerobot/test_sensor.py
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue