Created
April 12, 2021 10:54
-
-
Save drewandre/cc6fd92fe0ee2441badd739fd71e5534 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <React/RCTBridgeModule.h> | |
#import <HomeKit/HomeKit.h> | |
#import "React/RCTEventEmitter.h" | |
@interface RCT_EXTERN_MODULE(HomeKitModule, RCTEventEmitter) | |
RCT_EXTERN_METHOD(listHomes: (NSString *)name withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(addAndSetupAccessories: (NSString *)name withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(setCharacteristicValue:(NSString *)uuid value:(NSString *)value withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(addHome:(NSString *)name withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(removeHome:(NSString *)name withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(renameHome:(NSString *)newName oldName:(NSString *)oldName withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(addZone:(NSString *)name toHome:(NSString *)toHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(removeZone:(NSString *)name fromHome:(NSString *)fromHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(renameZone:(NSString *)oldName oldName:(NSString *)oldName inHome:(NSString *)inHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(addRoomToHome:(NSString *)name toHome:(NSString *)toHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(addRoomToZone:(NSString *)name toZone:(NSString *)toZone inHome:(NSString *)inHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(removeRoomFromZone:(NSString *)name fromZone:(NSString *)fromZone inHome:(NSString *)inHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(removeRoomFromHome:(NSString *)name fromHome:(NSString *)fromHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(renameRoom:(NSString *)oldName inHome:(NSString *)inHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(addAccessoryToHome:(NSString *)accessoryName toHome:(NSString *)toHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(removeAccessoryFromHome:(NSString *)accessoryName fromHome:(NSString *)fromHome withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(assignAccessoryToRoom:(NSString *)accessoryName roomName:(NSString *)roomName homeName:(NSString *)homeName withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(renameAccessory:(NSString *)oldName newName:(NSString *)newName withResolver:(RCTPromiseResolveBlock)resolve withRejecter:(RCTPromiseRejectBlock)reject) | |
RCT_EXTERN_METHOD(startSearchingForNewAccessories) | |
RCT_EXTERN_METHOD(stopSearchingForNewAccessories) | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import HomeKit | |
@objc(HomeKitModule) | |
class HomeKitModule: RCTEventEmitter, HMHomeManagerDelegate, HMAccessoryBrowserDelegate, HMAccessoryDelegate { | |
var homeManager = HMHomeManager() | |
let accessoryBrowser = HMAccessoryBrowser() | |
var discoveredAccessories: [HMAccessory] = [] | |
override init() { | |
self.homeManager = HMHomeManager() | |
super.init() | |
self.accessoryBrowser.delegate = self | |
self.homeManager.delegate = self | |
} | |
private func findAccessory(name: String) -> HMAccessory? { | |
for accessory in accessoryBrowser.discoveredAccessories { | |
if accessory.name == name { | |
return accessory | |
} | |
} | |
return nil | |
} | |
@objc override static func requiresMainQueueSetup() -> Bool { | |
return true | |
} | |
override func supportedEvents() -> [String]! { | |
return ["findNewAccessory", "removeNewAccessory", "characteristicNotify"] | |
} | |
@objc(listHomes:withResolver:withRejecter:) | |
func listHomes(name: String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
self.subscribeToNotifiableCharacteristics() | |
resolve(HomeKitModule.transformHomes(nhomes: homeManager.homes)) | |
} | |
func subscribeToNotifiableCharacteristics() -> Void { | |
NSLog("Subscribing to all available characteristic notifications") | |
NSLog("Number of homes: \(homeManager.homes.count)") | |
for home in homeManager.homes { | |
NSLog("Found home: \(home.name)") | |
for accessory in home.accessories { | |
NSLog(" Found accessory: \(accessory.name)") | |
accessory.delegate = self | |
for service in accessory.services { | |
NSLog(" Found service: \(service.name)") | |
for characteristic in service.characteristics { | |
if characteristic.isNotificationEnabled { | |
NSLog(" Found subscribable characteristic: \(characteristic.metadata?.manufacturerDescription ?? characteristic.localizedDescription)") | |
characteristic.enableNotification(true, completionHandler: { error in | |
let name = characteristic.metadata?.manufacturerDescription ?? characteristic.localizedDescription | |
if let error = error { | |
NSLog(" Error subscribing to \(name) characteristic notifications: \(error.localizedDescription)"); | |
} else { | |
NSLog(" Subscribed to \(name)"); | |
} | |
}) | |
} else { | |
NSLog(" Found characteristic: \(characteristic.metadata?.manufacturerDescription ?? characteristic.localizedDescription)") | |
} | |
} | |
} | |
} | |
} | |
} | |
@objc(addAndSetupAccessories:withResolver:withRejecter:) | |
func addAndSetupAccessories(name: String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
homeManager.homes[0].addAndSetupAccessories(completionHandler: { error in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve("") | |
} | |
}) | |
} | |
@objc(setCharacteristicValue:value:withResolver:withRejecter:) | |
func setCharacteristicValue(uuid: String, value: String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
NSLog("Writing \(value) to \(uuid)") | |
for home in homeManager.homes { | |
for accessory in home.accessories { | |
for service in accessory.services { | |
for characteristic in service.characteristics { | |
if (characteristic.uniqueIdentifier.uuidString == uuid) { | |
let numberFormatter = NumberFormatter() | |
let number = numberFormatter.number(from: value) | |
var formattedNumber: Any = value | |
switch characteristic.metadata?.format { | |
case "float": | |
formattedNumber = number?.floatValue ?? 0.0 | |
case "int": | |
formattedNumber = number?.intValue ?? 0 | |
case "bool": | |
formattedNumber = number?.boolValue ?? false | |
default: | |
NSLog("Unknown type: \(characteristic.metadata?.format ?? "unknown")") | |
formattedNumber = value | |
} | |
characteristic.writeValue(formattedNumber, completionHandler: { (error) -> Void in | |
if error == nil { | |
NSLog("Wrote \(value) to \(uuid)") | |
resolve("Wrote \(value) to \(uuid)") | |
} else { | |
NSLog("Error writing \(formattedNumber) (of type \(type(of: formattedNumber)) to \(uuid) (\(error.debugDescription)") | |
reject("error", "Error writing \(formattedNumber) (of type \(type(of: formattedNumber)) to \(uuid) (\(error.debugDescription)", error); | |
} | |
}) | |
} | |
} | |
} | |
} | |
} | |
} | |
@objc(addHome:withResolver:withRejecter:) | |
func addHome(name: String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
homeManager.addHome(withName: name) { newHome, error in | |
if error != nil { | |
reject("error", error?.localizedDescription, error); | |
} else { | |
resolve(HomeKitModule.transformHome(home: newHome!)) | |
} | |
} | |
} | |
@objc(removeHome:withResolver:withRejecter:) | |
func removeHome(name: String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
guard let home = self.findHome(name: name) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
homeManager.removeHome(home) { error in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(HomeKitModule.transformHome(home: home)) | |
} | |
} | |
} | |
@objc(renameHome:oldName:withResolver:withRejecter:) | |
func renameHome(newName: String, oldName: String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
guard let home = self.findHome(name: oldName) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
home.updateName(newName) { (error) in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(HomeKitModule.transformHome(home: home)) | |
} | |
} | |
} | |
@objc(addZone:toHome:withResolver:withRejecter:) | |
func addZone(name: String, toHome:String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
guard let home = self.findHome(name: toHome) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
home.addZone(withName: name) { zone, error in | |
if zone != nil { | |
resolve(name) | |
} | |
if let error = error { | |
reject("error",error.localizedDescription, nil); | |
} | |
} | |
} | |
@objc(removeZone:fromHome:withResolver:withRejecter:) | |
func removeZone(name: String, fromHome:String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
guard let home = self.findHome(name: fromHome) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
guard let zone = self.findZone(name: name, home: home) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
home.removeZone(zone) { (error) in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(name) | |
} | |
} | |
} | |
@objc(renameZone:oldName:inHome:withResolver:withRejecter:) | |
func renameZone(name: String, oldName:String, inHome:String, resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void { | |
guard let home = self.findHome(name: oldName) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
guard let zone = self.findZone(name: oldName, home: home) else { | |
reject("error","Zone cannot found", nil); | |
return | |
} | |
zone.updateName(name) { (error) in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(name) | |
} | |
} | |
} | |
@objc(addAccessoryToHome:toHome:withResolver:withRejecter:) | |
func addAccessoryToHome(accessoryName: String, toHome: String ,resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void{ | |
guard let home = self.findHome(name: toHome) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
guard let accessory = self.findAccessory(name: accessoryName) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
home.addAccessory(accessory) { (error) in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(accessory) | |
} | |
} | |
} | |
@objc(removeAccessoryFromHome:fromHome:withResolver:withRejecter:) | |
func removeAccessoryFromHome(accessoryName: String, fromHome: String ,resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void{ | |
guard let home = self.findHome(name: fromHome) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
guard let accessory = self.findAccessoryInHome(name: accessoryName, inHome: home) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
home.removeAccessory(accessory) { (error) in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(accessory) | |
} | |
} | |
} | |
@objc(assignAccessoryToRoom:roomName:homeName:withResolver:withRejecter:) | |
func assignAccessoryToRoom(accessoryName: String, roomName: String , homeName: String , resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void{ | |
guard let home = self.findHome(name: homeName) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
guard let accessory = self.findAccessoryInHome(name: accessoryName, inHome: home) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
guard let room = self.findRoom(name: accessoryName, inHome: home) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
home.assignAccessory(accessory , to: room) { (error) in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(accessory) | |
} | |
} | |
} | |
@objc(renameAccessory:newName:withResolver:withRejecter:) | |
func renameAccessory(oldName: String, newName: String ,resolve: @escaping(RCTPromiseResolveBlock), reject: @escaping(RCTPromiseRejectBlock)) -> Void{ | |
guard let accessory = self.findAccessory(name: oldName) else { | |
reject("error","Home cannot found", nil); | |
return | |
} | |
accessory.updateName(newName) { (error) in | |
if let error = error { | |
reject("error", error.localizedDescription, error); | |
} else { | |
resolve(accessory) | |
} | |
} | |
} | |
@objc(startSearchingForNewAccessories) | |
func startSearchingForNewAccessories () -> Void { | |
NSLog("Searching for new accessories!") | |
self.accessoryBrowser.startSearchingForNewAccessories() | |
} | |
@objc(stopSearchingForNewAccessories) | |
func stopSearchingForNewAccessories () -> Void { | |
self.accessoryBrowser.stopSearchingForNewAccessories() | |
} | |
//Helper functions | |
private func findHome(name: String) -> HMHome? { | |
for home in homeManager.homes { | |
if home.name == name { | |
return home | |
} | |
} | |
return nil | |
} | |
private func findRoom(name: String, inHome: HMHome) -> HMRoom? { | |
for room in inHome.rooms { | |
if room.name == name { | |
return room | |
} | |
} | |
return nil | |
} | |
private func findAccessoryInHome(name: String, inHome: HMHome) -> HMAccessory? { | |
for accessory in inHome.accessories { | |
if accessory.name == name { | |
return accessory | |
} | |
} | |
return nil | |
} | |
private func findZone(name: String, home:HMHome) -> HMZone? { | |
for zone in home.zones { | |
if zone.name == name { | |
return zone | |
} | |
} | |
return nil | |
} | |
private static func transformHome(home: HMHome) -> [String : Any?] { | |
return [ | |
"uuid": home.uniqueIdentifier.uuidString, | |
"name" : home.name, | |
"isPrimary": home.isPrimary, | |
"rooms": HomeKitModule.transformRooms(nrooms: home.rooms), | |
"accessories": transformAccessories(naccessories: home.accessories), | |
"zones": transformZones(nzones: home.zones), | |
]; | |
} | |
private static func transformRooms(nrooms: [HMRoom]) -> [Any] { | |
var rooms: [Any] = [] | |
for room in nrooms { | |
rooms.append(transformRoom(room: room, skipAccessories:true)) | |
} | |
return rooms; | |
} | |
private static func transformRoom(room: HMRoom, skipAccessories: Bool?) -> [String : Any?] { | |
return [ | |
"uuid": room.uniqueIdentifier.uuidString, | |
"name" : room.name, | |
"accessories": skipAccessories! ? nil : transformAccessories(naccessories: room.accessories) | |
]; | |
} | |
private static func transformAccessories(naccessories: [HMAccessory]) -> [Any] { | |
var accessories: [Any] = [] | |
for accessory in naccessories { | |
accessories.append(transformAccessory(acc: accessory)) | |
} | |
return accessories; | |
} | |
private static func transformAccessory(acc: HMAccessory) -> [String : Any?] { | |
return [ | |
"name": acc.name, | |
"uuid": acc.uniqueIdentifier.uuidString, | |
"bridged": acc.isBridged, | |
"services": transformServices(nservices: acc.services), | |
] | |
} | |
private static func transformZones(nzones: [HMZone]) -> [Any] { | |
var zones: [Any] = [] | |
for zone in nzones { | |
zones.append(transformZone(zone: zone)) | |
} | |
return zones; | |
} | |
private static func transformZone(zone: HMZone) -> [String : Any] { | |
return [ | |
"uuid": zone.uniqueIdentifier.uuidString, | |
"name": zone.name, | |
"rooms": transformRooms(nrooms: zone.rooms), | |
] | |
} | |
private static func transformServices(nservices: [HMService]) -> [Any] { | |
var services: [Any] = [] | |
for service in nservices { | |
services.append(transformService(service: service)) | |
} | |
return services; | |
} | |
private static func transformService(service: HMService) -> [String : Any] { | |
return [ | |
"uuid": service.uniqueIdentifier.uuidString, | |
"name": service.name, | |
"localizedDescription": service.localizedDescription, | |
"type": service.serviceType, | |
"characteristics": transformCharacteristics(ncharacteristics: service.characteristics) | |
] | |
} | |
private static func transformHomes(nhomes: [HMHome]) -> [Any] { | |
var homes = [Any]() | |
for home in nhomes { | |
homes.append(HomeKitModule.transformHome(home: home)) | |
} | |
return homes; | |
} | |
private static func transformCharacteristics(ncharacteristics: [HMCharacteristic]) -> [Any] { | |
var characteristics: [Any] = [] | |
for characteristic in ncharacteristics { | |
characteristics.append(transformCharacteristic(ncharacteristic:characteristic)) | |
} | |
return characteristics; | |
} | |
private static func transformCharacteristic(ncharacteristic: HMCharacteristic) -> [String : Any] { | |
let emptyMetadata = [String: String]() | |
var characteristic = [ | |
"type": ncharacteristic.characteristicType, | |
"description": ncharacteristic.localizedDescription, | |
"value": ncharacteristic.value ?? "0", | |
"localizedDescription": ncharacteristic.localizedDescription, | |
"properties": ncharacteristic.properties, | |
"isNotificationEnabled": ncharacteristic.isNotificationEnabled, | |
"uuid": ncharacteristic.uniqueIdentifier.uuidString, | |
"metadata": emptyMetadata, | |
] | |
if let metadata = ncharacteristic.metadata { | |
var formattedMetadata = [String: Any]() | |
formattedMetadata["stepValue"] = metadata.stepValue | |
formattedMetadata["maxLength"] = metadata.maxLength | |
formattedMetadata["format"] = metadata.format | |
formattedMetadata["units"] = metadata.units | |
formattedMetadata["manufacturerDescription"] = metadata.manufacturerDescription | |
formattedMetadata["minimumValue"] = metadata.minimumValue | |
formattedMetadata["maximumValue"] = metadata.maximumValue | |
characteristic["metadata"] = formattedMetadata; | |
} | |
return characteristic | |
} | |
// Delegate | |
// func accessoryBrowser(_ browser: HMAccessoryBrowser, didFindNewAccessory accessory: HMAccessory) { | |
// self.sendEvent(withName: "findNewAccessory", body:["accessory": HomeKitModule.transformAccessory(acc: accessory)]) | |
// | |
// } | |
// func accessoryBrowser(_ browser: HMAccessoryBrowser, didRemoveNewAccessory accessory: HMAccessory) { | |
// self.sendEvent(withName: "removeNewAccessory", body: ["accessory": HomeKitModule.transformAccessory(acc: accessory)] ) | |
// } | |
// | |
func accessory(_ accessory: HMAccessory, service: HMService, didUpdateValueFor characteristic: HMCharacteristic) { | |
self.sendEvent(withName: "characteristicNotify", body: [ | |
"characteristic": HomeKitModule.transformCharacteristic(ncharacteristic: characteristic), | |
"homes": HomeKitModule.transformHomes(nhomes: homeManager.homes) | |
]) | |
NSLog("\(accessory.name) \(characteristic.metadata?.manufacturerDescription ?? characteristic.localizedDescription) value updated to \(characteristic.value ?? "unknown")") | |
} | |
func accessoryDidUpdateName(_ accessory: HMAccessory) { | |
NSLog("accessoryDidUpdateName \(accessory.name)") | |
} | |
func accessoryDidUpdateServices(_ accessory: HMAccessory) { | |
NSLog("accessoryDidUpdateServices \(accessory.name)") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment