Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions homeassistant/components/fronius/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
FroniusInverterUpdateCoordinator,
FroniusLoggerUpdateCoordinator,
FroniusMeterUpdateCoordinator,
FroniusModbusCoordinatorBase,
FroniusModbusInverterUpdateCoordinator,
FroniusModbusSettingsUpdateCoordinator,
FroniusOhmpilotUpdateCoordinator,
Expand Down Expand Up @@ -377,8 +378,9 @@ async def _init_modbus_inverter(self, inverter_info: FroniusDeviceInfo) -> None:
modbus_inverter=modbus_inverter,
config_entry=self.config_entry,
)
if await self._start_modbus_coordinator(readings):
self.modbus_inverter_coordinators.append(readings)
await self._start_modbus_coordinator(
readings, self.modbus_inverter_coordinators
)
else:
_LOGGER.debug(
"No MPPT model exposed by inverter %s at Modbus unit %s",
Expand All @@ -396,8 +398,9 @@ async def _init_modbus_inverter(self, inverter_info: FroniusDeviceInfo) -> None:
modbus_inverter=modbus_inverter,
config_entry=self.config_entry,
)
if await self._start_modbus_coordinator(settings):
self.modbus_settings_coordinators.append(settings)
await self._start_modbus_coordinator(
settings, self.modbus_settings_coordinators
)
Comment thread
farmio marked this conversation as resolved.

_LOGGER.debug(
"Modbus enabled for inverter %s (UID: %s, unit ID: %s)",
Expand All @@ -406,8 +409,10 @@ async def _init_modbus_inverter(self, inverter_info: FroniusDeviceInfo) -> None:
unit_id,
)

async def _start_modbus_coordinator(
self, coordinator: FroniusCoordinatorBase
async def _start_modbus_coordinator[
_ModbusCoordinatorT: FroniusModbusCoordinatorBase
](
self, coordinator: _ModbusCoordinatorT, coordinators: list[_ModbusCoordinatorT]
) -> bool:
"""Do the first refresh of a Modbus coordinator, reporting success.

Expand All @@ -418,6 +423,9 @@ async def _start_modbus_coordinator(
await coordinator.async_refresh()
if not coordinator.last_update_success:
return False
# the platforms tell the coordinators apart by the list they are in,
# so it is kept before they are told about this one
coordinators.append(coordinator)
Comment thread
farmio marked this conversation as resolved.
# Only for re-scans. Initial setup adds entities through the
# platforms' async_setup_entry.
if self.config_entry.state is ConfigEntryState.LOADED:
Expand Down
25 changes: 22 additions & 3 deletions homeassistant/components/fronius/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@

from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.const import PERCENTAGE, EntityCategory, Platform
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import SOLAR_NET_DISCOVERY_NEW
from .entity import FroniusEntity, FroniusEntityDescription, ModbusComponentFn

if TYPE_CHECKING:
from . import FroniusConfigEntry
from .coordinator import FroniusModbusSettingsUpdateCoordinator
from .coordinator import (
FroniusCoordinatorBase,
FroniusModbusSettingsUpdateCoordinator,
)

# writes go to one device at a time
PARALLEL_UPDATES: Final = 1
Expand Down Expand Up @@ -84,11 +89,25 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Fronius number entities based on a config entry."""
for coordinator in config_entry.runtime_data.modbus_settings_coordinators:
solar_net = config_entry.runtime_data
for coordinator in solar_net.modbus_settings_coordinators:
coordinator.add_entities_for_seen_keys(
async_add_entities, Platform.NUMBER, ModbusSetpointNumber
)

@callback
def async_add_new_entities(coordinator: FroniusCoordinatorBase) -> None:
"""Add the entities of a coordinator found after setup."""
if coordinator not in solar_net.modbus_settings_coordinators:
return
Comment thread
farmio marked this conversation as resolved.
coordinator.add_entities_for_seen_keys(
async_add_entities, Platform.NUMBER, ModbusSetpointNumber
)

config_entry.async_on_unload(
async_dispatcher_connect(hass, SOLAR_NET_DISCOVERY_NEW, async_add_new_entities)
)


class ModbusSetpointNumber(FroniusEntity, NumberEntity):
"""A writable setpoint of an inverters Modbus interface."""
Expand Down
25 changes: 22 additions & 3 deletions homeassistant/components/fronius/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@

from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory, Platform
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import SOLAR_NET_DISCOVERY_NEW
from .entity import FroniusEntity, FroniusEntityDescription, ModbusComponentFn

if TYPE_CHECKING:
from . import FroniusConfigEntry
from .coordinator import FroniusModbusSettingsUpdateCoordinator
from .coordinator import (
FroniusCoordinatorBase,
FroniusModbusSettingsUpdateCoordinator,
)

# writes go to one device at a time
PARALLEL_UPDATES: Final = 1
Expand Down Expand Up @@ -63,11 +68,25 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Fronius switch entities based on a config entry."""
for coordinator in config_entry.runtime_data.modbus_settings_coordinators:
solar_net = config_entry.runtime_data
for coordinator in solar_net.modbus_settings_coordinators:
coordinator.add_entities_for_seen_keys(
async_add_entities, Platform.SWITCH, ModbusControlSwitch
)

@callback
def async_add_new_entities(coordinator: FroniusCoordinatorBase) -> None:
"""Add the entities of a coordinator found after setup."""
if coordinator not in solar_net.modbus_settings_coordinators:
return
Comment thread
farmio marked this conversation as resolved.
coordinator.add_entities_for_seen_keys(
async_add_entities, Platform.SWITCH, ModbusControlSwitch
)

config_entry.async_on_unload(
async_dispatcher_connect(hass, SOLAR_NET_DISCOVERY_NEW, async_add_new_entities)
)


class ModbusControlSwitch(FroniusEntity, SwitchEntity):
"""A control of an inverters Modbus interface that is on or off.
Expand Down
44 changes: 44 additions & 0 deletions tests/components/fronius/test_modbus.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ async def test_modbus_retried_after_setup(
aioclient_mock: AiohttpClientMocker,
mock_modbus_unavailable: MagicMock,
mock_modbus_connection: MockModbusConnection,
entity_registry: er.EntityRegistry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test an inverter asleep at setup time gets its Modbus entities later.
Expand Down Expand Up @@ -416,6 +417,10 @@ async def test_modbus_retried_after_setup(

assert config_entry.runtime_data.modbus_inverter_coordinators
assert_state(hass, "sensor.gen24_storage_mppt_1_dc_power", 3300)
# the Modbus sensors of the re-scan are told apart from the SolarAPI ones
entry = entity_registry.async_get("sensor.gen24_storage_mppt_1_dc_power")
assert entry
assert "-modbus-" in entry.unique_id
Comment thread
farmio marked this conversation as resolved.
# the hold on the shared connection is taken once, not once per re-scan
assert mock_modbus_unavailable.call_count == 1

Expand Down Expand Up @@ -451,3 +456,42 @@ async def test_control_refused_creates_no_control_entities(
)
if entry.domain == "number"
]


async def test_controls_enabled_later_get_their_entities(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
mock_fronius_modbus: MockModbusConnection,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test entities appear for controls a re-scan finds after setup.

The platforms are set up once, so a coordinator that only comes up on a
later re-scan has to be handed to them through the dispatcher.
"""
mock_fronius_modbus.for_unit(1).holding.update(
build_sunspec_map([], include_mppt_model=False)
)
mock_responses(aioclient_mock, fixture_set="gen24_storage")
with (
patch(
"homeassistant.components.fronius.PLATFORMS",
[Platform.NUMBER, Platform.SWITCH],
),
patch(
"fronius_modbus.Controls.probe_write_access", AsyncMock(return_value=False)
),
):
config_entry = await setup_fronius_integration(
hass, is_logger=False, unique_id="12345678"
)
assert hass.states.get("number.gen24_storage_ac_power_limit") is None

# inverter control via Modbus is enabled on the device web interface
freezer.tick(timedelta(minutes=SOLAR_NET_RESCAN_TIMER, seconds=1))
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)

assert config_entry.runtime_data.modbus_settings_coordinators
assert hass.states.get("number.gen24_storage_ac_power_limit")
assert hass.states.get("switch.gen24_storage_ac_power_limiting")
Loading