Create greeneye_monitor entities when monitor connects (#66710)

This commit is contained in:
Jonathan Keljo 2022-02-23 10:09:12 -08:00 committed by GitHub
parent 93fab1f996
commit a08165a8d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 165 additions and 148 deletions

View File

@ -53,15 +53,25 @@ async def async_setup_platform(
if not discovery_info: if not discovery_info:
return return
monitor_configs = discovery_info[CONF_MONITORS]
def on_new_monitor(monitor: greeneye.monitor.Monitor) -> None:
monitor_config = next(
filter(
lambda monitor_config: monitor_config[CONF_SERIAL_NUMBER]
== monitor.serial_number,
monitor_configs,
),
None,
)
if monitor_config:
entities: list[GEMSensor] = [] entities: list[GEMSensor] = []
for monitor_config in discovery_info[CONF_MONITORS]:
monitor_serial_number = monitor_config[CONF_SERIAL_NUMBER]
channel_configs = monitor_config[CONF_CHANNELS] channel_configs = monitor_config[CONF_CHANNELS]
for sensor in channel_configs: for sensor in channel_configs:
entities.append( entities.append(
CurrentSensor( CurrentSensor(
monitor_serial_number, monitor,
sensor[CONF_NUMBER], sensor[CONF_NUMBER],
sensor[CONF_NAME], sensor[CONF_NAME],
sensor[CONF_NET_METERING], sensor[CONF_NET_METERING],
@ -72,7 +82,7 @@ async def async_setup_platform(
for sensor in pulse_counter_configs: for sensor in pulse_counter_configs:
entities.append( entities.append(
PulseCounter( PulseCounter(
monitor_serial_number, monitor,
sensor[CONF_NUMBER], sensor[CONF_NUMBER],
sensor[CONF_NAME], sensor[CONF_NAME],
sensor[CONF_COUNTED_QUANTITY], sensor[CONF_COUNTED_QUANTITY],
@ -85,7 +95,7 @@ async def async_setup_platform(
for sensor in temperature_sensor_configs[CONF_SENSORS]: for sensor in temperature_sensor_configs[CONF_SENSORS]:
entities.append( entities.append(
TemperatureSensor( TemperatureSensor(
monitor_serial_number, monitor,
sensor[CONF_NUMBER], sensor[CONF_NUMBER],
sensor[CONF_NAME], sensor[CONF_NAME],
temperature_sensor_configs[CONF_TEMPERATURE_UNIT], temperature_sensor_configs[CONF_TEMPERATURE_UNIT],
@ -95,14 +105,19 @@ async def async_setup_platform(
voltage_sensor_configs = monitor_config[CONF_VOLTAGE_SENSORS] voltage_sensor_configs = monitor_config[CONF_VOLTAGE_SENSORS]
for sensor in voltage_sensor_configs: for sensor in voltage_sensor_configs:
entities.append( entities.append(
VoltageSensor( VoltageSensor(monitor, sensor[CONF_NUMBER], sensor[CONF_NAME])
monitor_serial_number,
sensor[CONF_NUMBER],
sensor[CONF_NAME],
)
) )
async_add_entities(entities) async_add_entities(entities)
monitor_configs.remove(monitor_config)
if len(monitor_configs) == 0:
monitors.remove_listener(on_new_monitor)
monitors: greeneye.Monitors = hass.data[DATA_GREENEYE_MONITOR]
monitors.add_listener(on_new_monitor)
for monitor in monitors.monitors.values():
on_new_monitor(monitor)
UnderlyingSensorType = Union[ UnderlyingSensorType = Union[
@ -119,13 +134,19 @@ class GEMSensor(SensorEntity):
_attr_should_poll = False _attr_should_poll = False
def __init__( def __init__(
self, monitor_serial_number: int, name: str, sensor_type: str, number: int self,
monitor: greeneye.monitor.Monitor,
name: str,
sensor_type: str,
sensor: UnderlyingSensorType,
number: int,
) -> None: ) -> None:
"""Construct the entity.""" """Construct the entity."""
self._monitor_serial_number = monitor_serial_number self._monitor = monitor
self._monitor_serial_number = self._monitor.serial_number
self._attr_name = name self._attr_name = name
self._monitor: greeneye.monitor.Monitor | None = None
self._sensor_type = sensor_type self._sensor_type = sensor_type
self._sensor: UnderlyingSensorType = sensor
self._number = number self._number = number
self._attr_unique_id = ( self._attr_unique_id = (
f"{self._monitor_serial_number}-{self._sensor_type}-{self._number}" f"{self._monitor_serial_number}-{self._sensor_type}-{self._number}"
@ -133,37 +154,12 @@ class GEMSensor(SensorEntity):
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
"""Wait for and connect to the sensor.""" """Wait for and connect to the sensor."""
monitors = self.hass.data[DATA_GREENEYE_MONITOR] self._sensor.add_listener(self.async_write_ha_state)
if not self._try_connect_to_monitor(monitors):
monitors.add_listener(self._on_new_monitor)
def _on_new_monitor(self, monitor: greeneye.monitor.Monitor) -> None:
monitors = self.hass.data[DATA_GREENEYE_MONITOR]
if self._try_connect_to_monitor(monitors):
monitors.remove_listener(self._on_new_monitor)
async def async_will_remove_from_hass(self) -> None: async def async_will_remove_from_hass(self) -> None:
"""Remove listener from the sensor.""" """Remove listener from the sensor."""
if self._sensor: if self._sensor:
self._sensor.remove_listener(self.async_write_ha_state) self._sensor.remove_listener(self.async_write_ha_state)
else:
monitors = self.hass.data[DATA_GREENEYE_MONITOR]
monitors.remove_listener(self._on_new_monitor)
def _try_connect_to_monitor(self, monitors: greeneye.Monitors) -> bool:
self._monitor = monitors.monitors.get(self._monitor_serial_number)
if not self._sensor:
return False
self._sensor.add_listener(self.async_write_ha_state)
self.async_write_ha_state()
return True
@property
def _sensor(self) -> UnderlyingSensorType | None:
raise NotImplementedError()
class CurrentSensor(GEMSensor): class CurrentSensor(GEMSensor):
@ -173,30 +169,25 @@ class CurrentSensor(GEMSensor):
_attr_device_class = SensorDeviceClass.POWER _attr_device_class = SensorDeviceClass.POWER
def __init__( def __init__(
self, monitor_serial_number: int, number: int, name: str, net_metering: bool self,
monitor: greeneye.monitor.Monitor,
number: int,
name: str,
net_metering: bool,
) -> None: ) -> None:
"""Construct the entity.""" """Construct the entity."""
super().__init__(monitor_serial_number, name, "current", number) super().__init__(monitor, name, "current", monitor.channels[number - 1], number)
self._sensor: greeneye.monitor.Channel = self._sensor
self._net_metering = net_metering self._net_metering = net_metering
@property
def _sensor(self) -> greeneye.monitor.Channel | None:
return self._monitor.channels[self._number - 1] if self._monitor else None
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
"""Return the current number of watts being used by the channel.""" """Return the current number of watts being used by the channel."""
if not self._sensor:
return None
return self._sensor.watts return self._sensor.watts
@property @property
def extra_state_attributes(self) -> dict[str, Any] | None: def extra_state_attributes(self) -> dict[str, Any] | None:
"""Return total wattseconds in the state dictionary.""" """Return total wattseconds in the state dictionary."""
if not self._sensor:
return None
if self._net_metering: if self._net_metering:
watt_seconds = self._sensor.polarized_watt_seconds watt_seconds = self._sensor.polarized_watt_seconds
else: else:
@ -212,7 +203,7 @@ class PulseCounter(GEMSensor):
def __init__( def __init__(
self, self,
monitor_serial_number: int, monitor: greeneye.monitor.Monitor,
number: int, number: int,
name: str, name: str,
counted_quantity: str, counted_quantity: str,
@ -220,19 +211,18 @@ class PulseCounter(GEMSensor):
counted_quantity_per_pulse: float, counted_quantity_per_pulse: float,
) -> None: ) -> None:
"""Construct the entity.""" """Construct the entity."""
super().__init__(monitor_serial_number, name, "pulse", number) super().__init__(
monitor, name, "pulse", monitor.pulse_counters[number - 1], number
)
self._sensor: greeneye.monitor.PulseCounter = self._sensor
self._counted_quantity_per_pulse = counted_quantity_per_pulse self._counted_quantity_per_pulse = counted_quantity_per_pulse
self._time_unit = time_unit self._time_unit = time_unit
self._attr_native_unit_of_measurement = f"{counted_quantity}/{self._time_unit}" self._attr_native_unit_of_measurement = f"{counted_quantity}/{self._time_unit}"
@property
def _sensor(self) -> greeneye.monitor.PulseCounter | None:
return self._monitor.pulse_counters[self._number - 1] if self._monitor else None
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
"""Return the current rate of change for the given pulse counter.""" """Return the current rate of change for the given pulse counter."""
if not self._sensor or self._sensor.pulses_per_second is None: if self._sensor.pulses_per_second is None:
return None return None
result = ( result = (
@ -258,11 +248,8 @@ class PulseCounter(GEMSensor):
) )
@property @property
def extra_state_attributes(self) -> dict[str, Any] | None: def extra_state_attributes(self) -> dict[str, Any]:
"""Return total pulses in the data dictionary.""" """Return total pulses in the data dictionary."""
if not self._sensor:
return None
return {DATA_PULSES: self._sensor.pulses} return {DATA_PULSES: self._sensor.pulses}
@ -272,26 +259,18 @@ class TemperatureSensor(GEMSensor):
_attr_device_class = SensorDeviceClass.TEMPERATURE _attr_device_class = SensorDeviceClass.TEMPERATURE
def __init__( def __init__(
self, monitor_serial_number: int, number: int, name: str, unit: str self, monitor: greeneye.monitor.Monitor, number: int, name: str, unit: str
) -> None: ) -> None:
"""Construct the entity.""" """Construct the entity."""
super().__init__(monitor_serial_number, name, "temp", number) super().__init__(
self._attr_native_unit_of_measurement = unit monitor, name, "temp", monitor.temperature_sensors[number - 1], number
@property
def _sensor(self) -> greeneye.monitor.TemperatureSensor | None:
return (
self._monitor.temperature_sensors[self._number - 1]
if self._monitor
else None
) )
self._sensor: greeneye.monitor.TemperatureSensor = self._sensor
self._attr_native_unit_of_measurement = unit
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
"""Return the current temperature being reported by this sensor.""" """Return the current temperature being reported by this sensor."""
if not self._sensor:
return None
return self._sensor.temperature return self._sensor.temperature
@ -301,19 +280,14 @@ class VoltageSensor(GEMSensor):
_attr_native_unit_of_measurement = ELECTRIC_POTENTIAL_VOLT _attr_native_unit_of_measurement = ELECTRIC_POTENTIAL_VOLT
_attr_device_class = SensorDeviceClass.VOLTAGE _attr_device_class = SensorDeviceClass.VOLTAGE
def __init__(self, monitor_serial_number: int, number: int, name: str) -> None: def __init__(
self, monitor: greeneye.monitor.Monitor, number: int, name: str
) -> None:
"""Construct the entity.""" """Construct the entity."""
super().__init__(monitor_serial_number, name, "volts", number) super().__init__(monitor, name, "volts", monitor.voltage_sensor, number)
self._sensor: greeneye.monitor.VoltageSensor = self._sensor
@property
def _sensor(self) -> greeneye.monitor.VoltageSensor | None:
"""Wire the updates to the monitor itself, since there is no voltage element in the API."""
return self._monitor.voltage_sensor if self._monitor else None
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
"""Return the current voltage being reported by this sensor.""" """Return the current voltage being reported by this sensor."""
if not self._sensor:
return None
return self._sensor.voltage return self._sensor.voltage

View File

@ -239,3 +239,13 @@ def mock_monitor(serial_number: int) -> MagicMock:
monitor.temperature_sensors = [mock_temperature_sensor() for i in range(0, 8)] monitor.temperature_sensors = [mock_temperature_sensor() for i in range(0, 8)]
monitor.channels = [mock_channel() for i in range(0, 32)] monitor.channels = [mock_channel() for i in range(0, 32)]
return monitor return monitor
async def connect_monitor(
hass: HomeAssistant, monitors: AsyncMock, serial_number: int
) -> MagicMock:
"""Simulate a monitor connecting to Home Assistant. Returns the mock monitor API object."""
monitor = mock_monitor(serial_number)
monitors.add_monitor(monitor)
await hass.async_block_till_done()
return monitor

View File

@ -18,6 +18,7 @@ from .common import (
SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS,
SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS,
SINGLE_MONITOR_SERIAL_NUMBER, SINGLE_MONITOR_SERIAL_NUMBER,
connect_monitor,
setup_greeneye_monitor_component_with_config, setup_greeneye_monitor_component_with_config,
) )
from .conftest import ( from .conftest import (
@ -53,7 +54,7 @@ async def test_setup_creates_temperature_entities(
assert await setup_greeneye_monitor_component_with_config( assert await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS
) )
await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_temperature_sensor_registered( assert_temperature_sensor_registered(
hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "temp_a" hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "temp_a"
) )
@ -87,7 +88,7 @@ async def test_setup_creates_pulse_counter_entities(
assert await setup_greeneye_monitor_component_with_config( assert await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS
) )
await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_pulse_counter_registered( assert_pulse_counter_registered(
hass, hass,
SINGLE_MONITOR_SERIAL_NUMBER, SINGLE_MONITOR_SERIAL_NUMBER,
@ -124,7 +125,7 @@ async def test_setup_creates_power_sensor_entities(
assert await setup_greeneye_monitor_component_with_config( assert await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS
) )
await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "channel 1") assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "channel 1")
assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 2, "channel two") assert_power_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 2, "channel two")
@ -136,7 +137,7 @@ async def test_setup_creates_voltage_sensor_entities(
assert await setup_greeneye_monitor_component_with_config( assert await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
) )
await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_voltage_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "voltage 1") assert_voltage_sensor_registered(hass, SINGLE_MONITOR_SERIAL_NUMBER, 1, "voltage 1")
@ -147,6 +148,10 @@ async def test_multi_monitor_config(hass: HomeAssistant, monitors: AsyncMock) ->
MULTI_MONITOR_CONFIG, MULTI_MONITOR_CONFIG,
) )
await connect_monitor(hass, monitors, 1)
await connect_monitor(hass, monitors, 2)
await connect_monitor(hass, monitors, 3)
assert_temperature_sensor_registered(hass, 1, 1, "unit_1_temp_1") assert_temperature_sensor_registered(hass, 1, 1, "unit_1_temp_1")
assert_temperature_sensor_registered(hass, 2, 1, "unit_2_temp_1") assert_temperature_sensor_registered(hass, 2, 1, "unit_2_temp_1")
assert_temperature_sensor_registered(hass, 3, 1, "unit_3_temp_1") assert_temperature_sensor_registered(hass, 3, 1, "unit_3_temp_1")

View File

@ -1,5 +1,5 @@
"""Tests for greeneye_monitor sensors.""" """Tests for greeneye_monitor sensors."""
from unittest.mock import AsyncMock, MagicMock from unittest.mock import AsyncMock
from homeassistant.components.greeneye_monitor.sensor import ( from homeassistant.components.greeneye_monitor.sensor import (
DATA_PULSES, DATA_PULSES,
@ -19,38 +19,50 @@ from .common import (
SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS,
SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS,
SINGLE_MONITOR_SERIAL_NUMBER, SINGLE_MONITOR_SERIAL_NUMBER,
mock_monitor, connect_monitor,
setup_greeneye_monitor_component_with_config, setup_greeneye_monitor_component_with_config,
) )
from .conftest import assert_sensor_state from .conftest import assert_sensor_state
async def test_disable_sensor_before_monitor_connected( async def test_sensor_does_not_exist_before_monitor_connected(
hass: HomeAssistant, monitors: AsyncMock hass: HomeAssistant, monitors: AsyncMock
) -> None: ) -> None:
"""Test that a sensor disabled before its monitor connected stops listening for new monitors.""" """Test that a sensor does not exist before its monitor is connected."""
# The sensor base class handles connecting the monitor, so we test this with a single voltage sensor for ease # The sensor base class handles connecting the monitor, so we test this with a single voltage sensor for ease
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
) )
assert len(monitors.listeners) == 1 entity_registry = get_entity_registry(hass)
await disable_entity(hass, "sensor.voltage_1") assert entity_registry.async_get("sensor.voltage_1") is None
assert len(monitors.listeners) == 0 # Make sure we cleaned up the listener
async def test_updates_state_when_monitor_connected( async def test_sensors_created_when_monitor_connected(
hass: HomeAssistant, monitors: AsyncMock hass: HomeAssistant, monitors: AsyncMock
) -> None: ) -> None:
"""Test that a sensor updates its state when its monitor first connects.""" """Test that sensors get created when the monitor first connects."""
# The sensor base class handles updating the state on connection, so we test this with a single voltage sensor for ease # The sensor base class handles updating the state on connection, so we test this with a single voltage sensor for ease
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
) )
assert_sensor_state(hass, "sensor.voltage_1", STATE_UNKNOWN)
assert len(monitors.listeners) == 1 assert len(monitors.listeners) == 1
connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert len(monitors.listeners) == 0 # Make sure we cleaned up the listener
assert_sensor_state(hass, "sensor.voltage_1", "120.0")
async def test_sensors_created_during_setup_if_monitor_already_connected(
hass: HomeAssistant, monitors: AsyncMock
) -> None:
"""Test that sensors get created during setup if the monitor happens to connect really quickly."""
# The sensor base class handles updating the state on connection, so we test this with a single voltage sensor for ease
await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
)
assert len(monitors.listeners) == 0 # Make sure we cleaned up the listener assert len(monitors.listeners) == 0 # Make sure we cleaned up the listener
assert_sensor_state(hass, "sensor.voltage_1", "120.0") assert_sensor_state(hass, "sensor.voltage_1", "120.0")
@ -63,7 +75,7 @@ async def test_disable_sensor_after_monitor_connected(
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
) )
monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert len(monitor.voltage_sensor.listeners) == 1 assert len(monitor.voltage_sensor.listeners) == 1
await disable_entity(hass, "sensor.voltage_1") await disable_entity(hass, "sensor.voltage_1")
@ -78,7 +90,7 @@ async def test_updates_state_when_sensor_pushes(
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
) )
monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_sensor_state(hass, "sensor.voltage_1", "120.0") assert_sensor_state(hass, "sensor.voltage_1", "120.0")
monitor.voltage_sensor.voltage = 119.8 monitor.voltage_sensor.voltage = 119.8
@ -93,7 +105,7 @@ async def test_power_sensor_initially_unknown(
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS
) )
connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_sensor_state( assert_sensor_state(
hass, "sensor.channel_1", STATE_UNKNOWN, {DATA_WATT_SECONDS: 1000} hass, "sensor.channel_1", STATE_UNKNOWN, {DATA_WATT_SECONDS: 1000}
) )
@ -109,7 +121,7 @@ async def test_power_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None:
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS hass, SINGLE_MONITOR_CONFIG_POWER_SENSORS
) )
monitor = connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
monitor.channels[0].watts = 120.0 monitor.channels[0].watts = 120.0
monitor.channels[1].watts = 120.0 monitor.channels[1].watts = 120.0
monitor.channels[0].notify_all_listeners() monitor.channels[0].notify_all_listeners()
@ -120,12 +132,35 @@ async def test_power_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None:
assert_sensor_state(hass, "sensor.channel_two", "120.0", {DATA_WATT_SECONDS: -400}) assert_sensor_state(hass, "sensor.channel_two", "120.0", {DATA_WATT_SECONDS: -400})
async def test_pulse_counter_initially_unknown(
hass: HomeAssistant, monitors: AsyncMock
) -> None:
"""Test that the pulse counter sensor can handle its initial state being unknown (since the GEM API needs at least two packets to arrive before it can compute pulses per time)."""
await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS
)
monitor = await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
monitor.pulse_counters[0].pulses_per_second = None
monitor.pulse_counters[1].pulses_per_second = None
monitor.pulse_counters[2].pulses_per_second = None
monitor.pulse_counters[0].notify_all_listeners()
monitor.pulse_counters[1].notify_all_listeners()
monitor.pulse_counters[2].notify_all_listeners()
assert_sensor_state(hass, "sensor.pulse_a", STATE_UNKNOWN, {DATA_PULSES: 1000})
# This counter was configured with each pulse meaning 0.5 gallons and
# wanting to show gallons per minute, so 10 pulses per second -> 300 gal/min
assert_sensor_state(hass, "sensor.pulse_2", STATE_UNKNOWN, {DATA_PULSES: 1000})
# This counter was configured with each pulse meaning 0.5 gallons and
# wanting to show gallons per hour, so 10 pulses per second -> 18000 gal/hr
assert_sensor_state(hass, "sensor.pulse_3", STATE_UNKNOWN, {DATA_PULSES: 1000})
async def test_pulse_counter(hass: HomeAssistant, monitors: AsyncMock) -> None: async def test_pulse_counter(hass: HomeAssistant, monitors: AsyncMock) -> None:
"""Test that a pulse counter sensor reports its values properly, including calculating different units.""" """Test that a pulse counter sensor reports its values properly, including calculating different units."""
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS hass, SINGLE_MONITOR_CONFIG_PULSE_COUNTERS
) )
connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_sensor_state(hass, "sensor.pulse_a", "10.0", {DATA_PULSES: 1000}) assert_sensor_state(hass, "sensor.pulse_a", "10.0", {DATA_PULSES: 1000})
# This counter was configured with each pulse meaning 0.5 gallons and # This counter was configured with each pulse meaning 0.5 gallons and
# wanting to show gallons per minute, so 10 pulses per second -> 300 gal/min # wanting to show gallons per minute, so 10 pulses per second -> 300 gal/min
@ -140,7 +175,7 @@ async def test_temperature_sensor(hass: HomeAssistant, monitors: AsyncMock) -> N
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS hass, SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS
) )
connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
# The config says that the sensor is reporting in Fahrenheit; if we set that up # The config says that the sensor is reporting in Fahrenheit; if we set that up
# properly, HA will have converted that to Celsius by default. # properly, HA will have converted that to Celsius by default.
assert_sensor_state(hass, "sensor.temp_a", "0.0") assert_sensor_state(hass, "sensor.temp_a", "0.0")
@ -151,28 +186,21 @@ async def test_voltage_sensor(hass: HomeAssistant, monitors: AsyncMock) -> None:
await setup_greeneye_monitor_component_with_config( await setup_greeneye_monitor_component_with_config(
hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS hass, SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS
) )
connect_monitor(monitors, SINGLE_MONITOR_SERIAL_NUMBER) await connect_monitor(hass, monitors, SINGLE_MONITOR_SERIAL_NUMBER)
assert_sensor_state(hass, "sensor.voltage_1", "120.0") assert_sensor_state(hass, "sensor.voltage_1", "120.0")
async def test_multi_monitor_sensors(hass: HomeAssistant, monitors: AsyncMock) -> None: async def test_multi_monitor_sensors(hass: HomeAssistant, monitors: AsyncMock) -> None:
"""Test that sensors still work when multiple monitors are registered.""" """Test that sensors still work when multiple monitors are registered."""
await setup_greeneye_monitor_component_with_config(hass, MULTI_MONITOR_CONFIG) await setup_greeneye_monitor_component_with_config(hass, MULTI_MONITOR_CONFIG)
connect_monitor(monitors, 1) await connect_monitor(hass, monitors, 1)
connect_monitor(monitors, 2) await connect_monitor(hass, monitors, 2)
connect_monitor(monitors, 3) await connect_monitor(hass, monitors, 3)
assert_sensor_state(hass, "sensor.unit_1_temp_1", "32.0") assert_sensor_state(hass, "sensor.unit_1_temp_1", "32.0")
assert_sensor_state(hass, "sensor.unit_2_temp_1", "0.0") assert_sensor_state(hass, "sensor.unit_2_temp_1", "0.0")
assert_sensor_state(hass, "sensor.unit_3_temp_1", "32.0") assert_sensor_state(hass, "sensor.unit_3_temp_1", "32.0")
def connect_monitor(monitors: AsyncMock, serial_number: int) -> MagicMock:
"""Simulate a monitor connecting to Home Assistant. Returns the mock monitor API object."""
monitor = mock_monitor(serial_number)
monitors.add_monitor(monitor)
return monitor
async def disable_entity(hass: HomeAssistant, entity_id: str) -> None: async def disable_entity(hass: HomeAssistant, entity_id: str) -> None:
"""Disable the given entity.""" """Disable the given entity."""
entity_registry = get_entity_registry(hass) entity_registry = get_entity_registry(hass)