-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Powerwall off grid switch (#86357)
Co-authored-by: J. Nick Koston <nick@koston.org>
- Loading branch information
1 parent
60894c3
commit 66e21d7
Showing
9 changed files
with
194 additions
and
5 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"""Support for Powerwall Switches (V2 API only).""" | ||
|
||
from typing import Any | ||
|
||
from tesla_powerwall import GridStatus, IslandMode, PowerwallError | ||
|
||
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity | ||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.helpers.entity import EntityCategory | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
|
||
from .const import DOMAIN | ||
from .entity import PowerWallEntity | ||
from .models import PowerwallRuntimeData | ||
|
||
OFF_GRID_STATUSES = { | ||
GridStatus.TRANSITION_TO_ISLAND, | ||
GridStatus.ISLANDED, | ||
} | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
config_entry: ConfigEntry, | ||
async_add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Set up Powerwall switch platform from Powerwall resources.""" | ||
powerwall_data: PowerwallRuntimeData = hass.data[DOMAIN][config_entry.entry_id] | ||
async_add_entities([PowerwallOffGridEnabledEntity(powerwall_data)]) | ||
|
||
|
||
class PowerwallOffGridEnabledEntity(PowerWallEntity, SwitchEntity): | ||
"""Representation of a Switch entity for Powerwall Off-grid operation.""" | ||
|
||
_attr_name = "Off-Grid operation" | ||
_attr_has_entity_name = True | ||
_attr_entity_category = EntityCategory.CONFIG | ||
_attr_device_class = SwitchDeviceClass.SWITCH | ||
|
||
def __init__(self, powerwall_data: PowerwallRuntimeData) -> None: | ||
"""Initialize powerwall entity and unique id.""" | ||
super().__init__(powerwall_data) | ||
self._attr_unique_id = f"{self.base_unique_id}_off_grid_operation" | ||
|
||
@property | ||
def is_on(self) -> bool: | ||
"""Return true if the powerwall is off-grid.""" | ||
return self.coordinator.data.grid_status in OFF_GRID_STATUSES | ||
|
||
async def async_turn_on(self, **kwargs: Any) -> None: | ||
"""Turn off-grid mode on.""" | ||
await self._async_set_island_mode(IslandMode.OFFGRID) | ||
|
||
async def async_turn_off(self, **kwargs: Any) -> None: | ||
"""Turn off-grid mode off (return to on-grid usage).""" | ||
await self._async_set_island_mode(IslandMode.ONGRID) | ||
|
||
async def _async_set_island_mode(self, island_mode: IslandMode) -> None: | ||
"""Toggles off-grid mode using the island_mode argument.""" | ||
try: | ||
await self.hass.async_add_executor_job( | ||
self.power_wall.set_island_mode, island_mode | ||
) | ||
except PowerwallError as ex: | ||
raise HomeAssistantError( | ||
f"Setting off-grid operation to {island_mode} failed: {ex}" | ||
) from ex | ||
|
||
self._attr_is_on = island_mode == IslandMode.OFFGRID | ||
self.async_write_ha_state() | ||
|
||
await self.coordinator.async_request_refresh() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
"""Test for Powerwall off-grid switch.""" | ||
|
||
from unittest.mock import Mock, patch | ||
|
||
import pytest | ||
from tesla_powerwall import GridStatus, PowerwallError | ||
|
||
from homeassistant.components.powerwall.const import DOMAIN | ||
from homeassistant.components.switch import ( | ||
DOMAIN as SWITCH_DOMAIN, | ||
SERVICE_TURN_OFF, | ||
SERVICE_TURN_ON, | ||
) | ||
from homeassistant.const import ATTR_ENTITY_ID, CONF_IP_ADDRESS, STATE_OFF, STATE_ON | ||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.helpers import entity_registry as ent_reg | ||
|
||
from .mocks import _mock_powerwall_with_fixtures | ||
|
||
from tests.common import MockConfigEntry | ||
|
||
ENTITY_ID = "switch.mysite_off_grid_operation" | ||
|
||
|
||
@pytest.fixture(name="mock_powerwall") | ||
async def mock_powerwall_fixture(hass): | ||
"""Set up base powerwall fixture.""" | ||
|
||
mock_powerwall = await _mock_powerwall_with_fixtures(hass) | ||
|
||
config_entry = MockConfigEntry(domain=DOMAIN, data={CONF_IP_ADDRESS: "1.2.3.4"}) | ||
config_entry.add_to_hass(hass) | ||
with patch( | ||
"homeassistant.components.powerwall.Powerwall", return_value=mock_powerwall | ||
): | ||
assert await hass.config_entries.async_setup(config_entry.entry_id) | ||
await hass.async_block_till_done() | ||
yield mock_powerwall | ||
|
||
|
||
async def test_entity_registry(hass, mock_powerwall): | ||
"""Test powerwall off-grid switch device.""" | ||
|
||
mock_powerwall.get_grid_status = Mock(return_value=GridStatus.CONNECTED) | ||
entity_registry = ent_reg.async_get(hass) | ||
|
||
assert ENTITY_ID in entity_registry.entities | ||
|
||
|
||
async def test_initial(hass, mock_powerwall): | ||
"""Test initial grid status without off grid switch selected.""" | ||
|
||
mock_powerwall.get_grid_status = Mock(return_value=GridStatus.CONNECTED) | ||
|
||
state = hass.states.get(ENTITY_ID) | ||
assert state.state == STATE_OFF | ||
|
||
|
||
async def test_on(hass, mock_powerwall): | ||
"""Test state once offgrid switch has been turned on.""" | ||
|
||
mock_powerwall.get_grid_status = Mock(return_value=GridStatus.ISLANDED) | ||
|
||
await hass.services.async_call( | ||
SWITCH_DOMAIN, | ||
SERVICE_TURN_ON, | ||
{ATTR_ENTITY_ID: ENTITY_ID}, | ||
blocking=True, | ||
) | ||
|
||
state = hass.states.get(ENTITY_ID) | ||
assert state.state == STATE_ON | ||
|
||
|
||
async def test_off(hass, mock_powerwall): | ||
"""Test state once offgrid switch has been turned off.""" | ||
|
||
mock_powerwall.get_grid_status = Mock(return_value=GridStatus.CONNECTED) | ||
|
||
await hass.services.async_call( | ||
SWITCH_DOMAIN, | ||
SERVICE_TURN_OFF, | ||
{ATTR_ENTITY_ID: ENTITY_ID}, | ||
blocking=True, | ||
) | ||
|
||
state = hass.states.get(ENTITY_ID) | ||
assert state.state == STATE_OFF | ||
|
||
|
||
async def test_exception_on_powerwall_error(hass, mock_powerwall): | ||
"""Ensure that an exception in the tesla_powerwall library causes a HomeAssistantError.""" | ||
|
||
with pytest.raises(HomeAssistantError, match="Setting off-grid operation to"): | ||
mock_powerwall.set_island_mode = Mock( | ||
side_effect=PowerwallError("Mock exception") | ||
) | ||
|
||
await hass.services.async_call( | ||
SWITCH_DOMAIN, | ||
SERVICE_TURN_ON, | ||
{ATTR_ENTITY_ID: ENTITY_ID}, | ||
blocking=True, | ||
) |