"""Common helpers for greeneye_monitor tests.""" from __future__ import annotations from typing import Any from unittest.mock import AsyncMock, MagicMock from homeassistant.components.greeneye_monitor import ( CONF_CHANNELS, CONF_COUNTED_QUANTITY, CONF_COUNTED_QUANTITY_PER_PULSE, CONF_MONITORS, CONF_NET_METERING, CONF_NUMBER, CONF_PULSE_COUNTERS, CONF_SERIAL_NUMBER, CONF_TEMPERATURE_SENSORS, CONF_TIME_UNIT, CONF_VOLTAGE_SENSORS, DOMAIN, ) from homeassistant.const import ( CONF_NAME, CONF_PORT, CONF_SENSORS, CONF_TEMPERATURE_UNIT, ) from homeassistant.core import HomeAssistant from homeassistant.helpers.typing import ConfigType from homeassistant.setup import async_setup_component SINGLE_MONITOR_SERIAL_NUMBER = 110011 def make_single_monitor_config_with_sensors(sensors: dict[str, Any]) -> dict[str, Any]: """Wrap the given sensor config in the boilerplate for a single monitor with serial number SINGLE_MONITOR_SERIAL_NUMBER.""" return { DOMAIN: { CONF_PORT: 7513, CONF_MONITORS: [ { CONF_SERIAL_NUMBER: f"00{SINGLE_MONITOR_SERIAL_NUMBER}", **sensors, } ], } } SINGLE_MONITOR_CONFIG_NO_SENSORS = make_single_monitor_config_with_sensors({}) SINGLE_MONITOR_CONFIG_PULSE_COUNTERS = make_single_monitor_config_with_sensors( { CONF_PULSE_COUNTERS: [ { CONF_NUMBER: 1, CONF_NAME: "pulse_a", CONF_COUNTED_QUANTITY: "pulses", CONF_COUNTED_QUANTITY_PER_PULSE: 1.0, CONF_TIME_UNIT: "s", }, { CONF_NUMBER: 2, CONF_NAME: "pulse_2", CONF_COUNTED_QUANTITY: "gal", CONF_COUNTED_QUANTITY_PER_PULSE: 0.5, CONF_TIME_UNIT: "min", }, { CONF_NUMBER: 3, CONF_NAME: "pulse_3", CONF_COUNTED_QUANTITY: "gal", CONF_COUNTED_QUANTITY_PER_PULSE: 0.5, CONF_TIME_UNIT: "h", }, { CONF_NUMBER: 4, CONF_NAME: "pulse_d", CONF_COUNTED_QUANTITY: "pulses", CONF_COUNTED_QUANTITY_PER_PULSE: 1.0, CONF_TIME_UNIT: "s", }, ] } ) SINGLE_MONITOR_CONFIG_POWER_SENSORS = make_single_monitor_config_with_sensors( { CONF_CHANNELS: [ { CONF_NUMBER: 1, CONF_NAME: "channel 1", }, { CONF_NUMBER: 2, CONF_NAME: "channel two", CONF_NET_METERING: True, }, ] } ) SINGLE_MONITOR_CONFIG_TEMPERATURE_SENSORS = make_single_monitor_config_with_sensors( { CONF_TEMPERATURE_SENSORS: { CONF_TEMPERATURE_UNIT: "F", CONF_SENSORS: [ {CONF_NUMBER: 1, CONF_NAME: "temp_a"}, {CONF_NUMBER: 2, CONF_NAME: "temp_2"}, {CONF_NUMBER: 3, CONF_NAME: "temp_c"}, {CONF_NUMBER: 4, CONF_NAME: "temp_d"}, {CONF_NUMBER: 5, CONF_NAME: "temp_5"}, {CONF_NUMBER: 6, CONF_NAME: "temp_f"}, {CONF_NUMBER: 7, CONF_NAME: "temp_g"}, {CONF_NUMBER: 8, CONF_NAME: "temp_h"}, ], } } ) SINGLE_MONITOR_CONFIG_VOLTAGE_SENSORS = make_single_monitor_config_with_sensors( { CONF_VOLTAGE_SENSORS: [ { CONF_NUMBER: 1, CONF_NAME: "voltage 1", }, ] } ) MULTI_MONITOR_CONFIG = { DOMAIN: { CONF_PORT: 7513, CONF_MONITORS: [ { CONF_SERIAL_NUMBER: "00000001", CONF_TEMPERATURE_SENSORS: { CONF_TEMPERATURE_UNIT: "C", CONF_SENSORS: [{CONF_NUMBER: 1, CONF_NAME: "unit_1_temp_1"}], }, }, { CONF_SERIAL_NUMBER: "00000002", CONF_TEMPERATURE_SENSORS: { CONF_TEMPERATURE_UNIT: "F", CONF_SENSORS: [{CONF_NUMBER: 1, CONF_NAME: "unit_2_temp_1"}], }, }, { CONF_SERIAL_NUMBER: "00000003", CONF_TEMPERATURE_SENSORS: { CONF_TEMPERATURE_UNIT: "C", CONF_SENSORS: [{CONF_NUMBER: 1, CONF_NAME: "unit_3_temp_1"}], }, }, ], } } async def setup_greeneye_monitor_component_with_config( hass: HomeAssistant, config: ConfigType ) -> bool: """Set up the greeneye_monitor component with the given config. Return True if successful, False otherwise.""" result = await async_setup_component( hass, DOMAIN, config, ) await hass.async_block_till_done() return result def mock_with_listeners() -> MagicMock: """Create a MagicMock with methods that follow the same pattern for working with listeners in the greeneye_monitor API.""" mock = MagicMock() add_listeners(mock) return mock def async_mock_with_listeners() -> AsyncMock: """Create an AsyncMock with methods that follow the same pattern for working with listeners in the greeneye_monitor API.""" mock = AsyncMock() add_listeners(mock) return mock def add_listeners(mock: MagicMock | AsyncMock) -> None: """Add add_listener and remove_listener methods to the given mock that behave like their counterparts on objects from the greeneye_monitor API, plus a notify_all_listeners method that calls all registered listeners.""" mock.listeners = [] mock.add_listener = mock.listeners.append mock.remove_listener = mock.listeners.remove def notify_all_listeners(*args): for listener in list(mock.listeners): listener(*args) mock.notify_all_listeners = notify_all_listeners def mock_pulse_counter() -> MagicMock: """Create a mock GreenEye Monitor pulse counter.""" pulse_counter = mock_with_listeners() pulse_counter.pulses = 1000 pulse_counter.pulses_per_second = 10 return pulse_counter def mock_temperature_sensor() -> MagicMock: """Create a mock GreenEye Monitor temperature sensor.""" temperature_sensor = mock_with_listeners() temperature_sensor.temperature = 32.0 return temperature_sensor def mock_voltage_sensor() -> MagicMock: """Create a mock GreenEye Monitor voltage sensor.""" voltage_sensor = mock_with_listeners() voltage_sensor.voltage = 120.0 return voltage_sensor def mock_channel() -> MagicMock: """Create a mock GreenEye Monitor CT channel.""" channel = mock_with_listeners() channel.absolute_watt_seconds = 1000 channel.polarized_watt_seconds = -400 channel.watts = None return channel def mock_monitor(serial_number: int) -> MagicMock: """Create a mock GreenEye Monitor.""" monitor = mock_with_listeners() monitor.serial_number = serial_number monitor.voltage_sensor = mock_voltage_sensor() monitor.pulse_counters = [mock_pulse_counter() for i in range(0, 4)] monitor.temperature_sensors = [mock_temperature_sensor() for i in range(0, 8)] monitor.channels = [mock_channel() for i in range(0, 32)] 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