HubitatServer: Adds HubitatServer type and adds it to the multiplexer
This commit is contained in:
@@ -39,6 +39,8 @@
|
|||||||
CD815B8E241C6F4000E2FA97 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B8D241C6F4000E2FA97 /* ServerProtocol.swift */; };
|
CD815B8E241C6F4000E2FA97 /* ServerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B8D241C6F4000E2FA97 /* ServerProtocol.swift */; };
|
||||||
CD815B90241C7CAC00E2FA97 /* DeviceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B8F241C7CAC00E2FA97 /* DeviceProtocol.swift */; };
|
CD815B90241C7CAC00E2FA97 /* DeviceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B8F241C7CAC00E2FA97 /* DeviceProtocol.swift */; };
|
||||||
CD815B92241DA28B00E2FA97 /* ServerMultiplex.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B91241DA28B00E2FA97 /* ServerMultiplex.swift */; };
|
CD815B92241DA28B00E2FA97 /* ServerMultiplex.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B91241DA28B00E2FA97 /* ServerMultiplex.swift */; };
|
||||||
|
CD815B94241DC16700E2FA97 /* HubitatServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B93241DC16700E2FA97 /* HubitatServer.swift */; };
|
||||||
|
CD815B96241DC44D00E2FA97 /* HubitatDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD815B95241DC44D00E2FA97 /* HubitatDevice.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
@@ -79,6 +81,8 @@
|
|||||||
CD815B8D241C6F4000E2FA97 /* ServerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerProtocol.swift; sourceTree = "<group>"; };
|
CD815B8D241C6F4000E2FA97 /* ServerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerProtocol.swift; sourceTree = "<group>"; };
|
||||||
CD815B8F241C7CAC00E2FA97 /* DeviceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProtocol.swift; sourceTree = "<group>"; };
|
CD815B8F241C7CAC00E2FA97 /* DeviceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProtocol.swift; sourceTree = "<group>"; };
|
||||||
CD815B91241DA28B00E2FA97 /* ServerMultiplex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerMultiplex.swift; sourceTree = "<group>"; };
|
CD815B91241DA28B00E2FA97 /* ServerMultiplex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerMultiplex.swift; sourceTree = "<group>"; };
|
||||||
|
CD815B93241DC16700E2FA97 /* HubitatServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HubitatServer.swift; sourceTree = "<group>"; };
|
||||||
|
CD815B95241DC44D00E2FA97 /* HubitatDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HubitatDevice.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -126,6 +130,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
CD815B8F241C7CAC00E2FA97 /* DeviceProtocol.swift */,
|
CD815B8F241C7CAC00E2FA97 /* DeviceProtocol.swift */,
|
||||||
|
CD815B95241DC44D00E2FA97 /* HubitatDevice.swift */,
|
||||||
0C4314FF1C35B1E700164559 /* WemoDevice.swift */,
|
0C4314FF1C35B1E700164559 /* WemoDevice.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
@@ -227,6 +232,7 @@
|
|||||||
children = (
|
children = (
|
||||||
CD815B91241DA28B00E2FA97 /* ServerMultiplex.swift */,
|
CD815B91241DA28B00E2FA97 /* ServerMultiplex.swift */,
|
||||||
CD815B8D241C6F4000E2FA97 /* ServerProtocol.swift */,
|
CD815B8D241C6F4000E2FA97 /* ServerProtocol.swift */,
|
||||||
|
CD815B93241DC16700E2FA97 /* HubitatServer.swift */,
|
||||||
0CAA32781C361A2400B353CF /* WemoServer.swift */,
|
0CAA32781C361A2400B353CF /* WemoServer.swift */,
|
||||||
);
|
);
|
||||||
path = Servers;
|
path = Servers;
|
||||||
@@ -318,9 +324,11 @@
|
|||||||
0C41EF4E1C34B81F00AF847F /* KSIRingView.m in Sources */,
|
0C41EF4E1C34B81F00AF847F /* KSIRingView.m in Sources */,
|
||||||
0CAA327B1C361CBE00B353CF /* NSErrorAdditions.swift in Sources */,
|
0CAA327B1C361CBE00B353CF /* NSErrorAdditions.swift in Sources */,
|
||||||
0CF0F8961C362D8F00FE7752 /* NSURLAdditions.swift in Sources */,
|
0CF0F8961C362D8F00FE7752 /* NSURLAdditions.swift in Sources */,
|
||||||
|
CD815B94241DC16700E2FA97 /* HubitatServer.swift in Sources */,
|
||||||
0CAA32791C361A2400B353CF /* WemoServer.swift in Sources */,
|
0CAA32791C361A2400B353CF /* WemoServer.swift in Sources */,
|
||||||
0C41EF561C35ABCD00AF847F /* ConnectionStatusView.swift in Sources */,
|
0C41EF561C35ABCD00AF847F /* ConnectionStatusView.swift in Sources */,
|
||||||
0C63A87D1C33C655007E4B52 /* NSValue+XIONAdditions.m in Sources */,
|
0C63A87D1C33C655007E4B52 /* NSValue+XIONAdditions.m in Sources */,
|
||||||
|
CD815B96241DC44D00E2FA97 /* HubitatDevice.swift in Sources */,
|
||||||
0C20E2BD1C33534F00BA789B /* VisualizationViewController.swift in Sources */,
|
0C20E2BD1C33534F00BA789B /* VisualizationViewController.swift in Sources */,
|
||||||
CD815B90241C7CAC00E2FA97 /* DeviceProtocol.swift in Sources */,
|
CD815B90241C7CAC00E2FA97 /* DeviceProtocol.swift in Sources */,
|
||||||
0C41EF4A1C34B51500AF847F /* SwitchesViewController.swift in Sources */,
|
0C41EF4A1C34B51500AF847F /* SwitchesViewController.swift in Sources */,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate, Serv
|
|||||||
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
||||||
|
|
||||||
_serverMultiplex.delegate = self
|
_serverMultiplex.delegate = self
|
||||||
|
_serverMultiplex.addServer(HubitatServer(URL(string: "https://midna.xionsf.com:6000/apps/api/1/")!))
|
||||||
_serverMultiplex.addServer(WemoServer(URL(string: "http://midna.xionsf.com:5000")!))
|
_serverMultiplex.addServer(WemoServer(URL(string: "http://midna.xionsf.com:5000")!))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +204,7 @@ class MainViewController: UIViewController, SwitchesViewControllerDelegate, Serv
|
|||||||
|
|
||||||
func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice])
|
func serverMultiplex(_ multiplex: ServerMultiplex, didAddDevices devices: [AnyDevice])
|
||||||
{
|
{
|
||||||
_switchesController.devices = devices
|
_switchesController.devices = Array(multiplex.devices)
|
||||||
_updateVisualization(false)
|
_updateVisualization(false)
|
||||||
|
|
||||||
// Update connection status too, if applicable
|
// Update connection status too, if applicable
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ class SwitchesViewController: UIViewController,
|
|||||||
})
|
})
|
||||||
|
|
||||||
var snapshot = _collectionViewDataSource.snapshot()
|
var snapshot = _collectionViewDataSource.snapshot()
|
||||||
|
snapshot.deleteItems(snapshot.itemIdentifiers(inSection: SwitchesViewController.switchCellsSectionIdentifier))
|
||||||
snapshot.appendItems(self.devices.map { $0.hashValue }, toSection: SwitchesViewController.switchCellsSectionIdentifier)
|
snapshot.appendItems(self.devices.map { $0.hashValue }, toSection: SwitchesViewController.switchCellsSectionIdentifier)
|
||||||
_collectionViewDataSource.apply(snapshot, animatingDifferences: false)
|
_collectionViewDataSource.apply(snapshot, animatingDifferences: false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum DeviceState
|
enum DeviceState : String, Codable
|
||||||
{
|
{
|
||||||
case off
|
case off = "off"
|
||||||
case on
|
case on = "on"
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DeviceType
|
enum DeviceType : String, Codable
|
||||||
{
|
{
|
||||||
case `switch`
|
case `switch` = "switch"
|
||||||
}
|
}
|
||||||
|
|
||||||
protocol Device
|
protocol Device
|
||||||
|
|||||||
47
XIONControlPanel/Models/HubitatDevice.swift
Normal file
47
XIONControlPanel/Models/HubitatDevice.swift
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// HubitatDevice.swift
|
||||||
|
// XIONControlPanel
|
||||||
|
//
|
||||||
|
// Created by James Magahern on 3/14/20.
|
||||||
|
// Copyright © 2020 XION. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct HubitatDevice : Device
|
||||||
|
{
|
||||||
|
var name: String = ""
|
||||||
|
var serial: String = ""
|
||||||
|
var type: DeviceType = .switch
|
||||||
|
var state: DeviceState = .off
|
||||||
|
|
||||||
|
var id: Int = -1
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case name = "label"
|
||||||
|
case id = "id"
|
||||||
|
case attributes = "attributes"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AttributesKeys: String, CodingKey {
|
||||||
|
case power
|
||||||
|
case dataType
|
||||||
|
case `switch`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension HubitatDevice : Decodable
|
||||||
|
{
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
let idStr = try values.decode(String.self, forKey: .id)
|
||||||
|
id = Int(idStr)!
|
||||||
|
serial = String(format: "hubitat:%d", id)
|
||||||
|
|
||||||
|
name = try values.decode(String.self, forKey: .name)
|
||||||
|
|
||||||
|
let attributes = try values.nestedContainer(keyedBy: Self.AttributesKeys, forKey: .attributes)
|
||||||
|
state = try attributes.decode(DeviceState.self, forKey: .switch)
|
||||||
|
}
|
||||||
|
}
|
||||||
82
XIONControlPanel/Servers/HubitatServer.swift
Normal file
82
XIONControlPanel/Servers/HubitatServer.swift
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// HubitatServer.swift
|
||||||
|
// XIONControlPanel
|
||||||
|
//
|
||||||
|
// Created by James Magahern on 3/14/20.
|
||||||
|
// Copyright © 2020 XION. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
class HubitatServer : Server
|
||||||
|
{
|
||||||
|
var connected: Bool = false
|
||||||
|
|
||||||
|
public fileprivate(set) var devices: [HubitatDevice] = []
|
||||||
|
|
||||||
|
private let baseURL: URL
|
||||||
|
private let accessToken = "fa861583-71f0-466f-8add-f29b2278af6a"
|
||||||
|
private var cancellable: AnyCancellable?
|
||||||
|
|
||||||
|
required init(_ url: URL)
|
||||||
|
{
|
||||||
|
self.baseURL = url
|
||||||
|
}
|
||||||
|
|
||||||
|
func connect(_ completion: @escaping (Error?) -> Void)
|
||||||
|
{
|
||||||
|
// RESTful, assume we're connected unless we get an error
|
||||||
|
self.connected = true
|
||||||
|
completion(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnect(_ completion: (Error?) -> Void)
|
||||||
|
{
|
||||||
|
self.connected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchDevices(_ fetchCompletion: @escaping (Result<[AnyDevice], Error>) -> Void)
|
||||||
|
{
|
||||||
|
let url = baseURL.appendingPathComponent("devices/all").authenticatedURLWithAccessToken(accessToken)
|
||||||
|
|
||||||
|
self.cancellable = URLSession.shared.dataTaskPublisher(for: url)
|
||||||
|
.mapError { $0 as Error }
|
||||||
|
.map { $0.data }
|
||||||
|
.decode(type: [HubitatDevice].self, decoder: JSONDecoder())
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
.sink(receiveCompletion: { completion in
|
||||||
|
if case let Subscribers.Completion.failure(error) = completion {
|
||||||
|
fetchCompletion(.failure(error))
|
||||||
|
}
|
||||||
|
}) { (devices: [HubitatDevice]) in
|
||||||
|
self.devices = devices
|
||||||
|
fetchCompletion(.success(devices.map { AnyDevice($0) }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggleDevice(_ device: AnyDevice, state: DeviceState, completion: @escaping (Error?) -> Void)
|
||||||
|
{
|
||||||
|
if let hubitatDevice = (self.devices.first { $0.serial == device.serial }) {
|
||||||
|
print("HUBITAT: Toggle: ", hubitatDevice.id)
|
||||||
|
|
||||||
|
// TODO...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func responsibleForDevice(_ device: AnyDevice) -> Bool
|
||||||
|
{
|
||||||
|
return self.devices.contains { $0.serial == device.serial }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension URL
|
||||||
|
{
|
||||||
|
func authenticatedURLWithAccessToken(_ token: String) -> URL
|
||||||
|
{
|
||||||
|
guard var components = URLComponents(url: self, resolvingAgainstBaseURL: false) else { fatalError() }
|
||||||
|
components.queryItems = [ URLQueryItem(name: "access_token", value: token) ]
|
||||||
|
|
||||||
|
return components.url!
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,14 +102,7 @@ extension ServerMultiplex
|
|||||||
private func handleDevicesChanged(_ devicesChanged: [AnyDevice])
|
private func handleDevicesChanged(_ devicesChanged: [AnyDevice])
|
||||||
{
|
{
|
||||||
let newDevicesSet = Set<AnyDevice>(devicesChanged)
|
let newDevicesSet = Set<AnyDevice>(devicesChanged)
|
||||||
|
|
||||||
let additions = newDevicesSet.subtracting(self.devices)
|
let additions = newDevicesSet.subtracting(self.devices)
|
||||||
if additions.count > 0 {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.delegate?.serverMultiplex(self, didAddDevices: Array(additions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let changed = newDevicesSet.filter { (device: AnyDevice) in
|
let changed = newDevicesSet.filter { (device: AnyDevice) in
|
||||||
if let existing = (devices.first { $0.hashValue == device.hashValue }) {
|
if let existing = (devices.first { $0.hashValue == device.hashValue }) {
|
||||||
return existing.state != device.state
|
return existing.state != device.state
|
||||||
@@ -118,13 +111,19 @@ extension ServerMultiplex
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.devices = self.devices.union(newDevicesSet)
|
||||||
|
|
||||||
|
if additions.count > 0 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.delegate?.serverMultiplex(self, didAddDevices: Array(additions))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if changed.count > 0 {
|
if changed.count > 0 {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.delegate?.serverMultiplex(self, devicesStateChanged: Array(changed))
|
self.delegate?.serverMultiplex(self, devicesStateChanged: Array(changed))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.devices = self.devices.union(newDevicesSet)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleError(_ error: Error)
|
private func handleError(_ error: Error)
|
||||||
|
|||||||
Reference in New Issue
Block a user