Add Rainforest Eagle tests and price (#54887)

This commit is contained in:
Paulus Schoutsen 2021-08-19 13:19:31 -07:00 committed by GitHub
parent 6eadc0c303
commit f1a4ba8bb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 246 additions and 55 deletions

View File

@ -838,9 +838,6 @@ omit =
homeassistant/components/rainmachine/binary_sensor.py homeassistant/components/rainmachine/binary_sensor.py
homeassistant/components/rainmachine/sensor.py homeassistant/components/rainmachine/sensor.py
homeassistant/components/rainmachine/switch.py homeassistant/components/rainmachine/switch.py
homeassistant/components/rainforest_eagle/__init__.py
homeassistant/components/rainforest_eagle/data.py
homeassistant/components/rainforest_eagle/sensor.py
homeassistant/components/raspihats/* homeassistant/components/raspihats/*
homeassistant/components/raspyrfm/* homeassistant/components/raspyrfm/*
homeassistant/components/recollect_waste/__init__.py homeassistant/components/recollect_waste/__init__.py

View File

@ -147,14 +147,14 @@ class EagleDataCoordinator(DataUpdateCoordinator):
async def _async_update_data_100(self): async def _async_update_data_100(self):
"""Get the latest data from the Eagle-100 device.""" """Get the latest data from the Eagle-100 device."""
try: try:
data = await self.hass.async_add_executor_job(self._fetch_data) data = await self.hass.async_add_executor_job(self._fetch_data_100)
except UPDATE_100_ERRORS as error: except UPDATE_100_ERRORS as error:
raise UpdateFailed from error raise UpdateFailed from error
_LOGGER.debug("API data: %s", data) _LOGGER.debug("API data: %s", data)
return data return data
def _fetch_data(self): def _fetch_data_100(self):
"""Fetch and return the four sensor values in a dict.""" """Fetch and return the four sensor values in a dict."""
if self.eagle100_reader is None: if self.eagle100_reader is None:
self.eagle100_reader = Eagle100Reader( self.eagle100_reader = Eagle100Reader(

View File

@ -9,6 +9,7 @@ import voluptuous as vol
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING, STATE_CLASS_TOTAL_INCREASING,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
@ -39,6 +40,7 @@ SENSORS = (
name="Meter Power Demand", name="Meter Power Demand",
native_unit_of_measurement=POWER_KILO_WATT, native_unit_of_measurement=POWER_KILO_WATT,
device_class=DEVICE_CLASS_POWER, device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key="zigbee:CurrentSummationDelivered", key="zigbee:CurrentSummationDelivered",
@ -95,7 +97,22 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up a config entry.""" """Set up a config entry."""
coordinator = hass.data[DOMAIN][entry.entry_id] coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(EagleSensor(coordinator, description) for description in SENSORS) entities = [EagleSensor(coordinator, description) for description in SENSORS]
if coordinator.data.get("zigbee:Price") not in (None, "invalid"):
entities.append(
EagleSensor(
coordinator,
SensorEntityDescription(
key="zigbee:Price",
name="Meter Price",
native_unit_of_measurement=f"{coordinator.data['zigbee:PriceCurrency']}/{ENERGY_KILO_WATT_HOUR}",
state_class=STATE_CLASS_MEASUREMENT,
),
)
)
async_add_entities(entities)
class EagleSensor(CoordinatorEntity, SensorEntity): class EagleSensor(CoordinatorEntity, SensorEntity):
@ -111,7 +128,7 @@ class EagleSensor(CoordinatorEntity, SensorEntity):
@property @property
def unique_id(self) -> str | None: def unique_id(self) -> str | None:
"""Return unique ID of entity.""" """Return unique ID of entity."""
return f"{self.coordinator.cloud_id}-{self.entity_description.key}" return f"{self.coordinator.cloud_id}-${self.coordinator.hardware_address}-{self.entity_description.key}"
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:

View File

@ -242,7 +242,6 @@ class DataUpdateCoordinator(Generic[T]):
except Exception as err: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
self.last_exception = err self.last_exception = err
self.last_update_success = False self.last_update_success = False
if log_failures:
self.logger.exception( self.logger.exception(
"Unexpected error fetching %s data: %s", self.name, err "Unexpected error fetching %s data: %s", self.name, err
) )

View File

@ -1 +1,64 @@
"""Tests for the Rainforest Eagle integration.""" """Tests for the Rainforest Eagle integration."""
from unittest.mock import patch
from homeassistant import config_entries, setup
from homeassistant.components.rainforest_eagle.const import (
CONF_CLOUD_ID,
CONF_HARDWARE_ADDRESS,
CONF_INSTALL_CODE,
DOMAIN,
TYPE_EAGLE_200,
)
from homeassistant.const import CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import RESULT_TYPE_ABORT
from homeassistant.setup import async_setup_component
async def test_import(hass: HomeAssistant) -> None:
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch(
"homeassistant.components.rainforest_eagle.data.async_get_type",
return_value=(TYPE_EAGLE_200, "mock-hw"),
), patch(
"homeassistant.components.rainforest_eagle.async_setup_entry",
return_value=True,
) as mock_setup_entry:
await async_setup_component(
hass,
"sensor",
{
"sensor": {
"platform": DOMAIN,
"ip_address": "192.168.1.55",
CONF_CLOUD_ID: "abcdef",
CONF_INSTALL_CODE: "123456",
}
},
)
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
entry = entries[0]
assert entry.title == "abcdef"
assert entry.data == {
CONF_TYPE: TYPE_EAGLE_200,
CONF_CLOUD_ID: "abcdef",
CONF_INSTALL_CODE: "123456",
CONF_HARDWARE_ADDRESS: "mock-hw",
}
assert len(mock_setup_entry.mock_calls) == 1
# Second time we should get already_configured
result2 = await hass.config_entries.flow.async_init(
DOMAIN,
data={CONF_CLOUD_ID: "abcdef", CONF_INSTALL_CODE: "123456"},
context={"source": config_entries.SOURCE_IMPORT},
)
assert result2["type"] == RESULT_TYPE_ABORT
assert result2["reason"] == "already_configured"

View File

@ -12,11 +12,7 @@ from homeassistant.components.rainforest_eagle.const import (
from homeassistant.components.rainforest_eagle.data import CannotConnect, InvalidAuth from homeassistant.components.rainforest_eagle.data import CannotConnect, InvalidAuth
from homeassistant.const import CONF_TYPE from homeassistant.const import CONF_TYPE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import ( from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
RESULT_TYPE_ABORT,
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_FORM,
)
async def test_form(hass: HomeAssistant) -> None: async def test_form(hass: HomeAssistant) -> None:
@ -88,42 +84,3 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
assert result2["type"] == RESULT_TYPE_FORM assert result2["type"] == RESULT_TYPE_FORM
assert result2["errors"] == {"base": "cannot_connect"} assert result2["errors"] == {"base": "cannot_connect"}
async def test_import(hass: HomeAssistant) -> None:
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
with patch(
"homeassistant.components.rainforest_eagle.data.async_get_type",
return_value=(TYPE_EAGLE_200, "mock-hw"),
), patch(
"homeassistant.components.rainforest_eagle.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
data={CONF_CLOUD_ID: "abcdef", CONF_INSTALL_CODE: "123456"},
context={"source": config_entries.SOURCE_IMPORT},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "abcdef"
assert result["data"] == {
CONF_TYPE: TYPE_EAGLE_200,
CONF_CLOUD_ID: "abcdef",
CONF_INSTALL_CODE: "123456",
CONF_HARDWARE_ADDRESS: "mock-hw",
}
assert len(mock_setup_entry.mock_calls) == 1
# Second time we should get already_configured
result2 = await hass.config_entries.flow.async_init(
DOMAIN,
data={CONF_CLOUD_ID: "abcdef", CONF_INSTALL_CODE: "123456"},
context={"source": config_entries.SOURCE_IMPORT},
)
assert result2["type"] == RESULT_TYPE_ABORT
assert result2["reason"] == "already_configured"

View File

@ -0,0 +1,158 @@
"""Tests for rainforest eagle sensors."""
from unittest.mock import Mock, patch
import pytest
from homeassistant.components.rainforest_eagle.const import (
CONF_CLOUD_ID,
CONF_HARDWARE_ADDRESS,
CONF_INSTALL_CODE,
DOMAIN,
TYPE_EAGLE_100,
TYPE_EAGLE_200,
)
from homeassistant.const import CONF_TYPE
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
MOCK_CLOUD_ID = "12345"
MOCK_200_RESPONSE_WITH_PRICE = {
"zigbee:InstantaneousDemand": {
"Name": "zigbee:InstantaneousDemand",
"Value": "1.152000",
},
"zigbee:CurrentSummationDelivered": {
"Name": "zigbee:CurrentSummationDelivered",
"Value": "45251.285000",
},
"zigbee:CurrentSummationReceived": {
"Name": "zigbee:CurrentSummationReceived",
"Value": "232.232000",
},
"zigbee:Price": {"Name": "zigbee:Price", "Value": "0.053990"},
"zigbee:PriceCurrency": {"Name": "zigbee:PriceCurrency", "Value": "USD"},
}
MOCK_200_RESPONSE_WITHOUT_PRICE = {
"zigbee:InstantaneousDemand": {
"Name": "zigbee:InstantaneousDemand",
"Value": "1.152000",
},
"zigbee:CurrentSummationDelivered": {
"Name": "zigbee:CurrentSummationDelivered",
"Value": "45251.285000",
},
"zigbee:CurrentSummationReceived": {
"Name": "zigbee:CurrentSummationReceived",
"Value": "232.232000",
},
"zigbee:Price": {"Name": "zigbee:Price", "Value": "invalid"},
"zigbee:PriceCurrency": {"Name": "zigbee:PriceCurrency", "Value": "USD"},
}
@pytest.fixture
async def setup_rainforest_200(hass):
"""Set up rainforest."""
MockConfigEntry(
domain="rainforest_eagle",
data={
CONF_CLOUD_ID: MOCK_CLOUD_ID,
CONF_INSTALL_CODE: "abcdefgh",
CONF_HARDWARE_ADDRESS: "mock-hw-address",
CONF_TYPE: TYPE_EAGLE_200,
},
).add_to_hass(hass)
with patch(
"aioeagle.ElectricMeter.get_device_query",
return_value=MOCK_200_RESPONSE_WITHOUT_PRICE,
) as mock_update:
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
yield mock_update
@pytest.fixture
async def setup_rainforest_100(hass):
"""Set up rainforest."""
MockConfigEntry(
domain="rainforest_eagle",
data={
CONF_CLOUD_ID: MOCK_CLOUD_ID,
CONF_INSTALL_CODE: "abcdefgh",
CONF_HARDWARE_ADDRESS: None,
CONF_TYPE: TYPE_EAGLE_100,
},
).add_to_hass(hass)
with patch(
"homeassistant.components.rainforest_eagle.data.Eagle100Reader",
return_value=Mock(
get_instantaneous_demand=Mock(
return_value={"InstantaneousDemand": {"Demand": "1.152000"}}
),
get_current_summation=Mock(
return_value={
"CurrentSummation": {
"SummationDelivered": "45251.285000",
"SummationReceived": "232.232000",
}
}
),
),
) as mock_update:
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
yield mock_update
async def test_sensors_200(hass, setup_rainforest_200):
"""Test the sensors."""
assert len(hass.states.async_all()) == 3
demand = hass.states.get("sensor.meter_power_demand")
assert demand is not None
assert demand.state == "1.152000"
assert demand.attributes["unit_of_measurement"] == "kW"
delivered = hass.states.get("sensor.total_meter_energy_delivered")
assert delivered is not None
assert delivered.state == "45251.285000"
assert delivered.attributes["unit_of_measurement"] == "kWh"
received = hass.states.get("sensor.total_meter_energy_received")
assert received is not None
assert received.state == "232.232000"
assert received.attributes["unit_of_measurement"] == "kWh"
setup_rainforest_200.return_value = MOCK_200_RESPONSE_WITH_PRICE
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
await hass.config_entries.async_reload(config_entry.entry_id)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4
price = hass.states.get("sensor.meter_price")
assert price is not None
assert price.state == "0.053990"
assert price.attributes["unit_of_measurement"] == "USD/kWh"
async def test_sensors_100(hass, setup_rainforest_100):
"""Test the sensors."""
assert len(hass.states.async_all()) == 3
demand = hass.states.get("sensor.meter_power_demand")
assert demand is not None
assert demand.state == "1.152000"
assert demand.attributes["unit_of_measurement"] == "kW"
delivered = hass.states.get("sensor.total_meter_energy_delivered")
assert delivered is not None
assert delivered.state == "45251.285000"
assert delivered.attributes["unit_of_measurement"] == "kWh"
received = hass.states.get("sensor.total_meter_energy_received")
assert received is not None
assert received.state == "232.232000"
assert received.attributes["unit_of_measurement"] == "kWh"