mirror of https://github.com/home-assistant/core
Convert solaredge to asyncio with aiosolaredge (#115599)
This commit is contained in:
parent
d08bb96d00
commit
fd08b7281e
|
@ -1286,8 +1286,8 @@ build.json @home-assistant/supervisor
|
|||
/tests/components/snmp/ @nmaggioni
|
||||
/homeassistant/components/snooz/ @AustinBrunkhorst
|
||||
/tests/components/snooz/ @AustinBrunkhorst
|
||||
/homeassistant/components/solaredge/ @frenck
|
||||
/tests/components/solaredge/ @frenck
|
||||
/homeassistant/components/solaredge/ @frenck @bdraco
|
||||
/tests/components/solaredge/ @frenck @bdraco
|
||||
/homeassistant/components/solaredge_local/ @drobtravels @scheric
|
||||
/homeassistant/components/solarlog/ @Ernst79
|
||||
/tests/components/solarlog/ @Ernst79
|
||||
|
|
|
@ -4,13 +4,14 @@ from __future__ import annotations
|
|||
|
||||
import socket
|
||||
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
from solaredge import Solaredge
|
||||
from aiohttp import ClientError
|
||||
from aiosolaredge import SolarEdge
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_API_KEY, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import CONF_SITE_ID, DATA_API_CLIENT, DOMAIN, LOGGER
|
||||
|
@ -22,13 +23,12 @@ PLATFORMS = [Platform.SENSOR]
|
|||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up SolarEdge from a config entry."""
|
||||
api = Solaredge(entry.data[CONF_API_KEY])
|
||||
session = async_get_clientsession(hass)
|
||||
api = SolarEdge(entry.data[CONF_API_KEY], session)
|
||||
|
||||
try:
|
||||
response = await hass.async_add_executor_job(
|
||||
api.get_details, entry.data[CONF_SITE_ID]
|
||||
)
|
||||
except (ConnectTimeout, HTTPError, socket.gaierror) as ex:
|
||||
response = await api.get_details(entry.data[CONF_SITE_ID])
|
||||
except (TimeoutError, ClientError, socket.gaierror) as ex:
|
||||
LOGGER.error("Could not retrieve details from SolarEdge API")
|
||||
raise ConfigEntryNotReady from ex
|
||||
|
||||
|
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import socket
|
||||
from typing import Any
|
||||
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
import solaredge
|
||||
from aiohttp import ClientError
|
||||
import aiosolaredge
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_API_KEY, CONF_NAME
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import CONF_SITE_ID, DEFAULT_NAME, DOMAIN
|
||||
|
@ -38,15 +40,16 @@ class SolarEdgeConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
"""Return True if site_id exists in configuration."""
|
||||
return site_id in self._async_current_site_ids()
|
||||
|
||||
def _check_site(self, site_id: str, api_key: str) -> bool:
|
||||
async def _async_check_site(self, site_id: str, api_key: str) -> bool:
|
||||
"""Check if we can connect to the soleredge api service."""
|
||||
api = solaredge.Solaredge(api_key)
|
||||
session = async_get_clientsession(self.hass)
|
||||
api = aiosolaredge.SolarEdge(api_key, session)
|
||||
try:
|
||||
response = api.get_details(site_id)
|
||||
response = await api.get_details(site_id)
|
||||
if response["details"]["status"].lower() != "active":
|
||||
self._errors[CONF_SITE_ID] = "site_not_active"
|
||||
return False
|
||||
except (ConnectTimeout, HTTPError):
|
||||
except (TimeoutError, ClientError, socket.gaierror):
|
||||
self._errors[CONF_SITE_ID] = "could_not_connect"
|
||||
return False
|
||||
except KeyError:
|
||||
|
@ -66,9 +69,7 @@ class SolarEdgeConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||
else:
|
||||
site = user_input[CONF_SITE_ID]
|
||||
api = user_input[CONF_API_KEY]
|
||||
can_connect = await self.hass.async_add_executor_job(
|
||||
self._check_site, site, api
|
||||
)
|
||||
can_connect = await self._async_check_site(site, api)
|
||||
if can_connect:
|
||||
return self.async_create_entry(
|
||||
title=name, data={CONF_SITE_ID: site, CONF_API_KEY: api}
|
||||
|
|
|
@ -6,7 +6,7 @@ from abc import ABC, abstractmethod
|
|||
from datetime import date, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from solaredge import Solaredge
|
||||
from aiosolaredge import SolarEdge
|
||||
from stringcase import snakecase
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
@ -27,7 +27,7 @@ class SolarEdgeDataService(ABC):
|
|||
|
||||
coordinator: DataUpdateCoordinator[None]
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: Solaredge, site_id: str) -> None:
|
||||
def __init__(self, hass: HomeAssistant, api: SolarEdge, site_id: str) -> None:
|
||||
"""Initialize the data object."""
|
||||
self.api = api
|
||||
self.site_id = site_id
|
||||
|
@ -54,12 +54,8 @@ class SolarEdgeDataService(ABC):
|
|||
"""Update interval."""
|
||||
|
||||
@abstractmethod
|
||||
def update(self) -> None:
|
||||
"""Update data in executor."""
|
||||
|
||||
async def async_update_data(self) -> None:
|
||||
"""Update data."""
|
||||
await self.hass.async_add_executor_job(self.update)
|
||||
|
||||
|
||||
class SolarEdgeOverviewDataService(SolarEdgeDataService):
|
||||
|
@ -70,10 +66,10 @@ class SolarEdgeOverviewDataService(SolarEdgeDataService):
|
|||
"""Update interval."""
|
||||
return OVERVIEW_UPDATE_DELAY
|
||||
|
||||
def update(self) -> None:
|
||||
async def async_update_data(self) -> None:
|
||||
"""Update the data from the SolarEdge Monitoring API."""
|
||||
try:
|
||||
data = self.api.get_overview(self.site_id)
|
||||
data = await self.api.get_overview(self.site_id)
|
||||
overview = data["overview"]
|
||||
except KeyError as ex:
|
||||
raise UpdateFailed("Missing overview data, skipping update") from ex
|
||||
|
@ -113,11 +109,11 @@ class SolarEdgeDetailsDataService(SolarEdgeDataService):
|
|||
"""Update interval."""
|
||||
return DETAILS_UPDATE_DELAY
|
||||
|
||||
def update(self) -> None:
|
||||
async def async_update_data(self) -> None:
|
||||
"""Update the data from the SolarEdge Monitoring API."""
|
||||
|
||||
try:
|
||||
data = self.api.get_details(self.site_id)
|
||||
data = await self.api.get_details(self.site_id)
|
||||
details = data["details"]
|
||||
except KeyError as ex:
|
||||
raise UpdateFailed("Missing details data, skipping update") from ex
|
||||
|
@ -157,10 +153,10 @@ class SolarEdgeInventoryDataService(SolarEdgeDataService):
|
|||
"""Update interval."""
|
||||
return INVENTORY_UPDATE_DELAY
|
||||
|
||||
def update(self) -> None:
|
||||
async def async_update_data(self) -> None:
|
||||
"""Update the data from the SolarEdge Monitoring API."""
|
||||
try:
|
||||
data = self.api.get_inventory(self.site_id)
|
||||
data = await self.api.get_inventory(self.site_id)
|
||||
inventory = data["Inventory"]
|
||||
except KeyError as ex:
|
||||
raise UpdateFailed("Missing inventory data, skipping update") from ex
|
||||
|
@ -178,7 +174,7 @@ class SolarEdgeInventoryDataService(SolarEdgeDataService):
|
|||
class SolarEdgeEnergyDetailsService(SolarEdgeDataService):
|
||||
"""Get and update the latest power flow data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: Solaredge, site_id: str) -> None:
|
||||
def __init__(self, hass: HomeAssistant, api: SolarEdge, site_id: str) -> None:
|
||||
"""Initialize the power flow data service."""
|
||||
super().__init__(hass, api, site_id)
|
||||
|
||||
|
@ -189,17 +185,16 @@ class SolarEdgeEnergyDetailsService(SolarEdgeDataService):
|
|||
"""Update interval."""
|
||||
return ENERGY_DETAILS_DELAY
|
||||
|
||||
def update(self) -> None:
|
||||
async def async_update_data(self) -> None:
|
||||
"""Update the data from the SolarEdge Monitoring API."""
|
||||
try:
|
||||
now = datetime.now()
|
||||
today = date.today()
|
||||
midnight = datetime.combine(today, datetime.min.time())
|
||||
data = self.api.get_energy_details(
|
||||
data = await self.api.get_energy_details(
|
||||
self.site_id,
|
||||
midnight,
|
||||
now.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
meters=None,
|
||||
now,
|
||||
time_unit="DAY",
|
||||
)
|
||||
energy_details = data["energyDetails"]
|
||||
|
@ -239,7 +234,7 @@ class SolarEdgeEnergyDetailsService(SolarEdgeDataService):
|
|||
class SolarEdgePowerFlowDataService(SolarEdgeDataService):
|
||||
"""Get and update the latest power flow data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: Solaredge, site_id: str) -> None:
|
||||
def __init__(self, hass: HomeAssistant, api: SolarEdge, site_id: str) -> None:
|
||||
"""Initialize the power flow data service."""
|
||||
super().__init__(hass, api, site_id)
|
||||
|
||||
|
@ -250,10 +245,10 @@ class SolarEdgePowerFlowDataService(SolarEdgeDataService):
|
|||
"""Update interval."""
|
||||
return POWER_FLOW_UPDATE_DELAY
|
||||
|
||||
def update(self) -> None:
|
||||
async def async_update_data(self) -> None:
|
||||
"""Update the data from the SolarEdge Monitoring API."""
|
||||
try:
|
||||
data = self.api.get_current_power_flow(self.site_id)
|
||||
data = await self.api.get_current_power_flow(self.site_id)
|
||||
power_flow = data["siteCurrentPowerFlow"]
|
||||
except KeyError as ex:
|
||||
raise UpdateFailed("Missing power flow data, skipping update") from ex
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"domain": "solaredge",
|
||||
"name": "SolarEdge",
|
||||
"codeowners": ["@frenck"],
|
||||
"codeowners": ["@frenck", "@bdraco"],
|
||||
"config_flow": true,
|
||||
"dhcp": [
|
||||
{
|
||||
|
@ -12,6 +12,6 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/solaredge",
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["solaredge"],
|
||||
"requirements": ["solaredge==0.0.2", "stringcase==1.2.0"]
|
||||
"loggers": ["aiosolaredge"],
|
||||
"requirements": ["aiosolaredge==0.2.0", "stringcase==1.2.0"]
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
|||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from solaredge import Solaredge
|
||||
from aiosolaredge import SolarEdge
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
|
@ -205,7 +205,7 @@ async def async_setup_entry(
|
|||
) -> None:
|
||||
"""Add an solarEdge entry."""
|
||||
# Add the needed sensors to hass
|
||||
api: Solaredge = hass.data[DOMAIN][entry.entry_id][DATA_API_CLIENT]
|
||||
api: SolarEdge = hass.data[DOMAIN][entry.entry_id][DATA_API_CLIENT]
|
||||
|
||||
sensor_factory = SolarEdgeSensorFactory(hass, entry.data[CONF_SITE_ID], api)
|
||||
for service in sensor_factory.all_services:
|
||||
|
@ -223,7 +223,7 @@ async def async_setup_entry(
|
|||
class SolarEdgeSensorFactory:
|
||||
"""Factory which creates sensors based on the sensor_key."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, site_id: str, api: Solaredge) -> None:
|
||||
def __init__(self, hass: HomeAssistant, site_id: str, api: SolarEdge) -> None:
|
||||
"""Initialize the factory."""
|
||||
|
||||
details = SolarEdgeDetailsDataService(hass, api, site_id)
|
||||
|
|
|
@ -367,6 +367,9 @@ aioskybell==22.7.0
|
|||
# homeassistant.components.slimproto
|
||||
aioslimproto==3.0.0
|
||||
|
||||
# homeassistant.components.solaredge
|
||||
aiosolaredge==0.2.0
|
||||
|
||||
# homeassistant.components.steamist
|
||||
aiosteamist==0.3.2
|
||||
|
||||
|
@ -2574,9 +2577,6 @@ soco==0.30.3
|
|||
# homeassistant.components.solaredge_local
|
||||
solaredge-local==0.2.3
|
||||
|
||||
# homeassistant.components.solaredge
|
||||
solaredge==0.0.2
|
||||
|
||||
# homeassistant.components.solax
|
||||
solax==3.1.0
|
||||
|
||||
|
|
|
@ -340,6 +340,9 @@ aioskybell==22.7.0
|
|||
# homeassistant.components.slimproto
|
||||
aioslimproto==3.0.0
|
||||
|
||||
# homeassistant.components.solaredge
|
||||
aiosolaredge==0.2.0
|
||||
|
||||
# homeassistant.components.steamist
|
||||
aiosteamist==0.3.2
|
||||
|
||||
|
@ -1990,9 +1993,6 @@ snapcast==2.3.6
|
|||
# homeassistant.components.sonos
|
||||
soco==0.30.3
|
||||
|
||||
# homeassistant.components.solaredge
|
||||
solaredge==0.0.2
|
||||
|
||||
# homeassistant.components.solax
|
||||
solax==3.1.0
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"""Tests for the SolarEdge config flow."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from aiohttp import ClientError
|
||||
import pytest
|
||||
from requests.exceptions import ConnectTimeout, HTTPError
|
||||
|
||||
from homeassistant.components.solaredge.const import CONF_SITE_ID, DEFAULT_NAME, DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_USER
|
||||
|
@ -22,8 +22,11 @@ API_KEY = "a1b2c3d4e5f6g7h8"
|
|||
def mock_controller():
|
||||
"""Mock a successful Solaredge API."""
|
||||
api = Mock()
|
||||
api.get_details.return_value = {"details": {"status": "active"}}
|
||||
with patch("solaredge.Solaredge", return_value=api):
|
||||
api.get_details = AsyncMock(return_value={"details": {"status": "active"}})
|
||||
with patch(
|
||||
"homeassistant.components.solaredge.config_flow.aiosolaredge.SolarEdge",
|
||||
return_value=api,
|
||||
):
|
||||
yield api
|
||||
|
||||
|
||||
|
@ -117,7 +120,7 @@ async def test_asserts(hass: HomeAssistant, test_api: Mock) -> None:
|
|||
assert result.get("errors") == {CONF_SITE_ID: "invalid_api_key"}
|
||||
|
||||
# test with ConnectionTimeout
|
||||
test_api.get_details.side_effect = ConnectTimeout()
|
||||
test_api.get_details = AsyncMock(side_effect=TimeoutError())
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
|
@ -127,7 +130,7 @@ async def test_asserts(hass: HomeAssistant, test_api: Mock) -> None:
|
|||
assert result.get("errors") == {CONF_SITE_ID: "could_not_connect"}
|
||||
|
||||
# test with HTTPError
|
||||
test_api.get_details.side_effect = HTTPError()
|
||||
test_api.get_details = AsyncMock(side_effect=ClientError())
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_USER},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"""Tests for the SolarEdge coordinator services."""
|
||||
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
@ -25,7 +25,7 @@ def enable_all_entities(entity_registry_enabled_by_default):
|
|||
"""Make sure all entities are enabled."""
|
||||
|
||||
|
||||
@patch("homeassistant.components.solaredge.Solaredge")
|
||||
@patch("homeassistant.components.solaredge.SolarEdge")
|
||||
async def test_solaredgeoverviewdataservice_energy_values_validity(
|
||||
mock_solaredge, hass: HomeAssistant, freezer: FrozenDateTimeFactory
|
||||
) -> None:
|
||||
|
@ -35,7 +35,9 @@ async def test_solaredgeoverviewdataservice_energy_values_validity(
|
|||
title=DEFAULT_NAME,
|
||||
data={CONF_NAME: DEFAULT_NAME, CONF_SITE_ID: SITE_ID, CONF_API_KEY: API_KEY},
|
||||
)
|
||||
mock_solaredge().get_details.return_value = {"details": {"status": "active"}}
|
||||
mock_solaredge().get_details = AsyncMock(
|
||||
return_value={"details": {"status": "active"}}
|
||||
)
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
@ -50,7 +52,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity(
|
|||
"currentPower": {"power": 0.0},
|
||||
}
|
||||
}
|
||||
mock_solaredge().get_overview.return_value = mock_overview_data
|
||||
mock_solaredge().get_overview = AsyncMock(return_value=mock_overview_data)
|
||||
freezer.tick(OVERVIEW_UPDATE_DELAY)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
@ -60,7 +62,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity(
|
|||
|
||||
# Invalid energy values, lifeTimeData energy is lower than last year, month or day.
|
||||
mock_overview_data["overview"]["lifeTimeData"]["energy"] = 0
|
||||
mock_solaredge().get_overview.return_value = mock_overview_data
|
||||
mock_solaredge().get_overview = AsyncMock(return_value=mock_overview_data)
|
||||
freezer.tick(OVERVIEW_UPDATE_DELAY)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
@ -71,7 +73,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity(
|
|||
|
||||
# New valid energy values update
|
||||
mock_overview_data["overview"]["lifeTimeData"]["energy"] = 100001
|
||||
mock_solaredge().get_overview.return_value = mock_overview_data
|
||||
mock_solaredge().get_overview = AsyncMock(return_value=mock_overview_data)
|
||||
freezer.tick(OVERVIEW_UPDATE_DELAY)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
@ -82,7 +84,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity(
|
|||
|
||||
# Invalid energy values, lastYearData energy is lower than last month or day.
|
||||
mock_overview_data["overview"]["lastYearData"]["energy"] = 0
|
||||
mock_solaredge().get_overview.return_value = mock_overview_data
|
||||
mock_solaredge().get_overview = AsyncMock(return_value=mock_overview_data)
|
||||
freezer.tick(OVERVIEW_UPDATE_DELAY)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
@ -100,7 +102,7 @@ async def test_solaredgeoverviewdataservice_energy_values_validity(
|
|||
mock_overview_data["overview"]["lastYearData"]["energy"] = 0.0
|
||||
mock_overview_data["overview"]["lastMonthData"]["energy"] = 0.0
|
||||
mock_overview_data["overview"]["lastDayData"]["energy"] = 0.0
|
||||
mock_solaredge().get_overview.return_value = mock_overview_data
|
||||
mock_solaredge().get_overview = AsyncMock(return_value=mock_overview_data)
|
||||
freezer.tick(OVERVIEW_UPDATE_DELAY)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
|
Loading…
Reference in New Issue