Resolves #124: Connection handling improvements

This commit is contained in:
Yegor Vialov 2018-10-06 16:01:38 +03:00
parent c975af4c79
commit c2b88c8a12
3 changed files with 70 additions and 80 deletions

View File

@ -55,27 +55,23 @@ class HomeAssistant {
} else { } else {
_fetchCompleter = new Completer(); _fetchCompleter = new Completer();
_fetchTimer = Timer(fetchTimeout, () { _fetchTimer = Timer(fetchTimeout, () {
closeConnection();
TheLogger.log("Error", "Data fetching timeout"); 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) { _connection().then((r) {
_getData(); _getData();
}).catchError((e) { }).catchError((e) {
_finishFetching(e); _completeFetching(e);
}); });
} }
return _fetchCompleter.future; return _fetchCompleter.future;
} }
closeConnection() { disconnect() async {
if (_socketSubscription != null) { if ((_hassioChannel != null) && (_hassioChannel.closeCode == null) && (_hassioChannel.sink != null)) {
_socketSubscription.cancel(); await _hassioChannel.sink.close().timeout(Duration(seconds: 3),
} onTimeout: () => TheLogger.log("Warning", "Socket sink closing timeout")
if (_hassioChannel != null) { );
if (_hassioChannel.closeCode == null) {
_hassioChannel.sink?.close();
}
_hassioChannel = null; _hassioChannel = null;
} }
} }
@ -84,30 +80,35 @@ class HomeAssistant {
if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) { if ((_connectionCompleter != null) && (!_connectionCompleter.isCompleted)) {
TheLogger.log("Debug","Previous connection is not complited"); TheLogger.log("Debug","Previous connection is not complited");
} else { } else {
if ((_hassioChannel == null) || (_hassioChannel.sink == null) || (_hassioChannel.closeCode != null)) { _connectionCompleter = new Completer();
closeConnection(); if ((_hassioChannel == null) || (_hassioChannel.closeCode != null)) {
TheLogger.log("Debug", "Socket connecting..."); disconnect().then((_){
_connectionCompleter = new Completer(); TheLogger.log("Debug", "Socket connecting...");
_connectionTimer = Timer(connectTimeout, () { _connectionTimer = Timer(connectTimeout, () {
closeConnection(); TheLogger.log("Error", "Socket connection timeout");
TheLogger.log("Error", "Socket connection timeout"); _completeConnecting({"errorCode" : 1,"errorMessage": "Couldn't connect to Home Assistant. Check network connection or connection settings."});
_finishConnecting({"errorCode" : 1,"errorMessage": "Couldn't connect to Home Assistant. Looks like a network issues"}); });
}); if (_socketSubscription != null) {
_hassioChannel = IOWebSocketChannel.connect( _socketSubscription.cancel();
}
_hassioChannel = IOWebSocketChannel.connect(
_hassioAPIEndpoint, pingInterval: Duration(seconds: 30)); _hassioAPIEndpoint, pingInterval: Duration(seconds: 30));
_hassioChannel.stream.handleError((e) { _socketSubscription = _hassioChannel.stream.listen(
TheLogger.log("Error", "Unhandled socket error: ${e.toString()}"); (message) => _handleMessage(_connectionCompleter, message),
}); cancelOnError: true,
if (_socketSubscription != null) _socketSubscription.cancel(); onDone: () {
_socketSubscription = _hassioChannel.stream.listen((message) => TheLogger.log("Debug","Socket stream closed. Disconnected.");
_handleMessage(_connectionCompleter, message)); disconnect();
_hassioChannel.sink.done.whenComplete(() { },
TheLogger.log("Debug","Socket sink finished. Assuming it is closed."); onError: (e) {
closeConnection(); 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 { } else {
//TheLogger.log("Debug","Socket looks connected...${_hassioChannel.protocol}, ${_hassioChannel.closeCode}, ${_hassioChannel.closeReason}"); //TheLogger.log("Debug","Socket looks connected...${_hassioChannel.protocol}, ${_hassioChannel.closeCode}, ${_hassioChannel.closeReason}");
_finishConnecting(null); _completeConnecting(null);
} }
} }
return _connectionCompleter.future; return _connectionCompleter.future;
@ -117,38 +118,34 @@ class HomeAssistant {
_getConfig().then((result) { _getConfig().then((result) {
_getStates().then((result) { _getStates().then((result) {
_getServices().then((result) { _getServices().then((result) {
_finishFetching(null); _completeFetching(null);
}).catchError((e) {
_finishFetching(e);
}); });
}).catchError((e) {
_finishFetching(e);
}); });
}).catchError((e) { }).catchError((e) {
_finishFetching(e); _completeFetching(e);
}); });
} }
void _finishFetching(error) { void _completeFetching(error) {
_fetchTimer.cancel(); _fetchTimer.cancel();
_finishConnecting(error); _completeConnecting(error);
if (error != null) { if (!_fetchCompleter.isCompleted) {
if (!_fetchCompleter.isCompleted) if (error != null) {
_fetchCompleter.completeError(error); _fetchCompleter.completeError(error);
} else { } else {
if (!_fetchCompleter.isCompleted)
_fetchCompleter.complete(); _fetchCompleter.complete();
}
} }
} }
void _finishConnecting(error) { void _completeConnecting(error) {
_connectionTimer.cancel(); _connectionTimer.cancel();
if (error != null) { if (!_connectionCompleter.isCompleted) {
if (!_connectionCompleter.isCompleted) if (error != null) {
_connectionCompleter.completeError(error); _connectionCompleter.completeError(error);
} else { } else {
if (!_connectionCompleter.isCompleted)
_connectionCompleter.complete(); _connectionCompleter.complete();
}
} }
} }
@ -158,10 +155,10 @@ class HomeAssistant {
if (data["type"] == "auth_required") { if (data["type"] == "auth_required") {
_sendAuthMessageRaw('{"type": "auth","$_hassioAuthType": "$_hassioPassword"}'); _sendAuthMessageRaw('{"type": "auth","$_hassioAuthType": "$_hassioPassword"}');
} else if (data["type"] == "auth_ok") { } else if (data["type"] == "auth_ok") {
_finishConnecting(null); _completeConnecting(null);
_sendSubscribe(); _sendSubscribe();
} else if (data["type"] == "auth_invalid") { } else if (data["type"] == "auth_invalid") {
_finishFetching({"errorCode": 6, "errorMessage": "${data["message"]}"}); _completeFetching({"errorCode": 6, "errorMessage": "${data["message"]}"});
} else if (data["type"] == "result") { } else if (data["type"] == "result") {
if (data["id"] == _configMessageId) { if (data["id"] == _configMessageId) {
_parseConfig(data); _parseConfig(data);

View File

@ -118,22 +118,20 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
_settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) { _settingsSubscription = eventBus.on<SettingsChangedEvent>().listen((event) {
TheLogger.log("Debug","Settings change event: reconnect=${event.reconnect}"); TheLogger.log("Debug","Settings change event: reconnect=${event.reconnect}");
if (event.reconnect) { if (event.reconnect) {
_homeAssistant.closeConnection(); _homeAssistant.disconnect().then((_){
_initConnection().then((b){ _loadConnectionSettings().then((b){
setState(() { _refreshData();
_homeAssistant.updateConnectionSettings(_apiEndpoint, _apiPassword, _authType);
_errorCodeToBeShown = 10;
_lastErrorMessage = "Connection settings was changed.";
});
}, onError: (_) { }, onError: (_) {
setState(() { setState(() {
_lastErrorMessage = _; _lastErrorMessage = _;
_errorCodeToBeShown = 5; _errorCodeToBeShown = 5;
}); });
}); }
);
});
} }
}); });
_initConnection().then((_){ _loadConnectionSettings().then((_){
_createConnection(); _createConnection();
}, onError: (_) { }, onError: (_) {
setState(() { setState(() {
@ -151,7 +149,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
} }
} }
_initConnection() async { _loadConnectionSettings() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
String domain = prefs.getString('hassio-domain'); String domain = prefs.getString('hassio-domain');
String port = prefs.getString('hassio-port'); String port = prefs.getString('hassio-port');
@ -681,7 +679,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
if (_settingsSubscription != null) _settingsSubscription.cancel(); if (_settingsSubscription != null) _settingsSubscription.cancel();
if (_serviceCallSubscription != null) _serviceCallSubscription.cancel(); if (_serviceCallSubscription != null) _serviceCallSubscription.cancel();
if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel(); if (_showEntityPageSubscription != null) _showEntityPageSubscription.cancel();
_homeAssistant.closeConnection(); _homeAssistant.disconnect();
super.dispose(); super.dispose();
} }
} }

View File

@ -11,11 +11,11 @@ class ConnectionSettingsPage extends StatefulWidget {
class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> { class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
String _hassioDomain = ""; String _hassioDomain = "";
String _hassioPort = "8123"; String _hassioPort = "";
String _hassioPassword = ""; String _hassioPassword = "";
String _socketProtocol = "wss"; String _socketProtocol = "wss";
String _authType = "access_token"; String _authType = "access_token";
bool _connectionSettingsChanged = false; bool _edited = false;
@override @override
void initState() { void initState() {
@ -27,9 +27,9 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() { setState(() {
_hassioDomain = prefs.getString("hassio-domain"); _hassioDomain = prefs.getString("hassio-domain")?? "";
_hassioPort = prefs.getString("hassio-port") ?? '8123'; _hassioPort = prefs.getString("hassio-port") ?? "";
_hassioPassword = prefs.getString("hassio-password"); _hassioPassword = prefs.getString("hassio-password") ?? "";
_socketProtocol = prefs.getString("hassio-protocol") ?? 'wss'; _socketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
_authType = prefs.getString("hassio-auth-type") ?? 'access_token'; _authType = prefs.getString("hassio-auth-type") ?? 'access_token';
}); });
@ -46,7 +46,6 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
prefs.setString("hassio-protocol", _socketProtocol); prefs.setString("hassio-protocol", _socketProtocol);
prefs.setString("hassio-res-protocol", _socketProtocol == "wss" ? "https" : "http"); prefs.setString("hassio-res-protocol", _socketProtocol == "wss" ? "https" : "http");
prefs.setString("hassio-auth-type", _authType); prefs.setString("hassio-auth-type", _authType);
_connectionSettingsChanged = true;
} }
@override @override
@ -60,16 +59,12 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
actions: <Widget>[ actions: <Widget>[
IconButton( IconButton(
icon: Icon(Icons.check), icon: Icon(Icons.check),
onPressed:(){ onPressed: _edited ? (){
if (_connectionSettingsChanged) {
_saveSettings().then((r){ _saveSettings().then((r){
Navigator.pop(context); Navigator.pop(context);
eventBus.fire(SettingsChangedEvent(_connectionSettingsChanged)); eventBus.fire(SettingsChangedEvent(true));
}); });
} else { } : null
Navigator.pop(context);
}
}
) )
], ],
), ),
@ -84,8 +79,8 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_socketProtocol = value ? "wss" : "ws"; _socketProtocol = value ? "wss" : "ws";
_edited = true;
}); });
_saveSettings();
}, },
) )
], ],
@ -99,19 +94,18 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
), ),
onChanged: (value) { onChanged: (value) {
_hassioDomain = value; _hassioDomain = value;
_saveSettings();
}, },
), ),
new TextField( new TextField(
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Home Assistant port" labelText: "Home Assistant port (default is 8123)"
), ),
controller: TextEditingController( controller: TextEditingController(
text: _hassioPort text: _hassioPort
), ),
onChanged: (value) { onChanged: (value) {
_hassioPort = value; _hassioPort = value;
_saveSettings(); //_saveSettings();
}, },
), ),
new Row( new Row(
@ -122,8 +116,9 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_authType = value ? "access_token" : "api_password"; _authType = value ? "access_token" : "api_password";
_edited = true;
}); });
_saveSettings(); //_saveSettings();
}, },
) )
], ],
@ -137,7 +132,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
), ),
onChanged: (value) { onChanged: (value) {
_hassioPassword = value; _hassioPassword = value;
_saveSettings(); //_saveSettings();
}, },
) )
], ],