part of 'main.dart'; class HomeAssistant { static const DEFAULT_DASHBOARD = 'lovelace'; static final HomeAssistant _instance = HomeAssistant._internal(); factory HomeAssistant() { return _instance; } EntityCollection entities; HomeAssistantUI ui; Map _instanceConfig = {}; String _userName; String currentDashboardPath; HSVColor savedColor; int savedPlayerPosition; String sendToPlayerId; String sendFromPlayerId; Map services; bool autoUi = false; String fcmToken; Map _rawLovelaceData; var _rawStates; var _rawUserInfo; var _rawPanels; List panels = []; Duration fetchTimeout = Duration(seconds: 30); String get locationName { if (!autoUi) { return ui?.title ?? "Home"; } else { return _instanceConfig["location_name"] ?? "Home"; } } String get userName => _userName ?? ''; String get userAvatarText => userName.length > 0 ? userName[0] : ""; bool get isNoEntities => entities == null || entities.isEmpty; bool get isNoViews => ui == null || ui.isEmpty; HomeAssistant._internal() { ConnectionManager().onStateChangeCallback = _handleEntityStateChange; ConnectionManager().onLovelaceUpdatedCallback = _handleLovelaceUpdate; DeviceInfoManager().loadDeviceInfo(); } Completer _fetchCompleter; Future fetchData(bool uiOnly) { if (_fetchCompleter != null && !_fetchCompleter.isCompleted) { Logger.w("Previous data fetch is not completed yet"); return _fetchCompleter.future; } _fetchCompleter = Completer(); List futures = []; if (!uiOnly) { if (entities == null) entities = EntityCollection(AppSettings().httpWebHost); futures.add(_getStates(null)); futures.add(_getConfig(null)); futures.add(_getUserInfo(null)); futures.add(_getPanels(null)); futures.add(_getServices(null)); } if (!autoUi) { futures.add(_getLovelace(null)); } Future.wait(futures).then((_) { if (isComponentEnabled('mobile_app')) { _createUI(); _fetchCompleter.complete(); } else { _fetchCompleter.completeError(HACException("Mobile app component not found", actions: [HAErrorAction.tryAgain(), HAErrorAction(type: HAErrorActionType.URL ,title: "Help",url: "http://ha-client.app/docs#mobile-app-integration")])); } }).catchError((e) { _fetchCompleter.completeError(e); }); return _fetchCompleter.future; } Future fetchDataFromCache() async { Logger.d('Loading cached data'); SharedPreferences prefs = await SharedPreferences.getInstance(); bool cached = prefs.getBool('cached'); if (cached != null && cached) { if (entities == null) entities = EntityCollection(AppSettings().httpWebHost); try { _getStates(prefs); if (!autoUi) { _getLovelace(prefs); } _getConfig(prefs); _getUserInfo(prefs); _getPanels(prefs); _getServices(prefs); if (isComponentEnabled('mobile_app')) { _createUI(); } } catch (e) { Logger.d('Didnt get cached data: $e'); } } } void saveCache() async { Logger.d('Saving data to cache...'); SharedPreferences prefs = await SharedPreferences.getInstance(); try { await prefs.setString('cached_states', json.encode(_rawStates)); await prefs.setString('cached_lovelace', json.encode(_rawLovelaceData)); await prefs.setString('cached_user', json.encode(_rawUserInfo)); await prefs.setString('cached_config', json.encode(_instanceConfig)); await prefs.setString('cached_panels', json.encode(_rawPanels)); await prefs.setString('cached_services', json.encode(services)); await prefs.setBool('cached', true); } catch (e, stacktrace) { await prefs.setBool('cached', false); Logger.e('Error saving cache: $e', stacktrace: stacktrace); } Logger.d('Done saving cache'); } Future logout() async { Logger.d("Logging out..."); await ConnectionManager().logout().then((_) { ui?.clear(); entities?.clear(); panels?.clear(); }); } Future _getConfig(SharedPreferences sharedPrefs) async { _instanceConfig?.clear(); if (sharedPrefs != null && sharedPrefs.containsKey('cached_config')) { try { var data = json.decode(sharedPrefs.getString('cached_config')); _parseConfig(data); } catch (e, stacktrace) { Logger.e('Error gettong config from cache: $e', stacktrace: stacktrace); } } else { await ConnectionManager().sendSocketMessage(type: "get_config").then((data) => _parseConfig(data)).catchError((e) { Logger.e('get_config error: $e'); throw HACException("Error getting config: $e"); }); } } void _parseConfig(data) { _instanceConfig = data; } Future _getStates(SharedPreferences sharedPrefs) async { if (sharedPrefs != null && sharedPrefs.containsKey('cached_states')) { try { var data = json.decode(sharedPrefs.getString('cached_states')); _parseStates(data ?? []); } catch (e, stacktrace) { Logger.e('Error getting cached states: $e', stacktrace: stacktrace); } } else { await ConnectionManager().sendSocketMessage(type: "get_states").then( (data) => _parseStates(data) ).catchError((e) { Logger.e('get_states error: $e'); throw HACException("Error getting states: $e"); }); } } void _parseStates(data) { _rawStates = data; entities.parse(data); } Future _getLovelace(SharedPreferences sharedPrefs) { if (sharedPrefs != null && sharedPrefs.containsKey('cached_lovelace')) { try { var data = json.decode(sharedPrefs.getString('cached_lovelace')); _rawLovelaceData = data; } catch (e) { autoUi = true; } return Future.value(); } else { Completer completer = Completer(); var additionalData; if (AppSettings().haVersion >= 107 && currentDashboardPath != HomeAssistant.DEFAULT_DASHBOARD) { additionalData = { 'url_path': currentDashboardPath }; } ConnectionManager().sendSocketMessage( type: 'lovelace/config', additionalData: additionalData ).then((data) { _rawLovelaceData = data; completer.complete(); }).catchError((e) { if ("$e".contains("config_not_found")) { autoUi = true; _rawLovelaceData = null; completer.complete(); } else { Logger.e('lovelace/config error: $e'); completer.completeError(HACException("Error getting lovelace config: $e")); } }); return completer.future; } } Future _getServices(SharedPreferences prefs) async { services?.clear(); if (prefs != null && prefs.containsKey('cached_services')) { try { var data = json.decode(prefs.getString('cached_services')); _parseServices(data ?? {}); } catch (e, stacktrace) { Logger.e(e, stacktrace: stacktrace); } } await ConnectionManager().sendSocketMessage(type: "get_services").then((data) => _parseServices(data)).catchError((e) { Logger.e('get_services error: $e'); }); } void _parseServices(data) { services = data; } Future _getUserInfo(SharedPreferences sharedPrefs) async { _userName = null; if (sharedPrefs != null && sharedPrefs.containsKey('cached_user')) { try { var data = json.decode(sharedPrefs.getString('cached_user')); _parseUserInfo(data ?? {}); } catch (e, stacktrace) { Logger.e('Error getting cached user info: $e', stacktrace: stacktrace); } return Future.value(); } else { await ConnectionManager().sendSocketMessage(type: "auth/current_user").then((data) => _parseUserInfo(data)).catchError((e) { Logger.e('auth/current_user error: $e'); }); } } void _parseUserInfo(data) { _rawUserInfo = data; _userName = data["name"]; } Future _getPanels(SharedPreferences sharedPrefs) async { if (sharedPrefs != null && sharedPrefs.containsKey('cached_panels')) { try { var data = json.decode(sharedPrefs.getString('cached_panels')); _parsePanels(data ?? {}); } catch (e, stacktrace) { Logger.e(e, stacktrace: stacktrace); panels.clear(); } } else { await ConnectionManager().sendSocketMessage(type: "get_panels").then((data) => _parsePanels(data)).catchError((e, stacktrace) { Logger.e('get_panels error: $e', stacktrace: stacktrace); panels.clear(); throw HACException('Can\'t get panels: $e'); }); } } void _parsePanels(data) { panels.clear(); _rawPanels = data; List dashboards = []; data.forEach((k,v) { String title = v['title'] == null ? "${k[0].toUpperCase()}${k.substring(1)}" : "${v['title'][0].toUpperCase()}${v['title'].substring(1)}"; if (v['component_name'] != null && v['component_name'] == 'lovelace') { dashboards.add( Panel( id: k, componentName: v['component_name'], title: title, urlPath: v['url_path'], config: v['config'], icon: (v['icon'] == null || v['icon'] == 'hass:view-dashboard') ? 'mdi:view-dashboard' : v['icon'] ) ); } else { panels.add( Panel( id: k, componentName: v['component_name'], title: title, urlPath: v['url_path'], config: v['config'], icon: v['icon'] ) ); } }); panels.insertAll(0, dashboards); } Future getCameraStream(String entityId) { Completer completer = Completer(); ConnectionManager().sendSocketMessage(type: "camera/stream", additionalData: {"entity_id": entityId}).then((data) { completer.complete(data); }).catchError((e) { completer.completeError(e); }); return completer.future; } bool isComponentEnabled(String name) { return _instanceConfig["components"] != null && (_instanceConfig["components"] as List).contains("$name"); } void _handleLovelaceUpdate() { if (_fetchCompleter != null && _fetchCompleter.isCompleted) { eventBus.fire(new LovelaceChangedEvent()); } } void _handleEntityStateChange(Map eventData) { if (_fetchCompleter != null && _fetchCompleter.isCompleted) { Map data = Map.from(eventData); eventBus.fire(new StateChangedEvent( entityId: data["entity_id"], needToRebuildUI: entities.updateState(data) )); } } bool isServiceExist(String service) { return services != null && services.isNotEmpty && services.containsKey(service); } void _createUI() { Logger.d("Creating Lovelace UI"); ui = HomeAssistantUI(rawLovelaceConfig: _rawLovelaceData); } }