Skip to content

Commit

Permalink
Merge branch 'dev' into relay_switch
Browse files Browse the repository at this point in the history
  • Loading branch information
bdraco committed Aug 15, 2023
2 parents 6ffd7e0 + 80d608b commit 310fab4
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 79 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ omit =
homeassistant/components/enphase_envoy/binary_sensor.py
homeassistant/components/enphase_envoy/coordinator.py
homeassistant/components/enphase_envoy/entity.py
homeassistant/components/enphase_envoy/number.py
homeassistant/components/enphase_envoy/select.py
homeassistant/components/enphase_envoy/sensor.py
homeassistant/components/enphase_envoy/switch.py
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/buienradar/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
SUPPORTED_COUNTRY_CODES = ["NL", "BE"]
DEFAULT_COUNTRY = "NL"

"""Schedule next call after (minutes)."""
SCHEDULE_OK = 10
"""When an error occurred, new call after (minutes)."""
"""Schedule next call after (minutes)."""
SCHEDULE_NOK = 2
"""When an error occurred, new call after (minutes)."""

STATE_CONDITIONS = ["clear", "cloudy", "fog", "rainy", "snowy", "lightning"]

Expand Down
11 changes: 6 additions & 5 deletions homeassistant/components/buienradar/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,17 +714,18 @@ async def async_setup_entry(
timeframe,
)

# create weather entities:
entities = [
BrSensor(config.get(CONF_NAME, "Buienradar"), coordinates, description)
for description in SENSOR_TYPES
]

async_add_entities(entities)

# create weather data:
data = BrData(hass, coordinates, timeframe, entities)
# schedule the first update in 1 minute from now:
await data.schedule_update(1)
hass.data[DOMAIN][entry.entry_id][Platform.SENSOR] = data
await data.async_update()

async_add_entities(entities)


class BrSensor(SensorEntity):
Expand Down Expand Up @@ -755,7 +756,7 @@ def __init__(
@callback
def data_updated(self, data: BrData):
"""Update data."""
if self.hass and self._load_data(data.data):
if self._load_data(data.data) and self.hass:
self.async_write_ha_state()

@callback
Expand Down
30 changes: 18 additions & 12 deletions homeassistant/components/buienradar/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from buienradar.urls import JSON_FEED_URL, json_precipitation_forecast_url

from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.core import CALLBACK_TYPE
from homeassistant.core import CALLBACK_TYPE, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util
Expand Down Expand Up @@ -77,7 +77,8 @@ async def update_devices(self):
for dev in self.devices:
dev.data_updated(self)

async def schedule_update(self, minute=1):
@callback
def async_schedule_update(self, minute=1):
"""Schedule an update after minute minutes."""
_LOGGER.debug("Scheduling next update in %s minutes", minute)
nxt = dt_util.utcnow() + timedelta(minutes=minute)
Expand Down Expand Up @@ -110,7 +111,7 @@ async def get_data(self, url):
if resp is not None:
await resp.release()

async def async_update(self, *_):
async def _async_update(self):
"""Update the data from buienradar."""
content = await self.get_data(JSON_FEED_URL)

Expand All @@ -123,9 +124,7 @@ async def async_update(self, *_):
content.get(MESSAGE),
content.get(STATUS_CODE),
)
# schedule new call
await self.schedule_update(SCHEDULE_NOK)
return
return None
self.load_error_count = 0

# rounding coordinates prevents unnecessary redirects/calls
Expand All @@ -143,9 +142,7 @@ async def async_update(self, *_):
raincontent.get(MESSAGE),
raincontent.get(STATUS_CODE),
)
# schedule new call
await self.schedule_update(SCHEDULE_NOK)
return
return None
self.rain_error_count = 0

result = parse_data(
Expand All @@ -164,12 +161,21 @@ async def async_update(self, *_):
"Unable to parse data from Buienradar. (Msg: %s)",
result.get(MESSAGE),
)
await self.schedule_update(SCHEDULE_NOK)
return None

return result[DATA]

async def async_update(self, *_):
"""Update the data from buienradar and schedule the next update."""
data = await self._async_update()

if data is None:
self.async_schedule_update(SCHEDULE_NOK)
return

self.data = result.get(DATA)
self.data = data
await self.update_devices()
await self.schedule_update(SCHEDULE_OK)
self.async_schedule_update(SCHEDULE_OK)

@property
def attribution(self):
Expand Down
36 changes: 13 additions & 23 deletions homeassistant/components/buienradar/weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
ATTR_CONDITION_WINDY_VARIANT: (),
ATTR_CONDITION_EXCEPTIONAL: (),
}
CONDITION_MAP = {
cond_code: cond_ha
for cond_ha, cond_codes in CONDITION_CLASSES.items()
for cond_code in cond_codes
}


async def async_setup_entry(
Expand All @@ -106,20 +111,10 @@ async def async_setup_entry(
# create weather data:
data = BrData(hass, coordinates, DEFAULT_TIMEFRAME, entities)
hass.data[DOMAIN][entry.entry_id][Platform.WEATHER] = data

# create condition helper
if DATA_CONDITION not in hass.data[DOMAIN]:
cond_keys = [str(chr(x)) for x in range(97, 123)]
hass.data[DOMAIN][DATA_CONDITION] = dict.fromkeys(cond_keys)
for cond, condlst in CONDITION_CLASSES.items():
for condi in condlst:
hass.data[DOMAIN][DATA_CONDITION][condi] = cond
await data.async_update()

async_add_entities(entities)

# schedule the first update in 1 minute from now:
await data.schedule_update(1)


class BrWeather(WeatherEntity):
"""Representation of a weather condition."""
Expand All @@ -143,9 +138,6 @@ def __init__(self, config, coordinates):
@callback
def data_updated(self, data: BrData) -> None:
"""Update data."""
if not self.hass:
return

self._attr_attribution = data.attribution
self._attr_condition = self._calc_condition(data)
self._attr_forecast = self._calc_forecast(data)
Expand All @@ -158,33 +150,31 @@ def data_updated(self, data: BrData) -> None:
self._attr_native_visibility = data.visibility
self._attr_native_wind_speed = data.wind_speed
self._attr_wind_bearing = data.wind_bearing

if not self.hass:
return
self.async_write_ha_state()

def _calc_condition(self, data: BrData):
"""Return the current condition."""
if (
data.condition
and (ccode := data.condition.get(CONDCODE))
and (conditions := self.hass.data[DOMAIN].get(DATA_CONDITION))
):
return conditions.get(ccode)
if data.condition and (ccode := data.condition.get(CONDCODE)):
return CONDITION_MAP.get(ccode)
return None

def _calc_forecast(self, data: BrData):
"""Return the forecast array."""
fcdata_out = []
cond = self.hass.data[DOMAIN][DATA_CONDITION]

if not data.forecast:
return None

for data_in in data.forecast:
# remap keys from external library to
# keys understood by the weather component:
condcode = data_in.get(CONDITION, []).get(CONDCODE)
condcode = data_in.get(CONDITION, {}).get(CONDCODE)
data_out = {
ATTR_FORECAST_TIME: data_in.get(DATETIME).isoformat(),
ATTR_FORECAST_CONDITION: cond[condcode],
ATTR_FORECAST_CONDITION: CONDITION_MAP.get(condcode),
ATTR_FORECAST_NATIVE_TEMP_LOW: data_in.get(MIN_TEMP),
ATTR_FORECAST_NATIVE_TEMP: data_in.get(MAX_TEMP),
ATTR_FORECAST_NATIVE_PRECIPITATION: data_in.get(RAIN),
Expand Down
9 changes: 3 additions & 6 deletions homeassistant/components/enphase_envoy/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ async def async_setup_entry(
coordinator: EnphaseUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
envoy_data = coordinator.envoy.data
assert envoy_data is not None
envoy_serial_num = config_entry.unique_id
assert envoy_serial_num is not None
entities: list[BinarySensorEntity] = []
if envoy_data.encharge_inventory:
entities.extend(
Expand Down Expand Up @@ -176,13 +174,12 @@ def __init__(
super().__init__(coordinator, description)
enpower = self.data.enpower
assert enpower is not None
self._serial_number = enpower.serial_number
self._attr_unique_id = f"{self._serial_number}_{description.key}"
self._attr_unique_id = f"{enpower.serial_number}_{description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._serial_number)},
identifiers={(DOMAIN, enpower.serial_number)},
manufacturer="Enphase",
model="Enpower",
name=f"Enpower {self._serial_number}",
name=f"Enpower {enpower.serial_number}",
sw_version=str(enpower.firmware_version),
via_device=(DOMAIN, self.envoy_serial_num),
)
Expand Down
8 changes: 7 additions & 1 deletion homeassistant/components/enphase_envoy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

DOMAIN = "enphase_envoy"

PLATFORMS = [Platform.BINARY_SENSOR, Platform.SELECT, Platform.SENSOR, Platform.SWITCH]
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.NUMBER,
Platform.SELECT,
Platform.SENSOR,
Platform.SWITCH,
]

INVALID_AUTH_ERRORS = (EnvoyAuthenticationError, EnvoyAuthenticationRequired)
116 changes: 116 additions & 0 deletions homeassistant/components/enphase_envoy/number.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""Number platform for Enphase Envoy solar energy monitor."""
from __future__ import annotations

from collections.abc import Callable
from dataclasses import dataclass

from pyenphase import EnvoyDryContactSettings

from homeassistant.components.number import (
NumberDeviceClass,
NumberEntity,
NumberEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .coordinator import EnphaseUpdateCoordinator
from .entity import EnvoyBaseEntity


@dataclass
class EnvoyRelayRequiredKeysMixin:
"""Mixin for required keys."""

value_fn: Callable[[EnvoyDryContactSettings], float]


@dataclass
class EnvoyRelayNumberEntityDescription(
NumberEntityDescription, EnvoyRelayRequiredKeysMixin
):
"""Describes an Envoy Dry Contact Relay number entity."""


RELAY_ENTITIES = (
EnvoyRelayNumberEntityDescription(
key="soc_low",
translation_key="cutoff_battery_level",
device_class=NumberDeviceClass.BATTERY,
entity_category=EntityCategory.CONFIG,
value_fn=lambda relay: relay.soc_low,
),
EnvoyRelayNumberEntityDescription(
key="soc_high",
translation_key="restore_battery_level",
device_class=NumberDeviceClass.BATTERY,
entity_category=EntityCategory.CONFIG,
value_fn=lambda relay: relay.soc_high,
),
)


async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Enphase Envoy number platform."""
coordinator: EnphaseUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
envoy_data = coordinator.envoy.data
assert envoy_data is not None
entities: list[NumberEntity] = []
if envoy_data.dry_contact_settings:
entities.extend(
EnvoyRelayNumberEntity(coordinator, entity, relay)
for entity in RELAY_ENTITIES
for relay in envoy_data.dry_contact_settings
)
async_add_entities(entities)


class EnvoyRelayNumberEntity(EnvoyBaseEntity, NumberEntity):
"""Representation of an Enphase Enpower number entity."""

entity_description: EnvoyRelayNumberEntityDescription

def __init__(
self,
coordinator: EnphaseUpdateCoordinator,
description: EnvoyRelayNumberEntityDescription,
relay_id: str,
) -> None:
"""Initialize the Enphase relay number entity."""
super().__init__(coordinator, description)
self.envoy = coordinator.envoy
enpower = self.data.enpower
assert enpower is not None
serial_number = enpower.serial_number
self._relay_id = relay_id
self._attr_unique_id = f"{serial_number}_relay_{relay_id}_{description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, relay_id)},
manufacturer="Enphase",
model="Dry contact relay",
name=self.data.dry_contact_settings[relay_id].load_name,
sw_version=str(enpower.firmware_version),
via_device=(DOMAIN, serial_number),
)

@property
def native_value(self) -> float:
"""Return the state of the relay entity."""
return self.entity_description.value_fn(
self.data.dry_contact_settings[self._relay_id]
)

async def async_set_native_value(self, value: float) -> None:
"""Update the relay."""
await self.envoy.update_dry_contact(
{"id": self._relay_id, self.entity_description.key: int(value)}
)
await self.coordinator.async_request_refresh()

0 comments on commit 310fab4

Please sign in to comment.