diff --git a/prefab-client/Client/Client+Accessories.swift b/prefab-client/Client/Client+Accessories.swift
index eb773b1..15563d9 100644
--- a/prefab-client/Client/Client+Accessories.swift
+++ b/prefab-client/Client/Client+Accessories.swift
@@ -15,5 +15,10 @@ extension Client {
public func getAccessory(name: String, home: String, room: String) async throws -> String {
return try await get(path: "/accessories/\(home)/\(room)/\(name)")
}
+
+ public func updateAccessory(name: String, home: String, room: String, serviceId: String, characteristicId: String, value: Any) async throws -> String {
+ let updateAccessoryInput: UpdateAccessoryInput = UpdateAccessoryInput(home: home, room: room, accessory: name, serviceId: serviceId, characteristicId: characteristicId, value: value as! String)
+ return try await put(path: "/accessories", data: updateAccessoryInput)
+ }
}
diff --git a/prefab-client/Client/Client.swift b/prefab-client/Client/Client.swift
index 65922c1..d43136a 100644
--- a/prefab-client/Client/Client.swift
+++ b/prefab-client/Client/Client.swift
@@ -74,4 +74,27 @@ public class Client {
}
return body
}
+
+ func put(path: String, data: Codable) async throws -> String {
+ let json = try JSONEncoder().encode(data)
+
+ let request = HTTPRequest(method: .put, scheme: self.scheme, authority: "\(self.host):\(self.port)", path: path)
+ let (data, response) = try await URLSession.shared.upload(for: URLRequest(httpRequest: request)!, from: json)
+ let body = String(decoding: data, as: UTF8.self)
+ if let httpResponse = response as? HTTPURLResponse {
+ guard httpResponse.httpResponse!.status == .ok else {
+ switch httpResponse.httpResponse!.status {
+ case .notFound:
+ throw HTTPResponseError.notFound(response: body)
+ case .tooManyRequests:
+ throw HTTPResponseError.tooManyRequests(response: body)
+ case .forbidden:
+ throw HTTPResponseError.forbidden(response: body)
+ default:
+ throw HTTPResponseError.unexpected(response: body, code: httpResponse.httpResponse!.status.code)
+ }
+ }
+ }
+ return body
+ }
}
diff --git a/prefab-client/Command/Prefab+Accessories.swift b/prefab-client/Command/Prefab+Accessories.swift
index d25087a..e7452bf 100644
--- a/prefab-client/Command/Prefab+Accessories.swift
+++ b/prefab-client/Command/Prefab+Accessories.swift
@@ -51,7 +51,7 @@ extension Prefab {
prefab get-accessory
""",
discussion: """
- Returns an accessories from HomeKit.
+ Returns an accessory from HomeKit.
""")
@Option(name: .shortAndLong, help: "The name of the home you would like accessories for.")
var home: String
@@ -78,3 +78,51 @@ extension Prefab {
}
}
}
+
+
+extension Prefab {
+ struct UpdateAccessory: AsyncParsableCommand {
+ static var configuration = CommandConfiguration(
+ commandName: "update-accessory",
+ abstract: "Update the characteristic of an accessory from a room in your home.",
+ usage: """
+ prefab update-accessory
+ """,
+ discussion: """
+ Update the characteristic of an accessory from a room in your home.
+ """)
+ @Option(name: .shortAndLong, help: "The name of the home you would like accessories for.")
+ var home: String
+
+ @Option(name: .shortAndLong, help: "The name of the room you would like accessories for.")
+ var room: String
+
+ @Option(name: .shortAndLong, help: "The name of the accessory you would like to update.")
+ var accessory: String
+
+ @Option(name: .shortAndLong, help: "The id of the service you would like to update.")
+ var serviceId: String
+
+ @Option(name: .shortAndLong, help: "The id of the characteristic you would like to update.")
+ var characteristicId: String
+
+ @Option(name: .shortAndLong, help: "The value of the characteristic you would like to update.")
+ var value: String
+
+// 0D84EB02-914A-5B90-BF23-EF27764F9438 B16D61DB-EFC7-5BA3-BBF0-101860D06D60
+ mutating func run() async {
+ do{
+ let accData = try await client.updateAccessory(name: accessory, home: home, room: room, serviceId: serviceId, characteristicId: characteristicId, value: value)
+ print(accData)
+ } catch UninitializeClientError.propertyIsNotSet(let property) {
+ print("Attempting to use client without setting \(property)")
+ } catch HTTPResponseError.notFound(let response), HTTPResponseError.tooManyRequests(let response), HTTPResponseError.forbidden(let response) {
+ print("\(response)")
+ } catch HTTPResponseError.unexpected(let response, let code) {
+ print("\(response), \(code)")
+ } catch {
+ print("An unknown error occured: \(error)")
+ }
+ }
+ }
+}
diff --git a/prefab-client/Root.swift b/prefab-client/Root.swift
index 5e94aac..729b4a8 100644
--- a/prefab-client/Root.swift
+++ b/prefab-client/Root.swift
@@ -19,7 +19,7 @@ struct Prefab: AsyncParsableCommand {
discussion: """
Prints to stdout forever, or until you halt the program.
""",
- subcommands: [GetHomes.self, GetHome.self, GetRooms.self, GetRoom.self, GetAccessory.self, GetAccessories.self])
+ subcommands: [GetHomes.self, GetHome.self, GetRooms.self, GetRoom.self, GetAccessory.self, GetAccessories.self, UpdateAccessory.self])
static let client: Client = Client.initShared(host: "localhost", port: "8080", scheme: "http")
diff --git a/prefab.xcodeproj/xcuserdata/kellyplummer.xcuserdatad/xcschemes/xcschememanagement.plist b/prefab.xcodeproj/xcuserdata/kellyplummer.xcuserdatad/xcschemes/xcschememanagement.plist
index c84084a..8b0080c 100644
--- a/prefab.xcodeproj/xcuserdata/kellyplummer.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/prefab.xcodeproj/xcuserdata/kellyplummer.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -7,7 +7,7 @@
Prefab.xcscheme_^#shared#^_
orderHint
- 1
+ 0
prefab-client.xcscheme_^#shared#^_
@@ -17,7 +17,7 @@
prefab.xcscheme_^#shared#^_
orderHint
- 0
+ 1
SuppressBuildableAutocreation
diff --git a/prefab/http/Data.swift b/prefab/http/Data.swift
index dea48e9..28d0892 100644
--- a/prefab/http/Data.swift
+++ b/prefab/http/Data.swift
@@ -7,7 +7,6 @@
import Foundation
-
struct Home: Encodable, Decodable {
var name: String
}
@@ -35,6 +34,7 @@ struct Accessory: Encodable, Decodable {
}
struct Service: Encodable, Decodable {
+ var uniqueIdentifier: UUID
var name: String
var type: String
var isPrimary: Bool
@@ -45,8 +45,40 @@ struct Service: Encodable, Decodable {
}
struct Characteristic: Encodable, Decodable {
+ var uniqueIdentifier: UUID
var description: String
var properties: [String]
var type: String
+ var metadata: CharacteristicMetadata?
var value: String?
}
+
+struct CharacteristicMetadata: Encodable, Decodable {
+ init(manufacturerDescription: String? = nil, validValues: [String]? = nil, minimumValue: String? = nil, maximumValue: String? = nil, stepValue: String? = nil, maxLength: String? = nil, format: String? = nil, units: String? = nil) {
+ self.manufacturerDescription = manufacturerDescription
+ self.validValues = validValues
+ self.minimumValue = minimumValue
+ self.maximumValue = maximumValue
+ self.stepValue = stepValue
+ self.maxLength = maxLength
+ self.format = format
+ self.units = units
+ }
+ var manufacturerDescription: String?
+ var validValues: [String]?
+ var minimumValue: (String)?
+ var maximumValue: (String)?
+ var stepValue: (String)?
+ var maxLength: (String)?
+ var format: String?
+ var units: String?
+}
+
+struct UpdateAccessoryInput: Encodable, Decodable {
+ var home: String
+ var room: String
+ var accessory: String
+ var serviceId: String
+ var characteristicId: String
+ var value: String
+}
diff --git a/prefab/http/Routes.swift b/prefab/http/Routes.swift
index e0720ca..c768bce 100644
--- a/prefab/http/Routes.swift
+++ b/prefab/http/Routes.swift
@@ -157,8 +157,8 @@ extension Server {
}
group.wait()
- let accessory: Accessory = Accessory(
- home: home!.name, room: room!.name, name: hkAccessory!.name, category: hkAccessory!.category.categoryType, isReachable: hkAccessory!.isReachable, supportsIdentify: hkAccessory!.supportsIdentify, isBridged: hkAccessory!.isBridged, services: hkAccessory!.services.map{ (service: HMService) -> Service in Service(name: service.name, type: service.serviceType, isPrimary: service.isPrimaryService, isUserInteractive: service.isUserInteractive, associatedType: service.associatedServiceType, characteristics: service.characteristics.map{ (char: HMCharacteristic) -> Characteristic in Characteristic(description: char.localizedDescription, properties: char.properties, type: char.characteristicType, value: "\(char.value ?? "")" ) }) }, firmwareVersion: hkAccessory!.firmwareVersion, manufacturer: hkAccessory!.manufacturer, model: hkAccessory!.model )
+ let accessory = Accessory(
+ home: home!.name, room: room!.name, name: hkAccessory!.name, category: hkAccessory!.category.categoryType, isReachable: hkAccessory!.isReachable, supportsIdentify: hkAccessory!.supportsIdentify, isBridged: hkAccessory!.isBridged, services: hkAccessory!.services.map{ (service: HMService) -> Service in Service(uniqueIdentifier: service.uniqueIdentifier, name: service.name, type: service.serviceType, isPrimary: service.isPrimaryService, isUserInteractive: service.isUserInteractive, associatedType: service.associatedServiceType, characteristics: service.characteristics.map{ (char: HMCharacteristic) -> Characteristic in Characteristic(uniqueIdentifier: char.uniqueIdentifier, description: char.localizedDescription, properties: char.properties, type: char.characteristicType, metadata: CharacteristicMetadata(manufacturerDescription: char.metadata?.manufacturerDescription, validValues: char.metadata?.validValues?.map{ (number: NSNumber) -> String in return number.stringValue}, minimumValue: char.metadata?.minimumValue?.stringValue, maximumValue: char.metadata?.maximumValue?.stringValue, stepValue: char.metadata?.stepValue?.stringValue, maxLength: char.metadata?.maxLength?.stringValue, format: char.metadata?.format, units: char.metadata?.units), value: "\(char.value ?? "")" )}) }, firmwareVersion: hkAccessory!.firmwareVersion, manufacturer: hkAccessory!.manufacturer, model: hkAccessory!.model )
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(accessory)
@@ -166,4 +166,83 @@ extension Server {
return json!
}
+
+
+
+ func updateAccessoryCharacteristic(_ request: HBRequest) throws -> String {
+ var updateAccessoryInput: UpdateAccessoryInput
+ do {
+ updateAccessoryInput = try JSONDecoder().decode(UpdateAccessoryInput.self, from: request.body.buffer!)
+ } catch {
+ throw HBHTTPError(
+ .badRequest,
+ message: "Invalid update object."
+ )
+ }
+
+// guard let homeName = request.parameters["home"] else {
+// throw HBHTTPError(
+// .badRequest,
+// message: "Invalid name parameter."
+// )
+// }
+// guard let roomName = request.parameters["room"] else {
+// throw HBHTTPError(
+// .badRequest,
+// message: "Invalid name parameter."
+// )
+// }
+// guard let accessoryName = request.parameters["accessory"] else {
+// throw HBHTTPError(
+// .badRequest,
+// message: "Invalid name parameter."
+// )
+// }
+ let home = homeBase.homes.first(where: {$0.name == updateAccessoryInput.home.removingPercentEncoding})
+ if (home == nil) {
+ throw HBHTTPError(.notFound)
+ }
+ let room = home?.rooms.first(where: {$0.name == updateAccessoryInput.room.removingPercentEncoding})
+ if (room == nil) {
+ throw HBHTTPError(.notFound)
+ }
+ let hkAccessory = room?.accessories.first(where: { (hmAccessory: HMAccessory) -> Bool in hmAccessory.name == updateAccessoryInput.accessory.removingPercentEncoding})
+ if (hkAccessory == nil) {
+ throw HBHTTPError(.notFound)
+ }
+
+ let hkService = hkAccessory?.services.first(where: { (hmService: HMService) -> Bool in hmService.uniqueIdentifier.uuidString == updateAccessoryInput.serviceId})
+ if (hkAccessory == nil) {
+ Logger().debug("Service not found \(updateAccessoryInput.serviceId)")
+ throw HBHTTPError(.notFound)
+ }
+
+ let hkChar = hkService?.characteristics.first(where: { (hmChar: HMCharacteristic) -> Bool in hmChar.uniqueIdentifier.uuidString == updateAccessoryInput.characteristicId})
+ if (hkAccessory == nil) {
+ Logger().debug("Characteristic not found \(updateAccessoryInput.characteristicId)")
+ throw HBHTTPError(.notFound)
+ }
+
+
+ Logger().debug("Writing \(updateAccessoryInput.value) to \(hkChar)")
+
+ var anyValue: Any
+ switch hkChar?.metadata?.format {
+ case "bool":
+ anyValue = updateAccessoryInput.value.boolValue
+ default:
+ anyValue = updateAccessoryInput.value
+ }
+
+ let group = DispatchGroup()
+ group.enter()
+ hkChar?.writeValue(anyValue, completionHandler: { (error: Error?) -> Void in defer {group.leave()}; Logger().error("\(String(describing: error))") })
+ group.wait()
+
+ return "" //json!
+ }
}
+extension String {
+var boolValue: Bool {
+ return (self as NSString).boolValue
+}}
diff --git a/prefab/http/Server.swift b/prefab/http/Server.swift
index fa69084..7411b4e 100644
--- a/prefab/http/Server.swift
+++ b/prefab/http/Server.swift
@@ -46,6 +46,7 @@ class Server {
app.router.get("accessories/:home/:room", use: self.getAccessories)
app.router.get("accessories/:home/:room/:accessory", use: self.getAccessory)
+ app.router.put("accessories", use: self.updateAccessoryCharacteristic)
try app.start()