async def main_loop(loop, password, user, ip): # pylint: disable=invalid-name """Main loop.""" async with aiohttp.ClientSession( loop=loop, connector=aiohttp.TCPConnector(ssl=False)) as session: VAR["sma"] = pysma.SMA(session, ip, password=password, group=user) await VAR["sma"].new_session() if VAR["sma"].sma_sid is None: _LOGGER.info("No session ID") return _LOGGER.info("NEW SID: %s", VAR["sma"].sma_sid) VAR["running"] = True cnt = 5 sensors = pysma.Sensors() while VAR.get("running"): await VAR["sma"].read(sensors) print_table(sensors) send_table(sensors) cnt -= 1 if cnt == 0: break await asyncio.sleep(2) await VAR["sma"].close_session()
async def test_new_session_fail(self, mock_aioresponse): # noqa: F811 mock_aioresponse.post(f"{self.base_url}/dyn/login.json", payload={"result": {}}) session = aiohttp.ClientSession() sma = pysma.SMA(session, self.host, "pass") assert not await sma.new_session()
async def test_session(self, mock_warn, mock_aioresponse): # noqa: F811 mock_aioresponse.post(f"{self.base_url}/dyn/login.json", payload={"result": { "sid": "ABCD" }}) session = aiohttp.ClientSession() sma = pysma.SMA(session, self.host, "extralongpassword") assert await sma.new_session() assert mock_warn.call_count == 1 await sma.close_session() await sma.close_session() assert mock_warn.call_count == 1
async def test_device_info_fail(self, mock_aioresponse): # noqa: F811 mock_aioresponse.post(f"{self.base_url}/dyn/login.json", payload={"result": { "sid": "ABCD" }}) mock_aioresponse.post( f"{self.base_url}/dyn/getValues.json?sid=ABCD", payload={}, ) session = aiohttp.ClientSession() sma = pysma.SMA(session, self.host, "pass") assert await sma.new_session() result = await sma.device_info() assert not result
async def validate_input(hass: core.HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect.""" session = async_get_clientsession(hass, verify_ssl=data[CONF_VERIFY_SSL]) protocol = "https" if data[CONF_SSL] else "http" url = f"{protocol}://{data[CONF_HOST]}" sma = pysma.SMA(session, url, data[CONF_PASSWORD], group=data[CONF_GROUP]) # new_session raises SmaAuthenticationException on failure await sma.new_session() device_info = await sma.device_info() await sma.close_session() return device_info
async def test_device_info(self, mock_aioresponse): # noqa: F811 mock_aioresponse.post(f"{self.base_url}/dyn/login.json", payload={"result": { "sid": "ABCD" }}) mock_aioresponse.post( f"{self.base_url}/dyn/getValues.json?sid=ABCD", payload={ "result": { "0199-xxxxx385": { "6800_08822B00": { "1": [{ "validVals": [461], "val": [{ "tag": 461 }] }] }, "6800_00A21E00": { "1": [{ "low": 0, "high": None, "val": MOCK_DEVICE["serial"] }] }, "6800_08822000": { "1": [{ "validVals": [9401, 9402, 9403, 9404, 9405], "val": [{ "tag": 9402 }], }] }, "6800_10821E00": { "1": [{ "val": MOCK_DEVICE["name"] }] }, } } }, ) session = aiohttp.ClientSession() sma = pysma.SMA(session, self.host, "pass") result = await sma.device_info() assert result assert result == MOCK_DEVICE
def main(loop, password, ip): """Main loop.""" session = aiohttp.ClientSession(loop=loop) sma = pysma.SMA(session, ip, password=password, group=pysma.GROUP_INSTALLER) yield from sma.new_session() _LOGGER.info("NEW SID: %s", sma._sma_sid) while loop.jk_run: res = yield from sma.read( [pysma.KEY_CURRENT_CONSUMPTION_W, pysma.KEY_CURRENT_POWER_W]) _LOGGER.info(res) yield from asyncio.sleep(1) yield from sma.close_session() yield from session.close()
async def main(loop, address, password, host, interval): """Main loop.""" session = aiohttp.ClientSession(loop=loop) sma = pysma.SMA(session, address, password=password, group="user") await sma.new_session() atexit.register(await cleanup, sma, session) while True: try: with async_timeout.timeout(20): res = await sma.read([m["sensor"] for m in METRICS]) putval(res) except asyncio.TimeoutError: _LOGGER.error(traceback.format_exc()) _LOGGER.error("Timeout when talking to SMA inverter") sys.exit(3) await asyncio.sleep(interval)
async def validate_input(hass: core.HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: """Validate the user input allows us to connect.""" session = async_get_clientsession(hass, verify_ssl=data[CONF_VERIFY_SSL]) protocol = "https" if data[CONF_SSL] else "http" url = f"{protocol}://{data[CONF_HOST]}" sma = pysma.SMA(session, url, data[CONF_PASSWORD], group=data[CONF_GROUP]) if await sma.new_session() is False: raise InvalidAuth device_info = await sma.device_info() await sma.close_session() if not device_info: raise CannotRetrieveDeviceInfo return device_info
async def main_loop(loop, password, user, ip): # pylint: disable=invalid-name """Main loop.""" async with aiohttp.ClientSession(loop=loop) as session: VAR['sma'] = pysma.SMA(session, ip, password=password, group=user) await VAR['sma'].new_session() if VAR['sma'].sma_sid is None: _LOGGER.info("No session ID") return _LOGGER.info("NEW SID: %s", VAR['sma'].sma_sid) VAR['running'] = True cnt = 5 while VAR.get('running'): await VAR['sma'].read(pysma.SENSORS) cnt -= 1 if cnt == 0: break await asyncio.sleep(2) await VAR['sma'].close_session()
async def main_loop(password, user, url): """Run main loop.""" async with aiohttp.ClientSession( connector=aiohttp.TCPConnector(ssl=False) ) as session: VAR["sma"] = pysma.SMA(session, url, password=password, group=user) try: await VAR["sma"].new_session() except pysma.exceptions.SmaAuthenticationException: _LOGGER.warning("Authentication failed!") return except pysma.exceptions.SmaConnectionException: _LOGGER.warning("Unable to connect to device at %s", url) return # We should not get any exceptions, but if we do we will close the session. try: VAR["running"] = True cnt = 5 sensors = await VAR["sma"].get_sensors() device_info = await VAR["sma"].device_info() for name, value in device_info.items(): print("{:>15}{:>25}".format(name, value)) # enable all sensors for sensor in sensors: sensor.enabled = True while VAR.get("running"): await VAR["sma"].read(sensors) print_table(sensors) cnt -= 1 if cnt == 0: break await asyncio.sleep(2) finally: _LOGGER.info("Closing Session...") await VAR["sma"].close_session()
async def test_device_info_fallback(self, mock_aioresponse): # noqa: F811 mock_aioresponse.post(f"{self.base_url}/dyn/login.json", payload={"result": { "sid": "ABCD" }}) mock_aioresponse.post( f"{self.base_url}/dyn/getValues.json?sid=ABCD", payload={ "result": { "0199-xxxxx385": { "6800_08822B00": { "1": [{ "validVals": [461], "val": [{ "tag": 461 }] }] }, "6800_10821E00": { "1": [{ "val": MOCK_DEVICE["name"] }] }, } } }, ) session = aiohttp.ClientSession() sma = pysma.SMA(session, self.host, "pass") assert await sma.new_session() result = await sma.device_info() assert result assert result["manufacturer"] == "SMA" assert result["name"] == MOCK_DEVICE["name"] assert result["type"] == "" assert result["serial"] == "9999999999"
def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up SMA WebConnect sensor.""" import pysma # Sensor_defs from the library sensor_defs = dict( zip(SENSOR_OPTIONS, [(pysma.KEY_CURRENT_CONSUMPTION_W, 'W', 1), (pysma.KEY_CURRENT_POWER_W, 'W', 1), (pysma.KEY_TOTAL_CONSUMPTION_KWH, 'kWh', 1000), (pysma.KEY_TOTAL_YIELD_KWH, 'kWh', 1000)])) # Sensor_defs from the custom config for name, prop in config[CONF_CUSTOM].items(): if name in sensor_defs: _LOGGER.warning("Custom sensor %s replace built-in sensor", name) sensor_defs[name] = (prop['key'], prop['unit'], prop['factor']) # Prepare all HASS sensor entities hass_sensors = [] used_sensors = [] for name, attr in config[CONF_SENSORS].items(): hass_sensors.append(SMAsensor(name, attr, sensor_defs)) used_sensors.append(name) used_sensors.extend(attr) # Remove sensor_defs not in use sensor_defs = { name: val for name, val in sensor_defs.items() if name in used_sensors } async_add_entities(hass_sensors) # Init the SMA interface session = async_get_clientsession(hass) grp = { GROUP_INSTALLER: pysma.GROUP_INSTALLER, GROUP_USER: pysma.GROUP_USER }[config[CONF_GROUP]] url = "http{}://{}".format("s" if config[CONF_SSL] else "", config[CONF_HOST]) sma = pysma.SMA(session, url, config[CONF_PASSWORD], group=grp) # Ensure we logout on shutdown @asyncio.coroutine def async_close_session(event): """Close the session.""" yield from sma.close_session() hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_close_session) # Read SMA values periodically & update sensors names_to_query = list(sensor_defs.keys()) keys_to_query = [sensor_defs[name][0] for name in names_to_query] backoff = 0 @asyncio.coroutine def async_sma(event): """Update all the SMA sensors.""" nonlocal backoff if backoff > 1: backoff -= 1 return values = yield from sma.read(keys_to_query) if values is None: backoff = 3 return values = [0 if val is None else val for val in values] res = dict(zip(names_to_query, values)) res = {key: val // sensor_defs[key][2] for key, val in res.items()} _LOGGER.debug("Update sensors %s %s %s", keys_to_query, values, res) tasks = [] for sensor in hass_sensors: task = sensor.async_update_values(res) if task: tasks.append(task) if tasks: yield from asyncio.wait(tasks, loop=hass.loop) interval = config.get(CONF_SCAN_INTERVAL) or timedelta(seconds=5) async_track_time_interval(hass, async_sma, interval)
async def test_init(self): """Initialize & close the SMA transport class.""" aiosession = None sma = pysma.SMA(aiosession, "192.168.0.100", "pass") await sma.close_session()
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up sma from a config entry.""" # Init the SMA interface protocol = "https" if entry.data[CONF_SSL] else "http" url = f"{protocol}://{entry.data[CONF_HOST]}" verify_ssl = entry.data[CONF_VERIFY_SSL] group = entry.data[CONF_GROUP] password = entry.data[CONF_PASSWORD] session = async_get_clientsession(hass, verify_ssl=verify_ssl) sma = pysma.SMA(session, url, password, group) try: # Get updated device info sma_device_info = await sma.device_info() # Get all device sensors sensor_def = await sma.get_sensors() except ( pysma.exceptions.SmaReadException, pysma.exceptions.SmaConnectionException, ) as exc: raise ConfigEntryNotReady from exc if TYPE_CHECKING: assert entry.unique_id # Create DeviceInfo object from sma_device_info device_info = DeviceInfo( configuration_url=url, identifiers={(DOMAIN, entry.unique_id)}, manufacturer=sma_device_info["manufacturer"], model=sma_device_info["type"], name=sma_device_info["name"], sw_version=sma_device_info["sw_version"], ) # Define the coordinator async def async_update_data(): """Update the used SMA sensors.""" try: await sma.read(sensor_def) except ( pysma.exceptions.SmaReadException, pysma.exceptions.SmaConnectionException, ) as exc: raise UpdateFailed(exc) from exc interval = timedelta( seconds=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)) coordinator = DataUpdateCoordinator( hass, _LOGGER, name="sma", update_method=async_update_data, update_interval=interval, ) try: await coordinator.async_config_entry_first_refresh() except ConfigEntryNotReady: await sma.close_session() raise # Ensure we logout on shutdown async def async_close_session(event): """Close the session.""" await sma.close_session() remove_stop_listener = hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close_session) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { PYSMA_OBJECT: sma, PYSMA_COORDINATOR: coordinator, PYSMA_SENSORS: sensor_def, PYSMA_REMOVE_LISTENER: remove_stop_listener, PYSMA_DEVICE_INFO: device_info, } await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up SMA WebConnect sensor.""" import pysma # Check config again during load - dependency available config = _check_sensor_schema(config) # Init all default sensors sensor_def = pysma.Sensors() # Sensor from the custom config sensor_def.add([ pysma.Sensor(o[CONF_KEY], n, o[CONF_UNIT], o[CONF_FACTOR], o.get(CONF_PATH)) for n, o in config[CONF_CUSTOM].items() ]) # Use all sensors by default config_sensors = config[CONF_SENSORS] if not config_sensors: config_sensors = {s.name: [] for s in sensor_def} # Prepare all HASS sensor entities hass_sensors = [] used_sensors = [] for name, attr in config_sensors.items(): sub_sensors = [sensor_def[s] for s in attr] hass_sensors.append(SMAsensor(sensor_def[name], sub_sensors)) used_sensors.append(name) used_sensors.extend(attr) async_add_entities(hass_sensors) used_sensors = [sensor_def[s] for s in set(used_sensors)] # Init the SMA interface session = async_get_clientsession(hass, verify_ssl=config[CONF_VERIFY_SSL]) grp = config[CONF_GROUP] url = "http{}://{}".format("s" if config[CONF_SSL] else "", config[CONF_HOST]) sma = pysma.SMA(session, url, config[CONF_PASSWORD], group=grp) # Ensure we logout on shutdown async def async_close_session(event): """Close the session.""" await sma.close_session() hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_close_session) backoff = 0 backoff_step = 0 async def async_sma(event): """Update all the SMA sensors.""" nonlocal backoff, backoff_step if backoff > 1: backoff -= 1 return values = await sma.read(used_sensors) if not values: try: backoff = [1, 1, 1, 6, 30][backoff_step] backoff_step += 1 except IndexError: backoff = 60 return backoff_step = 0 tasks = [] for sensor in hass_sensors: task = sensor.async_update_values() if task: tasks.append(task) if tasks: await asyncio.wait(tasks) interval = config.get(CONF_SCAN_INTERVAL) or timedelta(seconds=5) async_track_time_interval(hass, async_sma, interval)
def test_init(self): """Initialize & close the SMA transport class.""" aiosession = None sma = pysma.SMA(aiosession, '192.168.0.100', 'pass') sma.close_session()
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up sma from a config entry.""" # Init all default sensors sensor_def = pysma.Sensors() if entry.source == SOURCE_IMPORT: config_sensors = _parse_legacy_options(entry, sensor_def) _migrate_old_unique_ids(hass, entry, sensor_def, config_sensors) # Init the SMA interface protocol = "https" if entry.data[CONF_SSL] else "http" url = f"{protocol}://{entry.data[CONF_HOST]}" verify_ssl = entry.data[CONF_VERIFY_SSL] group = entry.data[CONF_GROUP] password = entry.data[CONF_PASSWORD] session = async_get_clientsession(hass, verify_ssl=verify_ssl) sma = pysma.SMA(session, url, password, group) # Define the coordinator async def async_update_data(): """Update the used SMA sensors.""" values = await sma.read(sensor_def) if not values: raise UpdateFailed interval = timedelta( seconds=entry.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) ) coordinator = DataUpdateCoordinator( hass, _LOGGER, name="sma", update_method=async_update_data, update_interval=interval, ) try: await coordinator.async_config_entry_first_refresh() except ConfigEntryNotReady: await sma.close_session() raise # Ensure we logout on shutdown async def async_close_session(event): """Close the session.""" await sma.close_session() remove_stop_listener = hass.bus.async_listen_once( EVENT_HOMEASSISTANT_STOP, async_close_session ) hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { PYSMA_OBJECT: sma, PYSMA_COORDINATOR: coordinator, PYSMA_SENSORS: sensor_def, PYSMA_REMOVE_LISTENER: remove_stop_listener, } hass.config_entries.async_setup_platforms(entry, PLATFORMS) return True
async def test_new_session_invalid_group(self, mock_aioresponse): # noqa: F811 session = aiohttp.ClientSession() with pytest.raises(KeyError): pysma.SMA(session, self.host, "pass", "invalid-group")