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:
@@ -206,11 +206,6 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate, Serv
|
|||||||
{
|
{
|
||||||
_switchesController.devices = Array(multiplex.devices)
|
_switchesController.devices = Array(multiplex.devices)
|
||||||
_updateVisualization(false)
|
_updateVisualization(false)
|
||||||
|
|
||||||
// Update connection status too, if applicable
|
|
||||||
if _serverMultiplex.numServers == _serverMultiplex.numberOfConnectedServers() {
|
|
||||||
_updateConnectivityStatus(.connected)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice])
|
func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice])
|
||||||
@@ -218,6 +213,13 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate, Serv
|
|||||||
_switchesController.devicesStateChanged(devices)
|
_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)
|
func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error)
|
||||||
{
|
{
|
||||||
_updateConnectivityStatus(.error)
|
_updateConnectivityStatus(.error)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public enum HubitatServerError : Error
|
|||||||
|
|
||||||
public class HubitatServer : Server
|
public class HubitatServer : Server
|
||||||
{
|
{
|
||||||
var connected: Bool = false
|
var connectionStatus: ConnectionStatus = .disconnected
|
||||||
|
|
||||||
public fileprivate(set) var devices: [HubitatDevice] = []
|
public fileprivate(set) var devices: [HubitatDevice] = []
|
||||||
|
|
||||||
@@ -33,13 +33,13 @@ public class HubitatServer : Server
|
|||||||
func connect(_ completion: @escaping (Error?) -> Void)
|
func connect(_ completion: @escaping (Error?) -> Void)
|
||||||
{
|
{
|
||||||
// RESTful, assume we're connected unless we get an error
|
// RESTful, assume we're connected unless we get an error
|
||||||
self.connected = true
|
connectionStatus = .connected
|
||||||
completion(nil)
|
completion(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func disconnect(_ completion: (Error?) -> Void)
|
func disconnect(_ completion: (Error?) -> Void)
|
||||||
{
|
{
|
||||||
self.connected = false
|
connectionStatus = .disconnected
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchDevices(_ fetchCompletion: @escaping (Result<[AnyDevice], Error>) -> Void)
|
func fetchDevices(_ fetchCompletion: @escaping (Result<[AnyDevice], Error>) -> Void)
|
||||||
@@ -52,10 +52,10 @@ public class HubitatServer : Server
|
|||||||
.decode(type: [HubitatDevice].self, decoder: JSONDecoder())
|
.decode(type: [HubitatDevice].self, decoder: JSONDecoder())
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
if case let Subscribers.Completion.failure(error) = completion {
|
if case let Subscribers.Completion.failure(error) = completion {
|
||||||
self.connected = false
|
self.connectionStatus = .error
|
||||||
fetchCompletion(.failure(error))
|
fetchCompletion(.failure(error))
|
||||||
} else {
|
} else {
|
||||||
self.connected = true
|
self.connectionStatus = .connected
|
||||||
}
|
}
|
||||||
}) { (devices: [HubitatDevice]) in
|
}) { (devices: [HubitatDevice]) in
|
||||||
self.devices = devices
|
self.devices = devices
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ protocol ServerMultiplexDelegate
|
|||||||
{
|
{
|
||||||
func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice])
|
func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice])
|
||||||
func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice])
|
func serverMultiplex(_ multiplex: ServerMultiplex, devicesStateChanged devices: [AnyDevice])
|
||||||
|
func serverMultiplex(_ multiplex: ServerMultiplex, didReceiveAcknowledgementFromServer server: Server)
|
||||||
func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error)
|
func serverMultiplex(_ multiplex: ServerMultiplex, didEncounterError error: Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +34,26 @@ class ServerMultiplex
|
|||||||
servers.append(server)
|
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)
|
public func toggleDeviceState(_ device: AnyDevice, state: DeviceState, completion: @escaping (Error?) -> Void)
|
||||||
@@ -56,7 +74,7 @@ class ServerMultiplex
|
|||||||
{
|
{
|
||||||
self.servers.forEach { (server: Server) in
|
self.servers.forEach { (server: Server) in
|
||||||
server.fetchDevices { (result: Result<[AnyDevice], Error>) 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
|
extension ServerMultiplex
|
||||||
{
|
{
|
||||||
private func handleServerFetchResult(_ result: Result<[AnyDevice], Error>)
|
private func handleServerFetchResult(forServer server: Server, result: Result<[AnyDevice], Error>)
|
||||||
{
|
{
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let devices):
|
case .success(let devices):
|
||||||
handleDevicesChanged(devices)
|
handleDevicesChanged(forServer: server, devicesChanged: devices)
|
||||||
case .failure(let error):
|
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 newDevicesSet = Set<AnyDevice>(devicesChanged)
|
||||||
let additions = newDevicesSet.subtracting(self.devices)
|
let additions = newDevicesSet.subtracting(self.devices)
|
||||||
let changed = newDevicesSet.filter { (device: AnyDevice) in
|
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 {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.serverMultiplex(self, didEncounterError: error)
|
self.delegate?.serverMultiplex(self, didEncounterError: error)
|
||||||
|
|||||||
@@ -8,9 +8,17 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
enum ConnectionStatus
|
||||||
|
{
|
||||||
|
case disconnected
|
||||||
|
case connecting
|
||||||
|
case connected
|
||||||
|
case error
|
||||||
|
}
|
||||||
|
|
||||||
protocol Server
|
protocol Server
|
||||||
{
|
{
|
||||||
var connected: Bool { get }
|
var connectionStatus: ConnectionStatus { get }
|
||||||
|
|
||||||
/// Designated initializer. Takes an API endpoint URL
|
/// Designated initializer. Takes an API endpoint URL
|
||||||
init(_ url: URL)
|
init(_ url: URL)
|
||||||
|
|||||||
@@ -8,14 +8,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum ConnectionStatus
|
|
||||||
{
|
|
||||||
case disconnected
|
|
||||||
case connecting
|
|
||||||
case connected
|
|
||||||
case error
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ConnectionError : Error
|
enum ConnectionError : Error
|
||||||
{
|
{
|
||||||
case unknown
|
case unknown
|
||||||
@@ -25,11 +17,9 @@ enum ConnectionError : Error
|
|||||||
class WemoServer : Server
|
class WemoServer : Server
|
||||||
{
|
{
|
||||||
private var devices: [WemoDevice] = []
|
private var devices: [WemoDevice] = []
|
||||||
|
public var connectionStatus: ConnectionStatus = .disconnected
|
||||||
public var connected: Bool { get { return self.connectionStatus == .connected } }
|
|
||||||
|
|
||||||
fileprivate(set) var baseURL: URL
|
fileprivate(set) var baseURL: URL
|
||||||
fileprivate(set) var connectionStatus: ConnectionStatus = .disconnected
|
|
||||||
|
|
||||||
fileprivate var _urlSession: URLSession
|
fileprivate var _urlSession: URLSession
|
||||||
fileprivate var _errorStream: StandardErrorOutputStream = StandardErrorOutputStream()
|
fileprivate var _errorStream: StandardErrorOutputStream = StandardErrorOutputStream()
|
||||||
@@ -48,6 +38,8 @@ class WemoServer : Server
|
|||||||
func connect(_ completion: @escaping (Error?) -> Void)
|
func connect(_ completion: @escaping (Error?) -> Void)
|
||||||
{
|
{
|
||||||
if (self.connectionStatus == .disconnected) {
|
if (self.connectionStatus == .disconnected) {
|
||||||
|
self.connectionStatus = .connecting
|
||||||
|
|
||||||
let op = ConnectOperation(baseURL: self.baseURL, session: _urlSession)
|
let op = ConnectOperation(baseURL: self.baseURL, session: _urlSession)
|
||||||
weak var weakOp = op
|
weak var weakOp = op
|
||||||
op.completionBlock = {
|
op.completionBlock = {
|
||||||
@@ -103,7 +95,7 @@ class WemoServer : Server
|
|||||||
|
|
||||||
func toggleDevice(_ device: AnyDevice, state: DeviceState, completion: @escaping (Error?) -> Void)
|
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)
|
let op = ToggleDeviceOperation(baseURL: self.baseURL, session: _urlSession, device: device, state: state)
|
||||||
weak var weakOp = op
|
weak var weakOp = op
|
||||||
op.completionBlock = {
|
op.completionBlock = {
|
||||||
|
|||||||
Reference in New Issue
Block a user