Merge branch 'buzzert/offline_fix' into 'master'

Fixes issue where connectivity issues can lead to persistent "Offline" status in UI

See merge request zanneth/XIONControlPanel!2
This commit is contained in:
zanneth
2020-07-04 22:22:39 -04:00
5 changed files with 55 additions and 31 deletions

View File

@@ -206,11 +206,6 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate, Serv
{
_switchesController.devices = Array(multiplex.devices)
_updateVisualization(false)
// Update connection status too, if applicable
if _serverMultiplex.numServers == _serverMultiplex.numberOfConnectedServers() {
_updateConnectivityStatus(.connected)
}
}
func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice])
@@ -218,6 +213,13 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate, Serv
_switchesController.devicesStateChanged(devices)
}
func serverMultiplex(_ multiplex: ServerMultiplex, didReceiveAcknowledgementFromServer server: Server)
{
// Update connection status too, if applicable
let groupConnectionStatus = multiplex.groupConnectionStatus()
_updateConnectivityStatus(groupConnectionStatus)
}
func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error)
{
_updateConnectivityStatus(.error)

View File

@@ -16,7 +16,7 @@ public enum HubitatServerError : Error
public class HubitatServer : Server
{
var connected: Bool = false
var connectionStatus: ConnectionStatus = .disconnected
public fileprivate(set) var devices: [HubitatDevice] = []
@@ -33,13 +33,13 @@ public class HubitatServer : Server
func connect(_ completion: @escaping (Error?) -> Void)
{
// RESTful, assume we're connected unless we get an error
self.connected = true
connectionStatus = .connected
completion(nil)
}
func disconnect(_ completion: (Error?) -> Void)
{
self.connected = false
connectionStatus = .disconnected
}
func fetchDevices(_ fetchCompletion: @escaping (Result<[AnyDevice], Error>) -> Void)
@@ -52,10 +52,10 @@ public class HubitatServer : Server
.decode(type: [HubitatDevice].self, decoder: JSONDecoder())
.sink(receiveCompletion: { completion in
if case let Subscribers.Completion.failure(error) = completion {
self.connected = false
self.connectionStatus = .error
fetchCompletion(.failure(error))
} else {
self.connected = true
self.connectionStatus = .connected
}
}) { (devices: [HubitatDevice]) in
self.devices = devices

View File

@@ -12,6 +12,7 @@ protocol ServerMultiplexDelegate
{
func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice])
func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice])
func serverMultiplex(_ multiplex: ServerMultiplex, didReceiveAcknowledgementFromServer server: Server)
func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error)
}
@@ -33,9 +34,26 @@ class ServerMultiplex
servers.append(server)
}
public func numberOfConnectedServers() -> Int
/// Returns a status that best represents the status of the whole group, as if it were one device
public func groupConnectionStatus() -> ConnectionStatus
{
return servers.filter { $0.connected == true }.count
let relevancyRank: (ConnectionStatus) -> Int = { status in
switch status {
case .disconnected: return 0
case .connected: return 1
case .connecting: return 2
case .error: return 3
}
}
var groupStatus = ConnectionStatus.connected
servers.forEach { server in
if relevancyRank(server.connectionStatus) > relevancyRank(groupStatus) {
groupStatus = server.connectionStatus
}
}
return groupStatus
}
public func toggleDeviceState(_ device: AnyDevice, state: DeviceState, completion: @escaping (Error?) -> Void)
@@ -56,7 +74,7 @@ class ServerMultiplex
{
self.servers.forEach { (server: Server) in
server.fetchDevices { (result: Result<[AnyDevice], Error>) in
self.handleServerFetchResult(result)
self.handleServerFetchResult(forServer: server, result: result)
}
}
}
@@ -64,18 +82,22 @@ class ServerMultiplex
extension ServerMultiplex
{
private func handleServerFetchResult(_ result: Result<[AnyDevice], Error>)
private func handleServerFetchResult(forServer server: Server, result: Result<[AnyDevice], Error>)
{
switch result {
case .success(let devices):
handleDevicesChanged(devices)
handleDevicesChanged(forServer: server, devicesChanged: devices)
case .failure(let error):
handleError(error)
handleError(forServer: server, error: error)
}
}
private func handleDevicesChanged(_ devicesChanged: [AnyDevice])
private func handleDevicesChanged(forServer server: Server, devicesChanged: [AnyDevice])
{
// First send server acknowledgement
self.delegate?.serverMultiplex(self, didReceiveAcknowledgementFromServer: server)
// Then, optionally notify about new devices or device state changes
let newDevicesSet = Set<AnyDevice>(devicesChanged)
let additions = newDevicesSet.subtracting(self.devices)
let changed = newDevicesSet.filter { (device: AnyDevice) in
@@ -101,7 +123,7 @@ extension ServerMultiplex
}
}
private func handleError(_ error: Error)
private func handleError(forServer server: Server, error: Error)
{
DispatchQueue.main.async {
self.delegate?.serverMultiplex(self, didEncounterError: error)

View File

@@ -8,9 +8,17 @@
import Foundation
enum ConnectionStatus
{
case disconnected
case connecting
case connected
case error
}
protocol Server
{
var connected: Bool { get }
var connectionStatus: ConnectionStatus { get }
/// Designated initializer. Takes an API endpoint URL
init(_ url: URL)

View File

@@ -8,14 +8,6 @@
import Foundation
enum ConnectionStatus
{
case disconnected
case connecting
case connected
case error
}
enum ConnectionError : Error
{
case unknown
@@ -25,11 +17,9 @@ enum ConnectionError : Error
class WemoServer : Server
{
private var devices: [WemoDevice] = []
public var connected: Bool { get { return self.connectionStatus == .connected } }
public var connectionStatus: ConnectionStatus = .disconnected
fileprivate(set) var baseURL: URL
fileprivate(set) var connectionStatus: ConnectionStatus = .disconnected
fileprivate var _urlSession: URLSession
fileprivate var _errorStream: StandardErrorOutputStream = StandardErrorOutputStream()
@@ -48,6 +38,8 @@ class WemoServer : Server
func connect(_ completion: @escaping (Error?) -> Void)
{
if (self.connectionStatus == .disconnected) {
self.connectionStatus = .connecting
let op = ConnectOperation(baseURL: self.baseURL, session: _urlSession)
weak var weakOp = op
op.completionBlock = {
@@ -103,7 +95,7 @@ class WemoServer : Server
func toggleDevice(_ device: AnyDevice, state: DeviceState, completion: @escaping (Error?) -> Void)
{
if self.connected, let device = findDevice(device) {
if connectionStatus == .connected, let device = findDevice(device) {
let op = ToggleDeviceOperation(baseURL: self.baseURL, session: _urlSession, device: device, state: state)
weak var weakOp = op
op.completionBlock = {