Skip to content

Commit

Permalink
Writing bool to accessory
Browse files Browse the repository at this point in the history
  • Loading branch information
kellyp committed Feb 24, 2024
1 parent c4fe035 commit 6f8f3f5
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 7 deletions.
5 changes: 5 additions & 0 deletions prefab-client/Client/Client+Accessories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

23 changes: 23 additions & 0 deletions prefab-client/Client/Client.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
50 changes: 49 additions & 1 deletion prefab-client/Command/Prefab+Accessories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)")
}
}
}
}
2 changes: 1 addition & 1 deletion prefab-client/Root.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<key>Prefab.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>0</integer>
</dict>
<key>prefab-client.xcscheme_^#shared#^_</key>
<dict>
Expand All @@ -17,7 +17,7 @@
<key>prefab.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>1</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
Expand Down
34 changes: 33 additions & 1 deletion prefab/http/Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

import Foundation


struct Home: Encodable, Decodable {
var name: String
}
Expand Down Expand Up @@ -35,6 +34,7 @@ struct Accessory: Encodable, Decodable {
}

struct Service: Encodable, Decodable {
var uniqueIdentifier: UUID
var name: String
var type: String
var isPrimary: Bool
Expand All @@ -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
}
83 changes: 81 additions & 2 deletions prefab/http/Routes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,92 @@ 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)
let json = String(data: jsonData, encoding: String.Encoding.utf8)

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
}}
1 change: 1 addition & 0 deletions prefab/http/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 6f8f3f5

Please sign in to comment.