class NefitEasy: def __init__(self, hass, credentials): from aionefit import NefitCore _LOGGER.debug("Initialize Nefit class") self.data = {} self.ids = {} self.hass = hass self.error_state = "initializing" self.events = {'/ecus/rrc/uiStatus': asyncio.Event()} self.nefit = NefitCore(serial_number=credentials.get(CONF_SERIAL), access_key=credentials.get(CONF_ACCESSKEY), password=credentials.get(CONF_PASSWORD), message_callback=self.parse_message) self.nefit.failed_auth_handler = self.failed_auth_handler self.nefit.no_content_callback = self.no_content_callback async def connect(self): self.nefit.connect() _LOGGER.debug("Waiting for connected event") try: # await self.nefit.xmppclient.connected_event.wait() await asyncio.wait_for( self.nefit.xmppclient.connected_event.wait(), timeout=15.0) _LOGGER.debug("adding stop listener") self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown) except concurrent.futures._base.TimeoutError: _LOGGER.debug("TimeoutError on waiting for connected event") self.hass.components.persistent_notification.create( 'Timeout while connecting to Bosch cloud. Retrying in the background', title='Nefit error', notification_id='nefit_logon_error') raise PlatformNotReady if self.error_state == "authentication_failed": self.hass.components.persistent_notification.create( 'Invalid credentials while connecting to Bosch cloud.', title='Nefit error', notification_id='nefit_logon_error') raise PlatformNotReady def shutdown(self, event): _LOGGER.debug("Shutdown connection to Bosch cloud") self.nefit.disconnect() def no_content_callback(self, data): _LOGGER.debug("no_content_callback: %s", data) def failed_auth_handler(self, event): self.error_state = "authentication_failed" self.nefit.xmppclient.connected_event.set() def parse_message(self, data): """Message received callback function for the XMPP client. """ _LOGGER.debug("parse_message callback called with data %s", data) if not 'id' in data: _LOGGER.error("Unknown response received: %s", data) return if data['id'] == '/ecus/rrc/uiStatus': self.data['uistatus'] = data['value'] self.data['temp_setpoint'] = float( data['value']['TSP']) #for climate self.data['inhouse_temperature'] = float( data['value']['IHT']) #for climate self.data['user_mode'] = data['value']['UMD'] #for climate self.data['boiler_indicator'] = data['value']['BAI'] #for climate self.data['last_update'] = data['value']['CTD'] elif data['id'] == '/dhwCircuits/dhwA/dhwOperationClockMode' or data[ 'id'] == '/dhwCircuits/dhwA/dhwOperationManualMode': self.data['hot_water'] = data['value'] self.events['hot_water'].set() if data['id'] in self.events: if data['id'] in self.ids: self.data[self.ids[data['id']]] = data['value'] # Mark event as finished self.events[data['id']].set()
class NefitEasy: def __init__(self, hass, credentials): from aionefit import NefitCore _LOGGER.debug("Initialize Nefit class") self.data = {} #stores device states and values self.keys = {} #unique name for entity self.events = {} self.uiStatusVars = {} #variables to monitor for sensors self.hass = hass self.connected_state = STATE_INIT self.nefit = NefitCore(serial_number=credentials.get(CONF_SERIAL), access_key=credentials.get(CONF_ACCESSKEY), password=credentials.get(CONF_PASSWORD), message_callback=self.parse_message) self.nefit.failed_auth_handler = self.failed_auth_handler self.nefit.no_content_callback = self.no_content_callback async def connect(self): self.nefit.connect() _LOGGER.debug("Waiting for connected event") try: # await self.nefit.xmppclient.connected_event.wait() await asyncio.wait_for(self.nefit.xmppclient.connected_event.wait(), timeout=60.0) self.connected_state = STATE_CONNECTED _LOGGER.debug("adding stop listener") self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown) except concurrent.futures._base.TimeoutError: _LOGGER.debug("TimeoutError on waiting for connected event") self.hass.components.persistent_notification.create( 'Timeout while connecting to Bosch cloud.', title='Nefit error', notification_id='nefit_logon_error') raise PlatformNotReady if self.connected_state == STATE_ERROR_AUTH: self.hass.components.persistent_notification.create( 'Invalid credentials while connecting to Bosch cloud.', title='Nefit error', notification_id='nefit_logon_error') raise PlatformNotReady def shutdown(self, event): _LOGGER.debug("Shutdown connection to Bosch cloud") self.nefit.disconnect() def no_content_callback(self, data): _LOGGER.debug("no_content_callback: %s", data) def failed_auth_handler(self, event): self.connected_state = STATE_ERROR_AUTH self.nefit.xmppclient.connected_event.set() def parse_message(self, data): """Message received callback function for the XMPP client. """ _LOGGER.debug("parse_message data %s", data) if not 'id' in data: _LOGGER.error("Unknown response received: %s", data) return if data['id'] in self.keys: key = self.keys[data['id']] _LOGGER.debug("Got update for %s.", key) if data['id'] == '/ecus/rrc/uiStatus': self.data['temp_setpoint'] = float(data['value']['TSP']) #for climate self.data['inhouse_temperature'] = float(data['value']['IHT']) #for climate self.data['user_mode'] = data['value']['UMD'] #for climate self.data['boiler_indicator'] = data['value']['BAI'] #for climate self.data['last_update'] = data['value']['CTD'] for uikey in self.uiStatusVars: self.updateDeviceValue(uikey, data['value'].get(self.uiStatusVars[uikey])) self.updateDeviceValue(key, data['value']) # Mark event as finished if it was part of an update action if key in self.events: self.events[key].set() def updateDeviceValue(self, key, value): """Store new device value and send to dispatcher to be picked up by device""" self.data[key] = value #send update signal to dispatcher to pick up new state signal = DISPATCHER_ON_DEVICE_UPDATE.format(key=key) async_dispatcher_send(self.hass, signal)
class NefitThermostat(ClimateDevice): """Representation of a NefitThermostat device.""" def __init__(self, hass, config): from aionefit import NefitCore """Initialize the thermostat.""" name = config.get(CONF_NAME) serial = config.get(CONF_SERIAL) accesskey = config.get(CONF_ACCESSKEY) password = config.get(CONF_PASSWORD) self.config = config self.hass = hass self._name = name self.error_state = "initializing" self._online = False self._unit_of_measurement = TEMP_CELSIUS self._uistatus = None self._attributes = {} self._stateattr = {} self._data = {} self._hvac_modes = [HVAC_MODE_HEAT] self._url_events = { '/ecus/rrc/uiStatus': asyncio.Event(), '/heatingCircuits/hc1/actualSupplyTemperature': asyncio.Event(), '/system/sensors/temperatures/outdoor_t1': asyncio.Event(), '/system/appliance/systemPressure': asyncio.Event(), '/ecus/rrc/recordings/yearTotal': asyncio.Event() } self._client = NefitCore(serial_number=serial, access_key=accesskey, password=password, message_callback=self.parse_message) self._client.failed_auth_handler = self.failed_auth_handler self._client.no_content_callback = self.no_content_callback async def connect(self): self._client.connect() _LOGGER.debug("Waiting for connected event") try: # await self._client.xmppclient.connected_event.wait() await asyncio.wait_for(self._client.xmppclient.connected_event.wait(), timeout=10.0) _LOGGER.debug("adding stop listener") self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self._shutdown) except concurrent.futures._base.TimeoutError: _LOGGER.debug("TimeoutError on waiting for connected event") self.hass.components.persistent_notification.create( 'Timeout while connecting to Bosch cloud. Retrying in the background', title='Nefit error', notification_id='nefit_logon_error') raise PlatformNotReady if self.error_state == "authentication_failed": self.hass.components.persistent_notification.create( 'Invalid credentials while connecting to Bosch cloud.', title='Nefit error', notification_id='nefit_logon_error') raise PlatformNotReady def no_content_callback(self, data): _LOGGER.debug("no_content_callback: %s", data) def failed_auth_handler(self, event): self.error_state = "authentication_failed" self._client.xmppclient.connected_event.set() @property def supported_features(self): """Return the list of supported features. """ return SUPPORT_FLAGS @property def target_temperature_step(self): return 0.5 def parse_message(self, data): """Message received callback function for the XMPP client. """ _LOGGER.debug("parse_message callback called with data %s", data) if not 'id' in data: _LOGGER.error("Unknown response received: %s", data) return if data['id'] == '/ecus/rrc/uiStatus': self._uistatus = data['value'] self._data['temp_setpoint'] = float(data['value']['TSP']) self._data['inhouse_temperature'] = float(data['value']['IHT']) self._data['user_mode'] = data['value']['UMD'] self._stateattr['boiler_indicator'] = data['value']['BAI'] self._stateattr['current_time'] = data['value']['CTD'] elif data['id'] == '/heatingCircuits/hc1/actualSupplyTemperature': self._stateattr['supply_temperature'] = data['value'] elif data['id'] == '/system/sensors/temperatures/outdoor_t1': self._stateattr['outdoor_temperature'] = data['value'] elif data['id'] == '/system/appliance/systemPressure': self._stateattr['system_pressure'] = data['value'] elif data['id'] == '/ecus/rrc/recordings/yearTotal': self._stateattr['year_total'] = data['value'] if data['id'] in self._url_events: self._url_events[data['id']].set() async def async_update(self): """Get latest data """ _LOGGER.debug("async_update called") tasks = [] for url in self._url_events: event = self._url_events[url] event.clear() self._client.get(url) tasks.append(asyncio.wait_for(event.wait(), timeout=10)) await asyncio.gather(*tasks) _LOGGER.debug("async_update finished") @property def name(self): """Return the name of the ClimateDevice. """ return self._name @property def temperature_unit(self): """Return the unit of measurement. """ return self._unit_of_measurement @property def current_temperature(self): """Return the current temperature. """ return self._data.get('inhouse_temperature') @property def target_temperature(self): return self._data.get('temp_setpoint') @property def hvac_modes (self): """List of available modes.""" return self._hvac_modes @property def hvac_mode(self): return HVAC_MODE_HEAT @property def hvac_action(self): """Return the current running hvac operation if supported.""" if self._stateattr.get('boiler_indicator') == 'CH': #HW (hot water) is not for climate return CURRENT_HVAC_HEAT return CURRENT_HVAC_IDLE @property def preset_modes(self): """Return available preset modes.""" return [ OPERATION_CLOCK ] @property def preset_mode(self): """Return the current preset mode.""" if self._data.get('user_mode') == 'manual': return None elif self._data.get('user_mode') == 'clock': return OPERATION_CLOCK else: return None @property def device_state_attributes(self): """Return the device specific state attributes.""" return self._stateattr @property def min_temp(self): """Return the minimum temperature.""" return self.config.get(CONF_MIN_TEMP) @property def max_temp(self): """Return the maximum temperature.""" return self.config.get(CONF_MAX_TEMP) async def async_set_preset_mode(self, preset_mode): """Set new target operation mode.""" _LOGGER.debug("set_preset_mode called mode={}.".format(preset_mode)) if preset_mode == OPERATION_CLOCK: new_mode = "clock" else: new_mode = "manual" self._client.set_usermode(new_mode) await asyncio.wait_for(self._client.xmppclient.message_event.wait(), timeout=10.0) self._client.xmppclient.message_event.clear() self._data['user_mode'] = new_mode async def async_set_temperature(self, **kwargs): """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) self._data['target_temperature'] = temperature _LOGGER.debug("set_temperature called (temperature={}).".format(temperature)) self._client.set_temperature(temperature) await asyncio.wait_for(self._client.xmppclient.message_event.wait(), timeout=10.0) self._client.xmppclient.message_event.clear() self._data['target_temperature'] = temperature def _shutdown(self, event): _LOGGER.debug("shutdown") self._client.disconnect()
class NefitEasy: def __init__(self, hass, credentials): from aionefit import NefitCore _LOGGER.debug("Initialize Nefit class") self.data = {} #stores device states and values self.keys = {} #unique name for entity self.events = {} self.uiStatusVars = {} #variables to monitor for sensors self.hass = hass self.connected_state = STATE_INIT self.expected_end = False self.serial = credentials.get(CONF_SERIAL) self.nefit = NefitCore(serial_number=credentials.get(CONF_SERIAL), access_key=credentials.get(CONF_ACCESSKEY), password=credentials.get(CONF_PASSWORD), message_callback=self.parse_message) self.nefit.failed_auth_handler = self.failed_auth_handler self.nefit.no_content_callback = self.no_content_callback self.nefit.session_end_callback = self.session_end_callback async def connect(self): retriesA = 0 while self.connected_state != STATE_CONNECTION_VERIFIED and retriesA < 3: self.nefit.connect() _LOGGER.debug("Waiting for connected event") try: await asyncio.wait_for( self.nefit.xmppclient.connected_event.wait(), timeout=60.0) self.connected_state = STATE_CONNECTED _LOGGER.debug("adding stop listener") self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.shutdown) except concurrent.futures._base.TimeoutError: _LOGGER.debug("TimeoutError on waiting for connected event") retriesA = retriesA + 1 #test password for decrypting messages if self.connected_state == STATE_CONNECTED: _LOGGER.info("Testing connection") retriesB = 0 while self.connected_state != STATE_CONNECTION_VERIFIED and retriesB < 3: try: self.nefit.get('/gateway/brandID') await asyncio.wait_for( self.nefit.xmppclient.message_event.wait(), timeout=30.0) self.connected_state = STATE_CONNECTION_VERIFIED self.nefit.xmppclient.message_event.clear() _LOGGER.info( "Connected %s with %d retries and %d test retries.", self.serial, retriesA, retriesB) except concurrent.futures._base.TimeoutError: _LOGGER.error( "Did not get a response in time for testing connection." ) retriesB = retriesB + 1 except: _LOGGER.error( "No connection while testing connection.") break if self.connected_state != STATE_CONNECTION_VERIFIED: self.hass.components.persistent_notification.create( 'Did not succeed in connecting {} to Bosch cloud after retrying 3 times.' .format(self.serial), title='Nefit connect error', notification_id='nefit_connect_error') def shutdown(self, event): _LOGGER.debug("Shutdown connection to Bosch cloud") self.expected_end = True self.nefit.disconnect() def no_content_callback(self, data): _LOGGER.debug("no_content_callback: %s", data) def failed_auth_handler(self, event): self.connected_state = STATE_ERROR_AUTH self.nefit.xmppclient.connected_event.set() #disconnect, since nothing will work from now. self.shutdown('auth_failed') if event == 'auth_error_password': self.hass.components.persistent_notification.create( 'Invalid password for connecting {} to Bosch cloud.'.format( self.serial), title='Nefit password error', notification_id='nefit_password_error') else: self.hass.components.persistent_notification.create( 'Invalid credentials (serial or accesskey) for connecting {} to Bosch cloud.' .format(self.serial), title='Nefit authentication error', notification_id='nefit_logon_error') def session_end_callback(self): """If connection is closed unexpectedly, try to reconnect""" if not self.expected_end: self.hass.components.persistent_notification.create( 'Unexpected disconnect of {} with Bosch server'.format( self.serial), title='Nefit disconnect', notification_id='nefit_disconnect') #loop = asyncio.get_event_loop() #loop.run_until_complete(self.connect()) #loop.close() def parse_message(self, data): """Message received callback function for the XMPP client.""" _LOGGER.debug("parse_message data %s", data) if not 'id' in data: _LOGGER.error("Unknown response received: %s", data) return if data['id'] in self.keys: key = self.keys[data['id']] _LOGGER.debug("Got update for %s.", key) if data['id'] == '/ecus/rrc/uiStatus' and self.connected_state == STATE_CONNECTION_VERIFIED: self.data['temp_setpoint'] = float( data['value']['TSP']) #for climate self.data['inhouse_temperature'] = float( data['value']['IHT']) #for climate self.data['user_mode'] = data['value']['UMD'] #for climate self.data['boiler_indicator'] = data['value'][ 'BAI'] #for climate self.data['last_update'] = data['value']['CTD'] # Update all sensors/switches when there is new data form uiStatus for uikey in self.uiStatusVars: self.updateDeviceValue( uikey, data['value'].get(self.uiStatusVars[uikey])) self.updateDeviceValue(key, data['value']) # Mark event as finished if it was part of an update action if key in self.events: self.events[key].set() def updateDeviceValue(self, key, value): """Store new device value and send to dispatcher to be picked up by device""" self.data[key] = value #send update signal to dispatcher to pick up new state signal = DISPATCHER_ON_DEVICE_UPDATE.format(key=key) async_dispatcher_send(self.hass, signal) async def get_value(self, key, url): isNewKey = not url in self.keys if isNewKey: self.events[key] = asyncio.Event() self.keys[url] = key event = self.events[key] event.clear() #clear old event self.nefit.get(url) await asyncio.wait_for(event.wait(), timeout=9) if isNewKey: del self.events[key] del self.keys[url] return self.data[key]