Add Oncue by Kohler integration (#63203)
This commit is contained in:
parent
584e660548
commit
724f5dbf1a
17 changed files with 825 additions and 0 deletions
|
@ -98,6 +98,7 @@ homeassistant.components.no_ip.*
|
||||||
homeassistant.components.notify.*
|
homeassistant.components.notify.*
|
||||||
homeassistant.components.notion.*
|
homeassistant.components.notion.*
|
||||||
homeassistant.components.number.*
|
homeassistant.components.number.*
|
||||||
|
homeassistant.components.oncue.*
|
||||||
homeassistant.components.onewire.*
|
homeassistant.components.onewire.*
|
||||||
homeassistant.components.open_meteo.*
|
homeassistant.components.open_meteo.*
|
||||||
homeassistant.components.openuv.*
|
homeassistant.components.openuv.*
|
||||||
|
|
|
@ -646,6 +646,8 @@ homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
|
||||||
tests/components/omnilogic/* @oliver84 @djtimca @gentoosu
|
tests/components/omnilogic/* @oliver84 @djtimca @gentoosu
|
||||||
homeassistant/components/onboarding/* @home-assistant/core
|
homeassistant/components/onboarding/* @home-assistant/core
|
||||||
tests/components/onboarding/* @home-assistant/core
|
tests/components/onboarding/* @home-assistant/core
|
||||||
|
homeassistant/components/oncue/* @bdraco
|
||||||
|
tests/components/oncue/* @bdraco
|
||||||
homeassistant/components/ondilo_ico/* @JeromeHXP
|
homeassistant/components/ondilo_ico/* @JeromeHXP
|
||||||
tests/components/ondilo_ico/* @JeromeHXP
|
tests/components/ondilo_ico/* @JeromeHXP
|
||||||
homeassistant/components/onewire/* @garbled1 @epenet
|
homeassistant/components/onewire/* @garbled1 @epenet
|
||||||
|
|
54
homeassistant/components/oncue/__init__.py
Normal file
54
homeassistant/components/oncue/__init__.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
"""The Oncue integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from aiooncue import LoginFailedException, Oncue
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
|
from .const import CONNECTION_EXCEPTIONS, DOMAIN
|
||||||
|
|
||||||
|
PLATFORMS: list[str] = [Platform.SENSOR]
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up Oncue from a config entry."""
|
||||||
|
data = entry.data
|
||||||
|
websession = async_get_clientsession(hass)
|
||||||
|
client = Oncue(data[CONF_USERNAME], data[CONF_PASSWORD], websession)
|
||||||
|
try:
|
||||||
|
await client.async_login()
|
||||||
|
except CONNECTION_EXCEPTIONS as ex:
|
||||||
|
raise ConfigEntryNotReady(ex) from ex
|
||||||
|
except LoginFailedException as ex:
|
||||||
|
_LOGGER.error("Failed to login to oncue service: %s", ex)
|
||||||
|
return False
|
||||||
|
|
||||||
|
coordinator = DataUpdateCoordinator(
|
||||||
|
hass,
|
||||||
|
_LOGGER,
|
||||||
|
name=f"Oncue {entry.data[CONF_USERNAME]}",
|
||||||
|
update_interval=timedelta(minutes=10),
|
||||||
|
update_method=client.async_fetch_all,
|
||||||
|
)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
return unload_ok
|
62
homeassistant/components/oncue/config_flow.py
Normal file
62
homeassistant/components/oncue/config_flow.py
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
"""Config flow for Oncue integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from aiooncue import LoginFailedException, Oncue
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
|
from .const import CONNECTION_EXCEPTIONS, DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Handle a config flow for Oncue."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
try:
|
||||||
|
await Oncue(
|
||||||
|
user_input[CONF_USERNAME],
|
||||||
|
user_input[CONF_PASSWORD],
|
||||||
|
async_get_clientsession(self.hass),
|
||||||
|
).async_login()
|
||||||
|
except CONNECTION_EXCEPTIONS:
|
||||||
|
errors["base"] = "cannot_connect"
|
||||||
|
except LoginFailedException:
|
||||||
|
errors["base"] = "invalid_auth"
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
errors["base"] = "unknown"
|
||||||
|
else:
|
||||||
|
normalized_username = user_input[CONF_USERNAME].lower()
|
||||||
|
await self.async_set_unique_id(normalized_username)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=normalized_username, data=user_input
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_USERNAME): str,
|
||||||
|
vol.Required(CONF_PASSWORD): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
)
|
9
homeassistant/components/oncue/const.py
Normal file
9
homeassistant/components/oncue/const.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
"""Constants for the Oncue integration."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
DOMAIN = "oncue"
|
||||||
|
|
||||||
|
CONNECTION_EXCEPTIONS = (asyncio.TimeoutError, aiohttp.ClientError)
|
9
homeassistant/components/oncue/manifest.json
Normal file
9
homeassistant/components/oncue/manifest.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"domain": "oncue",
|
||||||
|
"name": "Oncue by Kohler",
|
||||||
|
"config_flow": true,
|
||||||
|
"documentation": "https://www.home-assistant.io/integrations/oncue",
|
||||||
|
"requirements": ["aiooncue==0.3.0"],
|
||||||
|
"codeowners": ["@bdraco"],
|
||||||
|
"iot_class": "cloud_polling"
|
||||||
|
}
|
164
homeassistant/components/oncue/sensor.py
Normal file
164
homeassistant/components/oncue/sensor.py
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
"""Support for Oncue sensors."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from aiooncue import OncueDevice, OncueSensor
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
SensorDeviceClass,
|
||||||
|
SensorEntity,
|
||||||
|
SensorEntityDescription,
|
||||||
|
SensorStateClass,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import (
|
||||||
|
ELECTRIC_POTENTIAL_VOLT,
|
||||||
|
FREQUENCY_HERTZ,
|
||||||
|
PERCENTAGE,
|
||||||
|
POWER_WATT,
|
||||||
|
PRESSURE_PSI,
|
||||||
|
TEMP_CELSIUS,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
)
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.entity import DeviceInfo
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
from homeassistant.helpers.update_coordinator import (
|
||||||
|
CoordinatorEntity,
|
||||||
|
DataUpdateCoordinator,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="LatestFirmware",
|
||||||
|
icon="mdi:update",
|
||||||
|
),
|
||||||
|
SensorEntityDescription(key="EngineSpeed", icon="mdi:speedometer"),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="EngineOilPressure",
|
||||||
|
native_unit_of_measurement=PRESSURE_PSI,
|
||||||
|
device_class=SensorDeviceClass.PRESSURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="EngineCoolantTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="BatteryVoltage",
|
||||||
|
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||||
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="LubeOilTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="GensetControllerTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="EngineCompartmentTemperature",
|
||||||
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="GeneratorTrueTotalPower",
|
||||||
|
native_unit_of_measurement=POWER_WATT,
|
||||||
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="GeneratorTruePercentOfRatedPower",
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="GeneratorVoltageAverageLineToLine",
|
||||||
|
native_unit_of_measurement=ELECTRIC_POTENTIAL_VOLT,
|
||||||
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="GeneratorFrequency",
|
||||||
|
native_unit_of_measurement=FREQUENCY_HERTZ,
|
||||||
|
device_class=SensorDeviceClass.FREQUENCY,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
),
|
||||||
|
SensorEntityDescription(key="GensetState", icon="mdi:home-lightning-bolt"),
|
||||||
|
SensorEntityDescription(
|
||||||
|
key="GensetControllerTotalOperationTime", icon="mdi:hours-24"
|
||||||
|
),
|
||||||
|
SensorEntityDescription(key="EngineTotalRunTime", icon="mdi:hours-24"),
|
||||||
|
SensorEntityDescription(key="AtsContactorPosition", icon="mdi:electric-switch"),
|
||||||
|
SensorEntityDescription(key="IPAddress", icon="mdi:ip-network"),
|
||||||
|
SensorEntityDescription(key="ConnectedServerIPAddress", icon="mdi:server-network"),
|
||||||
|
)
|
||||||
|
|
||||||
|
SENSOR_MAP = {description.key: description for description in SENSOR_TYPES}
|
||||||
|
|
||||||
|
UNIT_MAPPINGS = {
|
||||||
|
"C": TEMP_CELSIUS,
|
||||||
|
"F": TEMP_FAHRENHEIT,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up sensors."""
|
||||||
|
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
entities: list[OncueSensorEntity] = []
|
||||||
|
devices: dict[str, OncueDevice] = coordinator.data
|
||||||
|
for device_id, device in devices.items():
|
||||||
|
entities.extend(
|
||||||
|
OncueSensorEntity(coordinator, device_id, device, sensor, SENSOR_MAP[key])
|
||||||
|
for key, sensor in device.sensors.items()
|
||||||
|
if key in SENSOR_MAP
|
||||||
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
class OncueSensorEntity(CoordinatorEntity, SensorEntity):
|
||||||
|
"""Representation of an Oncue sensor."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: DataUpdateCoordinator,
|
||||||
|
device_id: str,
|
||||||
|
device: OncueDevice,
|
||||||
|
sensor: OncueSensor,
|
||||||
|
description: SensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the sensor."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self.entity_description = description
|
||||||
|
self._device_id = device_id
|
||||||
|
self._attr_unique_id = f"{device_id}_{description.key}"
|
||||||
|
self._attr_name = f"{device.name} {sensor.display_name}"
|
||||||
|
if not description.native_unit_of_measurement and sensor.unit is not None:
|
||||||
|
self._attr_native_unit_of_measurement = UNIT_MAPPINGS.get(
|
||||||
|
sensor.unit, sensor.unit
|
||||||
|
)
|
||||||
|
self._attr_device_info = DeviceInfo(
|
||||||
|
identifiers={(DOMAIN, device_id)},
|
||||||
|
name=device.name,
|
||||||
|
hw_version=device.hardware_version,
|
||||||
|
sw_version=device.sensors["FirmwareVersion"].display_value,
|
||||||
|
model=device.sensors["GensetModelNumberSelect"].display_value,
|
||||||
|
manufacturer="Kohler",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native_value(self) -> str | None:
|
||||||
|
"""Return the sensors state."""
|
||||||
|
device: OncueDevice = self.coordinator.data[self._device_id]
|
||||||
|
sensor: OncueSensor = device.sensors[self.entity_description.key]
|
||||||
|
return sensor.value
|
20
homeassistant/components/oncue/strings.json
Normal file
20
homeassistant/components/oncue/strings.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
homeassistant/components/oncue/translations/en.json
Normal file
20
homeassistant/components/oncue/translations/en.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Device is already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"cannot_connect": "Failed to connect",
|
||||||
|
"invalid_auth": "Invalid authentication",
|
||||||
|
"unknown": "Unexpected error"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -217,6 +217,7 @@ FLOWS = [
|
||||||
"nzbget",
|
"nzbget",
|
||||||
"octoprint",
|
"octoprint",
|
||||||
"omnilogic",
|
"omnilogic",
|
||||||
|
"oncue",
|
||||||
"ondilo_ico",
|
"ondilo_ico",
|
||||||
"onewire",
|
"onewire",
|
||||||
"onvif",
|
"onvif",
|
||||||
|
|
11
mypy.ini
11
mypy.ini
|
@ -1089,6 +1089,17 @@ no_implicit_optional = true
|
||||||
warn_return_any = true
|
warn_return_any = true
|
||||||
warn_unreachable = true
|
warn_unreachable = true
|
||||||
|
|
||||||
|
[mypy-homeassistant.components.oncue.*]
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
|
||||||
[mypy-homeassistant.components.onewire.*]
|
[mypy-homeassistant.components.onewire.*]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
|
|
|
@ -232,6 +232,9 @@ aionotify==0.2.0
|
||||||
# homeassistant.components.notion
|
# homeassistant.components.notion
|
||||||
aionotion==3.0.2
|
aionotion==3.0.2
|
||||||
|
|
||||||
|
# homeassistant.components.oncue
|
||||||
|
aiooncue==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.acmeda
|
# homeassistant.components.acmeda
|
||||||
aiopulse==0.4.3
|
aiopulse==0.4.3
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,9 @@ aionanoleaf==0.1.1
|
||||||
# homeassistant.components.notion
|
# homeassistant.components.notion
|
||||||
aionotion==3.0.2
|
aionotion==3.0.2
|
||||||
|
|
||||||
|
# homeassistant.components.oncue
|
||||||
|
aiooncue==0.3.0
|
||||||
|
|
||||||
# homeassistant.components.acmeda
|
# homeassistant.components.acmeda
|
||||||
aiopulse==0.4.3
|
aiopulse==0.4.3
|
||||||
|
|
||||||
|
|
197
tests/components/oncue/__init__.py
Normal file
197
tests/components/oncue/__init__.py
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
"""Tests for the Oncue integration."""
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from aiooncue import OncueDevice, OncueSensor
|
||||||
|
|
||||||
|
MOCK_ASYNC_FETCH_ALL = {
|
||||||
|
"123456": OncueDevice(
|
||||||
|
name="My Generator",
|
||||||
|
state="Off",
|
||||||
|
product_name="RDC 2.4",
|
||||||
|
hardware_version="319",
|
||||||
|
serial_number="SERIAL",
|
||||||
|
sensors={
|
||||||
|
"Product": OncueSensor(
|
||||||
|
name="Product",
|
||||||
|
display_name="Controller Type",
|
||||||
|
value="RDC 2.4",
|
||||||
|
display_value="RDC 2.4",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"FirmwareVersion": OncueSensor(
|
||||||
|
name="FirmwareVersion",
|
||||||
|
display_name="Current Firmware",
|
||||||
|
value="2.0.6",
|
||||||
|
display_value="2.0.6",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"LatestFirmware": OncueSensor(
|
||||||
|
name="LatestFirmware",
|
||||||
|
display_name="Latest Firmware",
|
||||||
|
value="2.0.6",
|
||||||
|
display_value="2.0.6",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"EngineSpeed": OncueSensor(
|
||||||
|
name="EngineSpeed",
|
||||||
|
display_name="Engine Speed",
|
||||||
|
value="0",
|
||||||
|
display_value="0 R/min",
|
||||||
|
unit="R/min",
|
||||||
|
),
|
||||||
|
"EngineOilPressure": OncueSensor(
|
||||||
|
name="EngineOilPressure",
|
||||||
|
display_name="Engine Oil Pressure",
|
||||||
|
value=0,
|
||||||
|
display_value="0 Psi",
|
||||||
|
unit="Psi",
|
||||||
|
),
|
||||||
|
"EngineCoolantTemperature": OncueSensor(
|
||||||
|
name="EngineCoolantTemperature",
|
||||||
|
display_name="Engine Coolant Temperature",
|
||||||
|
value=32,
|
||||||
|
display_value="32 F",
|
||||||
|
unit="F",
|
||||||
|
),
|
||||||
|
"BatteryVoltage": OncueSensor(
|
||||||
|
name="BatteryVoltage",
|
||||||
|
display_name="Battery Voltage",
|
||||||
|
value="13.5",
|
||||||
|
display_value="13.5 V",
|
||||||
|
unit="V",
|
||||||
|
),
|
||||||
|
"LubeOilTemperature": OncueSensor(
|
||||||
|
name="LubeOilTemperature",
|
||||||
|
display_name="Lube Oil Temperature",
|
||||||
|
value=32,
|
||||||
|
display_value="32 F",
|
||||||
|
unit="F",
|
||||||
|
),
|
||||||
|
"GensetControllerTemperature": OncueSensor(
|
||||||
|
name="GensetControllerTemperature",
|
||||||
|
display_name="Generator Controller Temperature",
|
||||||
|
value=100.4,
|
||||||
|
display_value="100.4 F",
|
||||||
|
unit="F",
|
||||||
|
),
|
||||||
|
"EngineCompartmentTemperature": OncueSensor(
|
||||||
|
name="EngineCompartmentTemperature",
|
||||||
|
display_name="Engine Compartment Temperature",
|
||||||
|
value=84.2,
|
||||||
|
display_value="84.2 F",
|
||||||
|
unit="F",
|
||||||
|
),
|
||||||
|
"GeneratorTrueTotalPower": OncueSensor(
|
||||||
|
name="GeneratorTrueTotalPower",
|
||||||
|
display_name="Generator True Total Power",
|
||||||
|
value="0.0",
|
||||||
|
display_value="0.0 W",
|
||||||
|
unit="W",
|
||||||
|
),
|
||||||
|
"GeneratorTruePercentOfRatedPower": OncueSensor(
|
||||||
|
name="GeneratorTruePercentOfRatedPower",
|
||||||
|
display_name="Generator True Percent Of Rated Power",
|
||||||
|
value="0",
|
||||||
|
display_value="0 %",
|
||||||
|
unit="%",
|
||||||
|
),
|
||||||
|
"GeneratorVoltageAverageLineToLine": OncueSensor(
|
||||||
|
name="GeneratorVoltageAverageLineToLine",
|
||||||
|
display_name="Generator Voltage Average Line To Line",
|
||||||
|
value="0.0",
|
||||||
|
display_value="0.0 V",
|
||||||
|
unit="V",
|
||||||
|
),
|
||||||
|
"GeneratorFrequency": OncueSensor(
|
||||||
|
name="GeneratorFrequency",
|
||||||
|
display_name="Generator Frequency",
|
||||||
|
value="0.0",
|
||||||
|
display_value="0.0 Hz",
|
||||||
|
unit="Hz",
|
||||||
|
),
|
||||||
|
"GensetSerialNumber": OncueSensor(
|
||||||
|
name="GensetSerialNumber",
|
||||||
|
display_name="Generator Serial Number",
|
||||||
|
value="33FDGMFR0026",
|
||||||
|
display_value="33FDGMFR0026",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"GensetState": OncueSensor(
|
||||||
|
name="GensetState",
|
||||||
|
display_name="Generator State",
|
||||||
|
value="Off",
|
||||||
|
display_value="Off",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"GensetModelNumberSelect": OncueSensor(
|
||||||
|
name="GensetModelNumberSelect",
|
||||||
|
display_name="Genset Model Number Select",
|
||||||
|
value="38 RCLB",
|
||||||
|
display_value="38 RCLB",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"GensetControllerClockTime": OncueSensor(
|
||||||
|
name="GensetControllerClockTime",
|
||||||
|
display_name="Generator Controller Clock Time",
|
||||||
|
value="2022-01-01 17:20:52",
|
||||||
|
display_value="2022-01-01 17:20:52",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"GensetControllerTotalOperationTime": OncueSensor(
|
||||||
|
name="GensetControllerTotalOperationTime",
|
||||||
|
display_name="Generator Controller Total Operation Time",
|
||||||
|
value="16482.0",
|
||||||
|
display_value="16482.0 h",
|
||||||
|
unit="h",
|
||||||
|
),
|
||||||
|
"EngineTotalRunTime": OncueSensor(
|
||||||
|
name="EngineTotalRunTime",
|
||||||
|
display_name="Engine Total Run Time",
|
||||||
|
value="28.1",
|
||||||
|
display_value="28.1 h",
|
||||||
|
unit="h",
|
||||||
|
),
|
||||||
|
"AtsContactorPosition": OncueSensor(
|
||||||
|
name="AtsContactorPosition",
|
||||||
|
display_name="Ats Contactor Position",
|
||||||
|
value="Source1",
|
||||||
|
display_value="Source1",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"IPAddress": OncueSensor(
|
||||||
|
name="IPAddress",
|
||||||
|
display_name="IP Address",
|
||||||
|
value="1.2.3.4:1026",
|
||||||
|
display_value="1.2.3.4:1026",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"ConnectedServerIPAddress": OncueSensor(
|
||||||
|
name="ConnectedServerIPAddress",
|
||||||
|
display_name="Connected Server IP Address",
|
||||||
|
value="40.117.195.28",
|
||||||
|
display_value="40.117.195.28",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
"NetworkConnectionEstablished": OncueSensor(
|
||||||
|
name="NetworkConnectionEstablished",
|
||||||
|
display_name="Network Connection Established",
|
||||||
|
value="true",
|
||||||
|
display_value="True",
|
||||||
|
unit=None,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _patch_login_and_data():
|
||||||
|
@contextmanager
|
||||||
|
def _patcher():
|
||||||
|
with patch("homeassistant.components.oncue.Oncue.async_login",), patch(
|
||||||
|
"homeassistant.components.oncue.Oncue.async_fetch_all",
|
||||||
|
return_value=MOCK_ASYNC_FETCH_ALL,
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
return _patcher()
|
106
tests/components/oncue/test_config_flow.py
Normal file
106
tests/components/oncue/test_config_flow.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
"""Test the Oncue config flow."""
|
||||||
|
import asyncio
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from aiooncue import LoginFailedException
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.oncue.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we get the form."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {}
|
||||||
|
|
||||||
|
with patch("homeassistant.components.oncue.config_flow.Oncue.async_login"), patch(
|
||||||
|
"homeassistant.components.oncue.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry:
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"username": "TEST-username",
|
||||||
|
"password": "test-password",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result2["title"] == "test-username"
|
||||||
|
assert result2["data"] == {
|
||||||
|
"username": "TEST-username",
|
||||||
|
"password": "test-password",
|
||||||
|
}
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_invalid_auth(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle invalid auth."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.oncue.config_flow.Oncue.async_login",
|
||||||
|
side_effect=LoginFailedException,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"username": "test-username",
|
||||||
|
"password": "test-password",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result2["errors"] == {"base": "invalid_auth"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle cannot connect error."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.oncue.config_flow.Oncue.async_login",
|
||||||
|
side_effect=asyncio.TimeoutError,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"username": "test-username",
|
||||||
|
"password": "test-password",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result2["errors"] == {"base": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_unknown_exception(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle unknown exceptions."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.oncue.config_flow.Oncue.async_login",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"username": "test-username",
|
||||||
|
"password": "test-password",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == RESULT_TYPE_FORM
|
||||||
|
assert result2["errors"] == {"base": "unknown"}
|
69
tests/components/oncue/test_init.py
Normal file
69
tests/components/oncue/test_init.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
"""Tests for the oncue component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from aiooncue import LoginFailedException
|
||||||
|
|
||||||
|
from homeassistant.components import oncue
|
||||||
|
from homeassistant.components.oncue.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from . import _patch_login_and_data
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_entry_reload(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that a config entry can be reloaded."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={CONF_USERNAME: "any", CONF_PASSWORD: "any"},
|
||||||
|
unique_id="any",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
with _patch_login_and_data():
|
||||||
|
await async_setup_component(hass, oncue.DOMAIN, {oncue.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state == ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_entry_login_error(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that a config entry is failed on login error."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={CONF_USERNAME: "any", CONF_PASSWORD: "any"},
|
||||||
|
unique_id="any",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.oncue.Oncue.async_login",
|
||||||
|
side_effect=LoginFailedException,
|
||||||
|
):
|
||||||
|
await async_setup_component(hass, oncue.DOMAIN, {oncue.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state == ConfigEntryState.SETUP_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_entry_retry_later(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that a config entry retry on connection error."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={CONF_USERNAME: "any", CONF_PASSWORD: "any"},
|
||||||
|
unique_id="any",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.oncue.Oncue.async_login",
|
||||||
|
side_effect=asyncio.TimeoutError,
|
||||||
|
):
|
||||||
|
await async_setup_component(hass, oncue.DOMAIN, {oncue.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state == ConfigEntryState.SETUP_RETRY
|
94
tests/components/oncue/test_sensor.py
Normal file
94
tests/components/oncue/test_sensor.py
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
"""Tests for the oncue component."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from homeassistant.components import oncue
|
||||||
|
from homeassistant.components.oncue.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from . import _patch_login_and_data
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensors(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that the sensors are setup with the expected values."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={CONF_USERNAME: "any", CONF_PASSWORD: "any"},
|
||||||
|
unique_id="any",
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
with _patch_login_and_data():
|
||||||
|
await async_setup_component(hass, oncue.DOMAIN, {oncue.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert config_entry.state == ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == 18
|
||||||
|
assert hass.states.get("sensor.my_generator_latest_firmware").state == "2.0.6"
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_engine_speed").state == "0"
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_engine_oil_pressure").state == "0"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.my_generator_engine_coolant_temperature").state == "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_battery_voltage").state == "13.5"
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_lube_oil_temperature").state == "0"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.my_generator_generator_controller_temperature").state
|
||||||
|
== "38.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.my_generator_engine_compartment_temperature").state
|
||||||
|
== "29.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.my_generator_generator_true_total_power").state == "0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"sensor.my_generator_generator_true_percent_of_rated_power"
|
||||||
|
).state
|
||||||
|
== "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"sensor.my_generator_generator_voltage_average_line_to_line"
|
||||||
|
).state
|
||||||
|
== "0.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_generator_frequency").state == "0.0"
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_generator_state").state == "Off"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get(
|
||||||
|
"sensor.my_generator_generator_controller_total_operation_time"
|
||||||
|
).state
|
||||||
|
== "16482.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_engine_total_run_time").state == "28.1"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.my_generator_ats_contactor_position").state == "Source1"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.my_generator_ip_address").state == "1.2.3.4:1026"
|
||||||
|
|
||||||
|
assert (
|
||||||
|
hass.states.get("sensor.my_generator_connected_server_ip_address").state
|
||||||
|
== "40.117.195.28"
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue