Add Rainforest Eagle tests and price (#54887)

pull/54896/head
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/sensor.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/raspyrfm/*
homeassistant/components/recollect_waste/__init__.py

View File

@ -147,14 +147,14 @@ class EagleDataCoordinator(DataUpdateCoordinator):
async def _async_update_data_100(self):
"""Get the latest data from the Eagle-100 device."""
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:
raise UpdateFailed from error
_LOGGER.debug("API data: %s", data)
return data
def _fetch_data(self):
def _fetch_data_100(self):
"""Fetch and return the four sensor values in a dict."""
if self.eagle100_reader is None:
self.eagle100_reader = Eagle100Reader(

View File

@ -9,6 +9,7 @@ import voluptuous as vol
from homeassistant.components.sensor import (
DEVICE_CLASS_ENERGY,
PLATFORM_SCHEMA,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity,
SensorEntityDescription,
@ -39,6 +40,7 @@ SENSORS = (
name="Meter Power Demand",
native_unit_of_measurement=POWER_KILO_WATT,
device_class=DEVICE_CLASS_POWER,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key="zigbee:CurrentSummationDelivered",
@ -95,7 +97,22 @@ async def async_setup_entry(
) -> None:
"""Set up a config entry."""
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):
@ -111,7 +128,7 @@ class EagleSensor(CoordinatorEntity, SensorEntity):
@property
def unique_id(self) -> str | None:
"""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
def native_value(self) -> StateType:

View File

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

View File

@ -1 +1,64 @@
"""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.const import CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT,
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_FORM,
)
from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
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["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"