From c2b88c8a1285e760a91e1fc26fdf679ed6058a32 Mon Sep 17 00:00:00 2001 From: Yegor Vialov Date: Sat, 6 Oct 2018 16:01:38 +0300 Subject: [PATCH] Resolves #124: Connection handling improvements --- lib/home_assistant.class.dart | 97 +++++++++++++++++------------------ lib/main.dart | 20 ++++---- lib/settings.page.dart | 33 +++++------- 3 files changed, 70 insertions(+), 80 deletions(-) diff --git a/lib/home_assistant.class.dart b/lib/home_assistant.class.dart index 7aa7df0..378ddb9 100644 --- a/lib/home_assistant.class.dart +++ b/lib/home_assistant.class.dart @@ -55,27 +55,23 @@ class HomeAssistant { } else { _fetchCompleter = new Completer(); _fetchTimer = Timer(fetchTimeout, () { - closeConnection(); TheLogger.log("Error", "Data fetching timeout"); - _finishFetching({"errorCode" : 9,"errorMessage": "Couldn't get data from server"}); + _completeFetching({"errorCode" : 9,"errorMessage": "Couldn't get data from server"}); }); _connection().then((r) { _getData(); }).catchError((e) { - _finishFetching(e); + _completeFetching(e); }); } return _fetchCompleter.future; } - closeConnection() { - if (_socketSubscription != null) { - _socketSubscription.cancel(); - } - if (_hassioChannel != null) { - if (_hassioChannel.closeCode == null) { - _hassioChannel.sink?.close(); - } + disconnect() async { + if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) { + await _hassioChannel.sink.close().timeout(Duration(seconds: 3), + onTimeout: () => TheLogger.log("Warning", "Socket sink closing timeout") + ); _hassioChannel = null; } } @@ -84,30 +80,35 @@ class HomeAssistant { if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) { TheLogger.log("Debug","Previous connection is not complited"); } else { - if ((_hassioChannel == null) || (_hassioChannel.sink == null) || (_hassioChannel.closeCode != null)) { - closeConnection(); - TheLogger.log("Debug", "Socket connecting..."); - _connectionCompleter = new Completer(); - _connectionTimer = Timer(connectTimeout, () { - closeConnection(); - TheLogger.log("Error", "Socket connection timeout"); - _finishConnecting({"errorCode" : 1,"errorMessage": "Couldn't connect to Home Assistant. Looks like a network issues"}); - }); - _hassioChannel = IOWebSocketChannel.connect( + _connectionCompleter = new Completer(); + if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) { + disconnect().then((_){ + TheLogger.log("Debug", "Socket connecting..."); + _connectionTimer = Timer(connectTimeout, () { + TheLogger.log("Error", "Socket connection timeout"); + _completeConnecting({"errorCode" : 1,"errorMessage": "Couldn't connect to Home Assistant. Check network connection or connection settings."}); + }); + if (_socketSubscription != null) { + _socketSubscription.cancel(); + } + _hassioChannel = IOWebSocketChannel.connect( _hassioAPIEndpoint, pingInterval: Duration(seconds: 30)); - _hassioChannel.stream.handleError((e) { - TheLogger.log("Error", "Unhandled socket error: ${e.toString()}"); - }); - if (_socketSubscription != null) _socketSubscription.cancel(); - _socketSubscription = _hassioChannel.stream.listen((message) => - _handleMessage(_connectionCompleter, message)); - _hassioChannel.sink.done.whenComplete(() { - TheLogger.log("Debug","Socket sink finished. Assuming it is closed."); - closeConnection(); + _socketSubscription = _hassioChannel.stream.listen( + (message) => _handleMessage(_connectionCompleter, message), + cancelOnError: true, + onDone: () { + TheLogger.log("Debug","Socket stream closed. Disconnected."); + disconnect(); + }, + onError: (e) { + TheLogger.log("Error","Socket stream Error: $e"); + disconnect().then((_) => _completeConnecting({"errorCode" : 1,"errorMessage": "Couldn't connect to Home Assistant. Check network connection or connection settings."})); + } + ); }); } else { //TheLogger.log("Debug","Socket looks connected...${_hassioChannel.protocol}, ${_hassioChannel.closeCode}, ${_hassioChannel.closeReason}"); - _finishConnecting(null); + _completeConnecting(null); } } return _connectionCompleter.future; @@ -117,38 +118,34 @@ class HomeAssistant { _getConfig().then((result) { _getStates().then((result) { _getServices().then((result) { - _finishFetching(null); - }).catchError((e) { - _finishFetching(e); + _completeFetching(null); }); - }).catchError((e) { - _finishFetching(e); }); }).catchError((e) { - _finishFetching(e); + _completeFetching(e); }); } - void _finishFetching(error) { + void _completeFetching(error) { _fetchTimer.cancel(); - _finishConnecting(error); - if (error != null) { - if (!_fetchCompleter.isCompleted) + _completeConnecting(error); + if (!_fetchCompleter.isCompleted) { + if (error != null) { _fetchCompleter.completeError(error); - } else { - if (!_fetchCompleter.isCompleted) + } else { _fetchCompleter.complete(); + } } } - void _finishConnecting(error) { + void _completeConnecting(error) { _connectionTimer.cancel(); - if (error != null) { - if (!_connectionCompleter.isCompleted) + if (!_connectionCompleter.isCompleted) { + if (error != null) { _connectionCompleter.completeError(error); - } else { - if (!_connectionCompleter.isCompleted) + } else { _connectionCompleter.complete(); + } } } @@ -158,10 +155,10 @@ class HomeAssistant { if (data["type"] == "auth_required") { _sendAuthMessageRaw('{"type": "auth","$_hassioAuthType": "$_hassioPassword"}'); } else if (data["type"] == "auth_ok") { - _finishConnecting(null); + _completeConnecting(null); _sendSubscribe(); } else if (data["type"] == "auth_invalid") { - _finishFetching({"errorCode": 6, "errorMessage": "${data["message"]}"}); + _completeFetching({"errorCode": 6, "errorMessage": "${data["message"]}"}); } else if (data["type"] == "result") { if (data["id"] == _configMessageId) { _parseConfig(data); diff --git a/lib/main.dart b/lib/main.dart index e5532bc..30e6932 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -118,22 +118,20 @@ class _MainPageState extends State with WidgetsBindingObserver { _settingsSubscription = eventBus.on().listen((event) { TheLogger.log("Debug","Settings change event: reconnect=${event.reconnect}"); if (event.reconnect) { - _homeAssistant.closeConnection(); - _initConnection().then((b){ - setState(() { - _homeAssistant.updateConnectionSettings(_apiEndpoint, _apiPassword, _authType); - _errorCodeToBeShown = 10; - _lastErrorMessage = "Connection settings was changed."; - }); + _homeAssistant.disconnect().then((_){ + _loadConnectionSettings().then((b){ + _refreshData(); }, onError: (_) { setState(() { _lastErrorMessage = _; _errorCodeToBeShown = 5; }); - }); + } + ); + }); } }); - _initConnection().then((_){ + _loadConnectionSettings().then((_){ _createConnection(); }, onError: (_) { setState(() { @@ -151,7 +149,7 @@ class _MainPageState extends State with WidgetsBindingObserver { } } - _initConnection() async { + _loadConnectionSettings() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String domain = prefs.getString('hassio-domain'); String port = prefs.getString('hassio-port'); @@ -681,7 +679,7 @@ class _MainPageState extends State with WidgetsBindingObserver { if (_settingsSubscription != null) _settingsSubscription.cancel(); if (_serviceCallSubscription != null) _serviceCallSubscription.cancel(); if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel(); - _homeAssistant.closeConnection(); + _homeAssistant.disconnect(); super.dispose(); } } diff --git a/lib/settings.page.dart b/lib/settings.page.dart index 805be06..043ec15 100644 --- a/lib/settings.page.dart +++ b/lib/settings.page.dart @@ -11,11 +11,11 @@ class ConnectionSettingsPage extends StatefulWidget { class _ConnectionSettingsPageState extends State { String _hassioDomain = ""; - String _hassioPort = "8123"; + String _hassioPort = ""; String _hassioPassword = ""; String _socketProtocol = "wss"; String _authType = "access_token"; - bool _connectionSettingsChanged = false; + bool _edited = false; @override void initState() { @@ -27,9 +27,9 @@ class _ConnectionSettingsPageState extends State { SharedPreferences prefs = await SharedPreferences.getInstance(); setState(() { - _hassioDomain = prefs.getString("hassio-domain"); - _hassioPort = prefs.getString("hassio-port") ?? '8123'; - _hassioPassword = prefs.getString("hassio-password"); + _hassioDomain = prefs.getString("hassio-domain")?? ""; + _hassioPort = prefs.getString("hassio-port") ?? ""; + _hassioPassword = prefs.getString("hassio-password") ?? ""; _socketProtocol = prefs.getString("hassio-protocol") ?? 'wss'; _authType = prefs.getString("hassio-auth-type") ?? 'access_token'; }); @@ -46,7 +46,6 @@ class _ConnectionSettingsPageState extends State { prefs.setString("hassio-protocol", _socketProtocol); prefs.setString("hassio-res-protocol", _socketProtocol == "wss" ? "https" : "http"); prefs.setString("hassio-auth-type", _authType); - _connectionSettingsChanged = true; } @override @@ -60,16 +59,12 @@ class _ConnectionSettingsPageState extends State { actions: [ IconButton( icon: Icon(Icons.check), - onPressed:(){ - if (_connectionSettingsChanged) { + onPressed: _edited ? (){ _saveSettings().then((r){ Navigator.pop(context); - eventBus.fire(SettingsChangedEvent(_connectionSettingsChanged)); + eventBus.fire(SettingsChangedEvent(true)); }); - } else { - Navigator.pop(context); - } - } + } : null ) ], ), @@ -84,8 +79,8 @@ class _ConnectionSettingsPageState extends State { onChanged: (value) { setState(() { _socketProtocol = value ? "wss" : "ws"; + _edited = true; }); - _saveSettings(); }, ) ], @@ -99,19 +94,18 @@ class _ConnectionSettingsPageState extends State { ), onChanged: (value) { _hassioDomain = value; - _saveSettings(); }, ), new TextField( decoration: InputDecoration( - labelText: "Home Assistant port" + labelText: "Home Assistant port (default is 8123)" ), controller: TextEditingController( text: _hassioPort ), onChanged: (value) { _hassioPort = value; - _saveSettings(); + //_saveSettings(); }, ), new Row( @@ -122,8 +116,9 @@ class _ConnectionSettingsPageState extends State { onChanged: (value) { setState(() { _authType = value ? "access_token" : "api_password"; + _edited = true; }); - _saveSettings(); + //_saveSettings(); }, ) ], @@ -137,7 +132,7 @@ class _ConnectionSettingsPageState extends State { ), onChanged: (value) { _hassioPassword = value; - _saveSettings(); + //_saveSettings(); }, ) ],