def _login_and_fetch_site_info( power_wall: Powerwall, password: str ) -> tuple[SiteInfo, str]: """Login to the powerwall and fetch the base info.""" if password is not None: power_wall.login(password) return power_wall.get_site_info(), power_wall.get_gateway_din()
def _login_and_fetch_base_info( power_wall: Powerwall, host: str, password: str ) -> PowerwallBaseInfo: """Login to the powerwall and fetch the base info.""" if password is not None: power_wall.login(password) return call_base_info(power_wall, host)
def _recreate_powerwall_login(self) -> None: """Recreate the login on auth failure.""" http_session = self.runtime_data[POWERWALL_HTTP_SESSION] http_session.close() http_session = requests.Session() self.runtime_data[POWERWALL_HTTP_SESSION] = http_session self.power_wall = Powerwall(self.ip_address, http_session=http_session) self.power_wall.login(self.password or "")
def _fetch_powerwall_data(power_wall: Powerwall) -> PowerwallData: """Process and update powerwall data.""" return PowerwallData( charge=power_wall.get_charge(), site_master=power_wall.get_sitemaster(), meters=power_wall.get_meters(), grid_services_active=power_wall.is_grid_services_active(), grid_status=power_wall.get_grid_status(), )
def _recreate_powerwall_login(): nonlocal http_session nonlocal power_wall http_session.close() http_session = requests.Session() power_wall = Powerwall(ip_address, http_session=http_session) runtime_data[POWERWALL_OBJECT] = power_wall runtime_data[POWERWALL_HTTP_SESSION] = http_session power_wall.login("", password)
def test_detect_and_pin_version(self): add( Response(responses.GET, url=f"{ENDPOINT}status", json=STATUS_RESPONSE)) vers = version.LooseVersion("1.50.1") pw = Powerwall(ENDPOINT) self.assertEqual(pw.detect_and_pin_version(), vers) self.assertEqual(pw._pin_version, vers)
def test_endpoint_setup(self): test_endpoint_1 = "1.1.1.1" pw = Powerwall(test_endpoint_1) self.assertEqual(pw._endpoint, f"https://{test_endpoint_1}/api/") test_endpoint_2 = "http://1.1.1.1" pw = Powerwall(test_endpoint_2) self.assertEqual(pw._endpoint, f"https://1.1.1.1/api/") test_endpoint_3 = "https://1.1.1.1/api/" pw = Powerwall(test_endpoint_3) self.assertEqual(pw._endpoint, test_endpoint_3)
def call_base_info(power_wall: Powerwall, host: str) -> PowerwallBaseInfo: """Return PowerwallBaseInfo for the device.""" # Make sure the serial numbers always have the same order gateway_din = None with contextlib.suppress(AssertionError, PowerwallError): gateway_din = power_wall.get_gateway_din().upper() return PowerwallBaseInfo( gateway_din=gateway_din, site_info=power_wall.get_site_info(), status=power_wall.get_status(), device_type=power_wall.get_device_type(), serial_numbers=sorted(power_wall.get_serial_numbers()), url=f"https://{host}", )
def main(): pp = pprint.PrettyPrinter(indent=2) creds = get_login_credentials() power_wall = Powerwall(BACKUP_GW_ADDR) try: power_wall.detect_and_pin_version() except PowerwallUnreachableError as e: print(e) return print("Detected and pinned version: {}".format( power_wall.get_pinned_version()))
def _mock_powerwall_return_value( site_info=None, charge=None, sitemaster=None, meters=None, grid_services_active=None, grid_status=None, status=None, device_type=None, serial_numbers=None, backup_reserve_percentage=None, ): powerwall_mock = MagicMock(Powerwall("1.2.3.4")) powerwall_mock.get_site_info = Mock(return_value=site_info) powerwall_mock.get_charge = Mock(return_value=charge) powerwall_mock.get_sitemaster = Mock(return_value=sitemaster) powerwall_mock.get_meters = Mock(return_value=meters) powerwall_mock.get_grid_status = Mock(return_value=grid_status) powerwall_mock.get_status = Mock(return_value=status) powerwall_mock.get_device_type = Mock(return_value=device_type) powerwall_mock.get_serial_numbers = Mock(return_value=serial_numbers) powerwall_mock.get_backup_reserve_percentage = Mock( return_value=backup_reserve_percentage) powerwall_mock.is_grid_services_active = Mock( return_value=grid_services_active) return powerwall_mock
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Tesla Powerwall from a config entry.""" http_session = requests.Session() ip_address = entry.data[CONF_IP_ADDRESS] password = entry.data.get(CONF_PASSWORD) power_wall = Powerwall(ip_address, http_session=http_session) try: base_info = await hass.async_add_executor_job( _login_and_fetch_base_info, power_wall, ip_address, password) except PowerwallUnreachableError as err: http_session.close() raise ConfigEntryNotReady from err except MissingAttributeError as err: http_session.close() # The error might include some important information about what exactly changed. _LOGGER.error("The powerwall api has changed: %s", str(err)) persistent_notification.async_create(hass, API_CHANGED_ERROR_BODY, API_CHANGED_TITLE) return False except AccessDeniedError as err: _LOGGER.debug("Authentication failed", exc_info=err) http_session.close() raise ConfigEntryAuthFailed from err except APIError as err: http_session.close() raise ConfigEntryNotReady from err gateway_din = base_info.gateway_din if gateway_din and entry.unique_id is not None and is_ip_address( entry.unique_id): hass.config_entries.async_update_entry(entry, unique_id=gateway_din) runtime_data = PowerwallRuntimeData( api_changed=False, base_info=base_info, http_session=http_session, coordinator=None, ) manager = PowerwallDataManager(hass, power_wall, ip_address, password, runtime_data) coordinator = DataUpdateCoordinator( hass, _LOGGER, name="Powerwall site", update_method=manager.async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) await coordinator.async_config_entry_first_refresh() runtime_data[POWERWALL_COORDINATOR] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = runtime_data await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True
async def _mock_powerwall_site_name(hass, site_name): powerwall_mock = MagicMock(Powerwall("1.2.3.4")) site_info_resp = SiteInfoResponse(await _async_load_json_fixture( hass, "site_info.json")) site_info_resp.site_name = site_name powerwall_mock.get_site_info = Mock(return_value=site_info_resp) return powerwall_mock
async def _mock_powerwall_site_name(opp, site_name): powerwall_mock = MagicMock(Powerwall("1.2.3.4")) site_info_resp = SiteInfo(await _async_load_json_fixture(opp, "site_info.json")) # Sets site_info_resp.site_name to return site_name site_info_resp.response["site_name"] = site_name powerwall_mock.get_site_info = Mock(return_value=site_info_resp) return powerwall_mock
async def _mock_powerwall_site_name(hass, site_name): powerwall_mock = MagicMock(Powerwall("1.2.3.4")) site_info_resp = SiteInfo(await _async_load_json_fixture(hass, "site_info.json")) # Sets site_info_resp.site_name to return site_name site_info_resp.response["site_name"] = site_name powerwall_mock.get_site_info = Mock(return_value=site_info_resp) powerwall_mock.get_gateway_din = Mock(return_value=MOCK_GATEWAY_DIN) return powerwall_mock
def test_pins_version_on_creation(self): pw = Powerwall(ENDPOINT, pin_version="1.49.0") self.assertEqual(pw.get_pinned_version(), version.LooseVersion("1.49.0")) pw = Powerwall(ENDPOINT, pin_version=version.LooseVersion("1.49.0")) self.assertEqual(pw.get_pinned_version(), version.LooseVersion("1.49.0"))
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Tesla Powerwall from a config entry.""" entry_id = entry.entry_id hass.data[DOMAIN].setdefault(entry_id, {}) http_session = requests.Session() power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session) try: await hass.async_add_executor_job(power_wall.detect_and_pin_version) powerwall_data = await hass.async_add_executor_job( call_base_info, power_wall) except (PowerwallUnreachableError, APIError, ConnectionError): http_session.close() raise ConfigEntryNotReady await _migrate_old_unique_ids(hass, entry_id, powerwall_data) async def async_update_data(): """Fetch data from API endpoint.""" return await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) coordinator = DataUpdateCoordinator( hass, _LOGGER, name="Powerwall site", update_method=async_update_data, update_interval=timedelta(seconds=UPDATE_INTERVAL), ) hass.data[DOMAIN][entry.entry_id] = powerwall_data hass.data[DOMAIN][entry.entry_id].update({ POWERWALL_OBJECT: power_wall, POWERWALL_COORDINATOR: coordinator, POWERWALL_HTTP_SESSION: http_session, }) await coordinator.async_refresh() for component in PLATFORMS: hass.async_create_task( hass.config_entries.async_forward_entry_setup(entry, component)) return True
def loginLocal(self, email, password, IPaddress): self.localEmail = email self.localPassword = password self.IPAddress = IPaddress LOGGER.debug('Local Access Supported') self.TPWlocal = Powerwall(IPaddress) self.TPWlocal.login(self.localPassword, self.localEmail) if not (self.TPWlocal.is_authenticated()): LOGGER.debug('Error Logging into Tesla Power Wall') self.localAccessUp = False else: self.localAccessUp = True self.metersDayStart = self.TPWlocal.get_meters() generator = self.TPWlocal._api.get('generators') if not (generator['generators']): self.generatorInstalled = False else: self.generatorInstalled = True solar = self.TPWlocal.get_solars() if solar: self.solarInstalled = True else: self.solarInstalled = False
async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from DATA_SCHEMA with values provided by the user. """ power_wall = Powerwall(data[CONF_IP_ADDRESS]) try: site_info = await hass.async_add_executor_job(power_wall.get_site_info) except (PowerwallUnreachableError, ApiError, ConnectionError): raise CannotConnect # Return info that you want to store in the config entry. return {"title": site_info.site_name}
def _mock_powerwall_return_value( site_info=None, charge=None, sitemaster=None, meters=None, grid_status=None, status=None, device_type=None, ): powerwall_mock = MagicMock(Powerwall("1.2.3.4")) powerwall_mock.get_site_info = Mock(return_value=site_info) powerwall_mock.get_charge = Mock(return_value=charge) powerwall_mock.get_sitemaster = Mock(return_value=sitemaster) powerwall_mock.get_meters = Mock(return_value=meters) powerwall_mock.get_grid_status = Mock(return_value=grid_status) powerwall_mock.get_status = Mock(return_value=status) powerwall_mock.get_device_type = Mock(return_value=device_type) return powerwall_mock
async def validate_input(opp: core.OpenPeerPower, data): """Validate the user input allows us to connect. Data has the keys from schema with values provided by the user. """ power_wall = Powerwall(data[CONF_IP_ADDRESS]) password = data[CONF_PASSWORD] try: site_info = await opp.async_add_executor_job( _login_and_fetch_site_info, power_wall, password) except MissingAttributeError as err: # Only log the exception without the traceback _LOGGER.error(str(err)) raise WrongVersion from err # Return info that you want to store in the config entry. return {"title": site_info.site_name}
async def validate_input(hass: core.HomeAssistant, data: dict[str, str]) -> dict[str, str]: """Validate the user input allows us to connect. Data has the keys from schema with values provided by the user. """ power_wall = Powerwall(data[CONF_IP_ADDRESS]) password = data[CONF_PASSWORD] try: site_info, gateway_din = await hass.async_add_executor_job( _login_and_fetch_site_info, power_wall, password) except MissingAttributeError as err: # Only log the exception without the traceback _LOGGER.error(str(err)) raise WrongVersion from err # Return info that you want to store in the config entry. return {"title": site_info.site_name, "unique_id": gateway_din.upper()}
async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. Data has the keys from schema with values provided by the user. """ power_wall = Powerwall(data[CONF_IP_ADDRESS]) try: await hass.async_add_executor_job(power_wall.detect_and_pin_version) site_info = await hass.async_add_executor_job(power_wall.get_site_info) except PowerwallUnreachableError as err: raise CannotConnect from err except MissingAttributeError as err: # Only log the exception without the traceback _LOGGER.error(str(err)) raise WrongVersion from err # Return info that you want to store in the config entry. return {"title": site_info.site_name}
def _fetch_powerwall_data(power_wall: Powerwall) -> PowerwallData: """Process and update powerwall data.""" try: backup_reserve = power_wall.get_backup_reserve_percentage() except MissingAttributeError: backup_reserve = None return PowerwallData( charge=power_wall.get_charge(), site_master=power_wall.get_sitemaster(), meters=power_wall.get_meters(), grid_services_active=power_wall.is_grid_services_active(), grid_status=power_wall.get_grid_status(), backup_reserve=backup_reserve, )
def powerwall(): strm="" try: powerwall = Powerwall(GW_IP) powerwall.login(GW_PASSWORD, GW_USER) charge=powerwall.get_charge() meters = powerwall.get_meters() meters.charge=charge strm = str(meters.__dict__) except Exception as ex: print("Could not access Tesla Gateway: "+ str(ex)) return Response(str(ex), mimetype="application/json") return Response(strm, mimetype="application/json")
def main(): """ Get credentials from vault. Poke backup gateway. Push stats to influxdb """ vault = get_hvac_client() bg_creds = vault.secrets.kv.v1.read_secret(BG_GATEWAY_SECRETS_PATH) influxdb_creds = vault.secrets.kv.v1.read_secret(INFLUXDB_CREDS_PATH) powerwall = Powerwall(BG_GATEWAY_HOST) try: powerwall.detect_and_pin_version() except PowerwallUnreachableError as e: print(e) return _ = powerwall.login(bg_creds["email"], bg_creds["password"]) battery_pct_charge = powerwall.get_charge() site_info = powerwall.get_site_info() meters = power_wall.get_meters() meter_data = meters.response for (meter, data) in meters.items(): pass return
def setUp(self) -> None: self.powerwall = Powerwall(POWERWALL_IP) self.powerwall.login(POWERWALL_PASSWORD)
class TestPowerwall(unittest.TestCase): def setUp(self) -> None: self.powerwall = Powerwall(POWERWALL_IP) self.powerwall.login(POWERWALL_PASSWORD) def tearDown(self) -> None: self.powerwall.close() def test_get_charge(self) -> None: charge = self.powerwall.get_charge() self.assertIsInstance(charge, float) def test_get_meters(self) -> None: meters = self.powerwall.get_meters() self.assertIsInstance(meters, MetersAggregates) self.assertIsInstance(meters.site, Meter) self.assertIsInstance(meters.solar, Meter) self.assertIsInstance(meters.battery, Meter) self.assertIsInstance(meters.load, Meter) self.assertIsInstance(meters.get_meter(MeterType.SOLAR), Meter) for meter_type in MeterType: meter = meters.get_meter(meter_type) meter = meters.battery meter.energy_exported meter.energy_imported meter.instant_power meter.last_communication_time meter.frequency meter.average_voltage meter.get_energy_exported() meter.get_energy_imported() self.assertIsInstance(meter.get_power(), float) self.assertIsInstance(meter.is_active(), bool) self.assertIsInstance(meter.is_drawing_from(), bool) self.assertIsInstance(meter.is_sending_to(), bool) def test_sitemaster(self) -> None: sitemaster = self.powerwall.get_sitemaster() self.assertIsInstance(sitemaster, SiteMaster) sitemaster.status sitemaster.is_running sitemaster.is_connected_to_tesla sitemaster.is_power_supply_mode def test_site_info(self) -> None: site_info = self.powerwall.get_site_info() self.assertIsInstance(site_info, SiteInfo) site_info.nominal_system_energy site_info.site_name site_info.timezone def get_grid_status(self) -> None: grid_status = self.powerwall.get_grid_status() self.assertIsInstance(grid_status, GridStatus) def get_status(self) -> None: status = self.powerwall.get_status() self.assertIsInstance(status, PowerwallStatus) status.up_time_seconds status.start_time status.version
class PowerwallDataManager: """Class to manager powerwall data and relogin on failure.""" def __init__( self, hass: HomeAssistant, power_wall: Powerwall, ip_address: str, password: str | None, runtime_data: PowerwallRuntimeData, ) -> None: """Init the data manager.""" self.hass = hass self.ip_address = ip_address self.password = password self.runtime_data = runtime_data self.power_wall = power_wall @property def login_failed_count(self) -> int: """Return the current number of failed logins.""" return self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] @property def api_changed(self) -> int: """Return true if the api has changed out from under us.""" return self.runtime_data[POWERWALL_API_CHANGED] def _increment_failed_logins(self) -> None: self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] += 1 def _clear_failed_logins(self) -> None: self.runtime_data[POWERWALL_LOGIN_FAILED_COUNT] = 0 def _recreate_powerwall_login(self) -> None: """Recreate the login on auth failure.""" http_session = self.runtime_data[POWERWALL_HTTP_SESSION] http_session.close() http_session = requests.Session() self.runtime_data[POWERWALL_HTTP_SESSION] = http_session self.power_wall = Powerwall(self.ip_address, http_session=http_session) self.power_wall.login(self.password or "") async def async_update_data(self) -> PowerwallData: """Fetch data from API endpoint.""" # Check if we had an error before _LOGGER.debug("Checking if update failed") if self.api_changed: raise UpdateFailed("The powerwall api has changed") return await self.hass.async_add_executor_job(self._update_data) def _update_data(self) -> PowerwallData: """Fetch data from API endpoint.""" _LOGGER.debug("Updating data") for attempt in range(2): try: if attempt == 1: self._recreate_powerwall_login() data = _fetch_powerwall_data(self.power_wall) except PowerwallUnreachableError as err: raise UpdateFailed( "Unable to fetch data from powerwall") from err except MissingAttributeError as err: _LOGGER.error("The powerwall api has changed: %s", str(err)) # The error might include some important information about what exactly changed. persistent_notification.create(self.hass, API_CHANGED_ERROR_BODY, API_CHANGED_TITLE) self.runtime_data[POWERWALL_API_CHANGED] = True raise UpdateFailed("The powerwall api has changed") from err except AccessDeniedError as err: if attempt == 1: self._increment_failed_logins() raise ConfigEntryAuthFailed from err if self.password is None: raise ConfigEntryAuthFailed from err raise UpdateFailed( f"Login attempt {self.login_failed_count}/{MAX_LOGIN_FAILURES} failed, will retry: {err}" ) from err except APIError as err: raise UpdateFailed( f"Updated failed due to {err}, will retry") from err else: self._clear_failed_logins() return data raise RuntimeError("unreachable")
def _mock_powerwall_side_effect(site_info=None): powerwall_mock = MagicMock(Powerwall("1.2.3.4")) powerwall_mock.get_site_info = Mock(side_effect=site_info) return powerwall_mock
def _login_and_fetch_site_info(power_wall: Powerwall, password: str): """Login to the powerwall and fetch the base info.""" if password is not None: power_wall.login(password) power_wall.detect_and_pin_version() return power_wall.get_site_info()