Add Oncue by Kohler integration (#63203)

This commit is contained in:
J. Nick Koston 2022-01-02 09:15:39 -10:00 committed by GitHub
parent 584e660548
commit 724f5dbf1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 825 additions and 0 deletions

View File

@ -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.*

View File

@ -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

View 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

View 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,
)

View File

@ -0,0 +1,9 @@
"""Constants for the Oncue integration."""
import asyncio
import aiohttp
DOMAIN = "oncue"
CONNECTION_EXCEPTIONS = (asyncio.TimeoutError, aiohttp.ClientError)

View 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"
}

View 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

View 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%]"
}
}
}

View 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"
}
}
}
}
}

View File

@ -217,6 +217,7 @@ FLOWS = [
"nzbget",
"octoprint",
"omnilogic",
"oncue",
"ondilo_ico",
"onewire",
"onvif",

View File

@ -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

View File

@ -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

View File

@ -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

View 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()

View 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"}

View 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

View 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"
)