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.notion.*
|
||||
homeassistant.components.number.*
|
||||
homeassistant.components.oncue.*
|
||||
homeassistant.components.onewire.*
|
||||
homeassistant.components.open_meteo.*
|
||||
homeassistant.components.openuv.*
|
||||
|
|
|
@ -646,6 +646,8 @@ homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
|
|||
tests/components/omnilogic/* @oliver84 @djtimca @gentoosu
|
||||
homeassistant/components/onboarding/* @home-assistant/core
|
||||
tests/components/onboarding/* @home-assistant/core
|
||||
homeassistant/components/oncue/* @bdraco
|
||||
tests/components/oncue/* @bdraco
|
||||
homeassistant/components/ondilo_ico/* @JeromeHXP
|
||||
tests/components/ondilo_ico/* @JeromeHXP
|
||||
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",
|
||||
"octoprint",
|
||||
"omnilogic",
|
||||
"oncue",
|
||||
"ondilo_ico",
|
||||
"onewire",
|
||||
"onvif",
|
||||
|
|
11
mypy.ini
11
mypy.ini
|
@ -1089,6 +1089,17 @@ no_implicit_optional = true
|
|||
warn_return_any = 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.*]
|
||||
check_untyped_defs = true
|
||||
disallow_incomplete_defs = true
|
||||
|
|
|
@ -232,6 +232,9 @@ aionotify==0.2.0
|
|||
# homeassistant.components.notion
|
||||
aionotion==3.0.2
|
||||
|
||||
# homeassistant.components.oncue
|
||||
aiooncue==0.3.0
|
||||
|
||||
# homeassistant.components.acmeda
|
||||
aiopulse==0.4.3
|
||||
|
||||
|
|
|
@ -161,6 +161,9 @@ aionanoleaf==0.1.1
|
|||
# homeassistant.components.notion
|
||||
aionotion==3.0.2
|
||||
|
||||
# homeassistant.components.oncue
|
||||
aiooncue==0.3.0
|
||||
|
||||
# homeassistant.components.acmeda
|
||||
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