From e0ec9d1d1ca3421c11fb7b37ef9435ac23890a71 Mon Sep 17 00:00:00 2001 From: kellyp Date: Thu, 29 Feb 2024 09:15:09 -0800 Subject: [PATCH] Refactor to resource specific routes --- .gitignore | 1 + prefab.xcodeproj/project.pbxproj | 12 ++ prefab/http/Routes+Accessories.swift | 125 +++++++++++++++ prefab/http/Routes+Homes.swift | 32 ++++ prefab/http/Routes+Rooms.swift | 43 ++++++ prefab/http/Routes.swift | 223 +-------------------------- prefab/http/Server.swift | 10 ++ prefab/model/HomeBase.swift | 4 + 8 files changed, 229 insertions(+), 221 deletions(-) create mode 100644 prefab/http/Routes+Accessories.swift create mode 100644 prefab/http/Routes+Homes.swift create mode 100644 prefab/http/Routes+Rooms.swift diff --git a/.gitignore b/.gitignore index 3169ce9..acab3a6 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ DerivedData/ build/ +default.profraw ##### # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) diff --git a/prefab.xcodeproj/project.pbxproj b/prefab.xcodeproj/project.pbxproj index aa940d2..84fd59f 100644 --- a/prefab.xcodeproj/project.pbxproj +++ b/prefab.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + A28EE47D2B8C29010042FAA0 /* Routes+Homes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28EE47C2B8C29010042FAA0 /* Routes+Homes.swift */; }; + A28EE47F2B8C29610042FAA0 /* Routes+Rooms.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28EE47E2B8C29610042FAA0 /* Routes+Rooms.swift */; }; + A28EE4812B8C29AE0042FAA0 /* Routes+Accessories.swift in Sources */ = {isa = PBXBuildFile; fileRef = A28EE4802B8C29AE0042FAA0 /* Routes+Accessories.swift */; }; CB78182B2B7D802B0077671A /* prefabApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB78182A2B7D802B0077671A /* prefabApp.swift */; }; CB78182D2B7D802B0077671A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB78182C2B7D802B0077671A /* ContentView.swift */; }; CB78182F2B7D802B0077671A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CB78182E2B7D802B0077671A /* Assets.xcassets */; }; @@ -90,6 +93,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + A28EE47C2B8C29010042FAA0 /* Routes+Homes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Homes.swift"; sourceTree = ""; }; + A28EE47E2B8C29610042FAA0 /* Routes+Rooms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Rooms.swift"; sourceTree = ""; }; + A28EE4802B8C29AE0042FAA0 /* Routes+Accessories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Accessories.swift"; sourceTree = ""; }; CB7818272B7D802B0077671A /* Prefab.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Prefab.app; sourceTree = BUILT_PRODUCTS_DIR; }; CB78182A2B7D802B0077671A /* prefabApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = prefabApp.swift; sourceTree = ""; }; CB78182C2B7D802B0077671A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -219,6 +225,9 @@ CB9C51872B7D8493007C1AD4 /* Data.swift */, CB9C51882B7D8493007C1AD4 /* Server.swift */, CB9C51892B7D8493007C1AD4 /* Routes.swift */, + A28EE4802B8C29AE0042FAA0 /* Routes+Accessories.swift */, + A28EE47C2B8C29010042FAA0 /* Routes+Homes.swift */, + A28EE47E2B8C29610042FAA0 /* Routes+Rooms.swift */, ); path = http; sourceTree = ""; @@ -431,7 +440,10 @@ files = ( CB78182D2B7D802B0077671A /* ContentView.swift in Sources */, CB9C518A2B7D8493007C1AD4 /* Data.swift in Sources */, + A28EE47F2B8C29610042FAA0 /* Routes+Rooms.swift in Sources */, + A28EE4812B8C29AE0042FAA0 /* Routes+Accessories.swift in Sources */, CB9C518C2B7D8493007C1AD4 /* Routes.swift in Sources */, + A28EE47D2B8C29010042FAA0 /* Routes+Homes.swift in Sources */, CB9C518B2B7D8493007C1AD4 /* Server.swift in Sources */, CB9C518F2B7D849D007C1AD4 /* HomeBase.swift in Sources */, CB78182B2B7D802B0077671A /* prefabApp.swift in Sources */, diff --git a/prefab/http/Routes+Accessories.swift b/prefab/http/Routes+Accessories.swift new file mode 100644 index 0000000..01308ac --- /dev/null +++ b/prefab/http/Routes+Accessories.swift @@ -0,0 +1,125 @@ +// +// Routes+Accessories.swift +// Prefab +// +// Created by kelly on 2/25/24. +// + +import Foundation +import HomeKit +import Hummingbird +import OSLog + +extension Server { + func getAccessories(_ request: HBRequest) throws -> String { + let homeName = try getRequiredParam(param: "home", request: request) + let roomName = try getRequiredParam(param: "room", request: request) + let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) + if (home == nil) { + throw HBHTTPError(.notFound) + } + let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) + if (room == nil) { + throw HBHTTPError(.notFound) + } + + let accessories = room?.accessories.map{ (hmAccessory: HMAccessory) -> Accessory in Accessory(home: home!.name, room: room!.name, name: hmAccessory.name)} + let jsonEncoder = JSONEncoder() + let jsonData = try jsonEncoder.encode(accessories) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + + return json! + } + + + func getAccessory(_ request: HBRequest) throws -> String { + let homeName = try getRequiredParam(param: "home", request: request) + let roomName = try getRequiredParam(param: "room", request: request) + let accessoryName = try getRequiredParam(param: "accessory", request: request) + + let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) + if (home == nil) { + throw HBHTTPError(.notFound) + } + let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) + if (room == nil) { + throw HBHTTPError(.notFound) + } + let hkAccessory = room?.accessories.first(where: { (hmAccessory: HMAccessory) -> Bool in hmAccessory.name == accessoryName.removingPercentEncoding}) + if (hkAccessory == nil) { + throw HBHTTPError(.notFound) + } + + let group = DispatchGroup() + for service in hkAccessory!.services { + for char in service.characteristics { + group.enter() + // Error handling for read + char.readValue{ (error: Error?) -> Void in group.leave() } + } + } + group.wait() + + 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 updateAccessory(_ 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." + ) + } + + let homeName = try getRequiredParam(param: "home", request: request) + let roomName = try getRequiredParam(param: "room", request: request) + let accessoryName = try getRequiredParam(param: "accessory", request: request) + + let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) + if (home == nil) { + throw HBHTTPError(.notFound) + } + let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) + if (room == nil) { + throw HBHTTPError(.notFound) + } + let hkAccessory = room?.accessories.first(where: { (hmAccessory: HMAccessory) -> Bool in hmAccessory.name == accessoryName.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)") + + let group = DispatchGroup() + group.enter() + hkChar?.writeValue(try GetValue(value: updateAccessoryInput.value, format: hkChar?.metadata?.format ?? ""), completionHandler: { (error: Error?) -> Void in defer {group.leave()}; Logger().error("\(String(describing: error))") }) + group.wait() + + return "" //json! + } +} diff --git a/prefab/http/Routes+Homes.swift b/prefab/http/Routes+Homes.swift new file mode 100644 index 0000000..80dd36a --- /dev/null +++ b/prefab/http/Routes+Homes.swift @@ -0,0 +1,32 @@ +// +// Routes+Homes.swift +// Prefab +// +// Created by kelly on 2/25/24. +// + +import Foundation +import Hummingbird + +extension Server { + func getHomes(_ request: HBRequest) throws -> String { + let jsonEncoder = JSONEncoder() + let jsonData = try jsonEncoder.encode(homeBase.homes.map{Home(name: $0.name)}) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + + return json ?? "[]" + } + + func getHome(_ request: HBRequest) throws -> String { + let homeName = try getRequiredParam(param: "home", request: request) + let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) + if (home == nil) { + throw HBHTTPError(.notFound) + } + let jsonEncoder = JSONEncoder() + let jsonData = try jsonEncoder.encode(Home(name: home!.name)) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + + return json! + } +} diff --git a/prefab/http/Routes+Rooms.swift b/prefab/http/Routes+Rooms.swift new file mode 100644 index 0000000..5c4d983 --- /dev/null +++ b/prefab/http/Routes+Rooms.swift @@ -0,0 +1,43 @@ +// +// Routes+Rooms.swift +// Prefab +// +// Created by kelly on 2/25/24. +// + +import Foundation +import Hummingbird + +extension Server { + func getRooms(_ request: HBRequest) throws -> String { + let homeName = try getRequiredParam(param: "home", request: request) + let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) + if (home == nil) { + throw HBHTTPError(.notFound) + } + let rooms = home?.rooms.map{Room(home: home!.name, name: $0.name)} + let jsonEncoder = JSONEncoder() + let jsonData = try jsonEncoder.encode(rooms) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + + return json! + } + + func getRoom(_ request: HBRequest) throws -> String { + let homeName = try getRequiredParam(param: "home", request: request) + let roomName = try getRequiredParam(param: "room", request: request) + let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) + if (home == nil) { + throw HBHTTPError(.notFound) + } + let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) + if (room == nil) { + throw HBHTTPError(.notFound) + } + let jsonEncoder = JSONEncoder() + let jsonData = try jsonEncoder.encode(Room(home: home!.name, name: room!.name)) + let json = String(data: jsonData, encoding: String.Encoding.utf8) + + return json! + } +} diff --git a/prefab/http/Routes.swift b/prefab/http/Routes.swift index a8a47d4..42bd4ca 100644 --- a/prefab/http/Routes.swift +++ b/prefab/http/Routes.swift @@ -11,226 +11,7 @@ import Hummingbird import OSLog extension Server { - func getHomes(_ request: HBRequest) throws -> String { - let jsonEncoder = JSONEncoder() - let jsonData = try jsonEncoder.encode(homeBase.homes.map{Home(name: $0.name)}) - let json = String(data: jsonData, encoding: String.Encoding.utf8) - - return json ?? "[]" - } - - func getHome(_ request: HBRequest) throws -> String { - guard let homeName = request.parameters["home"] else { - throw HBHTTPError( - .badRequest, - message: "Invalid name parameter." - ) - } - let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) - if (home == nil) { - throw HBHTTPError(.notFound) - } - let jsonEncoder = JSONEncoder() - let jsonData = try jsonEncoder.encode(Home(name: home!.name)) - let json = String(data: jsonData, encoding: String.Encoding.utf8) - - return json! - } - - func getRooms(_ request: HBRequest) throws -> String { - guard let homeName = request.parameters["home"] else { - throw HBHTTPError( - .badRequest, - message: "Invalid name parameter." - ) - } - let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) - if (home == nil) { - throw HBHTTPError(.notFound) - } - let rooms = home?.rooms.map{Room(home: home!.name, name: $0.name)} - let jsonEncoder = JSONEncoder() - let jsonData = try jsonEncoder.encode(rooms) - let json = String(data: jsonData, encoding: String.Encoding.utf8) - - return json! - } - - func getRoom(_ request: HBRequest) throws -> String { - 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." - ) - } - let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) - if (home == nil) { - throw HBHTTPError(.notFound) - } - let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) - if (room == nil) { - throw HBHTTPError(.notFound) - } - let jsonEncoder = JSONEncoder() - let jsonData = try jsonEncoder.encode(Room(home: home!.name, name: room!.name)) - let json = String(data: jsonData, encoding: String.Encoding.utf8) - - return json! - } - - func getAccessories(_ request: HBRequest) throws -> String { - 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." - ) - } - let home = homeBase.homes.first(where: {$0.name == homeName.removingPercentEncoding}) - if (home == nil) { - throw HBHTTPError(.notFound) - } - let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) - if (room == nil) { - throw HBHTTPError(.notFound) - } - - let accessories = room?.accessories.map{ (hmAccessory: HMAccessory) -> Accessory in Accessory(home: home!.name, room: room!.name, name: hmAccessory.name)} - let jsonEncoder = JSONEncoder() - let jsonData = try jsonEncoder.encode(accessories) - let json = String(data: jsonData, encoding: String.Encoding.utf8) - - return json! - } - - - func getAccessory(_ request: HBRequest) throws -> String { - 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 == homeName.removingPercentEncoding}) - if (home == nil) { - throw HBHTTPError(.notFound) - } - let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) - if (room == nil) { - throw HBHTTPError(.notFound) - } - let hkAccessory = room?.accessories.first(where: { (hmAccessory: HMAccessory) -> Bool in hmAccessory.name == accessoryName.removingPercentEncoding}) - if (hkAccessory == nil) { - throw HBHTTPError(.notFound) - } - - let group = DispatchGroup() - for service in hkAccessory!.services { - for char in service.characteristics { - group.enter() - // Error handling for read - char.readValue{ (error: Error?) -> Void in group.leave() } - } - } - group.wait() - - 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 updateAccessory(_ 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 == homeName.removingPercentEncoding}) - if (home == nil) { - throw HBHTTPError(.notFound) - } - let room = home?.rooms.first(where: {$0.name == roomName.removingPercentEncoding}) - if (room == nil) { - throw HBHTTPError(.notFound) - } - let hkAccessory = room?.accessories.first(where: { (hmAccessory: HMAccessory) -> Bool in hmAccessory.name == accessoryName.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)") - - let group = DispatchGroup() - group.enter() - hkChar?.writeValue(try GetValue(value: updateAccessoryInput.value, format: hkChar?.metadata?.format ?? ""), completionHandler: { (error: Error?) -> Void in defer {group.leave()}; Logger().error("\(String(describing: error))") }) - group.wait() - - return "" //json! + func getRoot(_ request: HBRequest) throws -> String { + return "" } } diff --git a/prefab/http/Server.swift b/prefab/http/Server.swift index 054db9b..b74c347 100644 --- a/prefab/http/Server.swift +++ b/prefab/http/Server.swift @@ -31,6 +31,16 @@ class Server { serverTread.start() } + func getRequiredParam(param: String, request: HBRequest) throws -> String { + guard let value = request.parameters[param] else { + throw HBHTTPError( + .badRequest, + message: "Invalid \(param) parameter." + ) + } + return value + } + @objc func startServer(homeStore: HomeBase) { Task{ diff --git a/prefab/model/HomeBase.swift b/prefab/model/HomeBase.swift index e484576..ceaf060 100644 --- a/prefab/model/HomeBase.swift +++ b/prefab/model/HomeBase.swift @@ -32,4 +32,8 @@ class HomeBase: NSObject, ObservableObject, HMHomeManagerDelegate { Logger().log("Homes: \(manager.homes)") homes = manager.homes } + + func getHomes() { + + } }