5 Replies
      Latest reply on Dec 7, 2019 1:03 AM by Claude31
      Captain Joe Level 1 Level 1 (0 points)

        Hello Coders,

         

        I'm new to Swift, iOS, HomeKit and Delegates. I'm trying to get a HomeKit Accessory to notify my function when a value changes. I'm testing with Brightness of a lamp for now. In other parts of my app I can read and write brightness.

         

        I've created two different functions to receive the brightness change. I realize that I will only need one, once I learn more. I'm able to enable Notifications for this one device.

         

        Any suggestions would be helpful, including general coding style!

         

        Thanks Steve

        import Foundation
        import HomeKit
        
        //let hkHomeManager = HMHomeManager()
        let hkHomeManager = HomeKitManagerClass()
        let hkAccessoryManager = HomeKitAccessoryClass()
        let myCharTypes = readCharacteristicsCSV()
        
        protocol HomeKitAccessoryDelegate {
          func didUpdateValueFor (_ accessory: HMAccessory, service: HMService, didUpdateValueFor characteristic: HMCharacteristic)
          func didReceiveBrightness(value: Int)
        }
        
        class HomeKitAccessoryClass: NSObject, HMAccessoryDelegate {
          var accDelegate:HomeKitAccessoryDelegate?
          
          func didUpdateValueFor (_ accessory: HMAccessory!, service: HMService!, didUpdateValueFor characteristic: HMCharacteristic) {
            var jsmAcc: HMAccessory = HMAccessory()
            var jsmSer: HMService = HMService()
            var jsmChar: HMCharacteristic = HMCharacteristic()
            if let accDelegate = self.accDelegate {
              accDelegate.didUpdateValueFor(jsmAcc, service: jsmSer, didUpdateValueFor: jsmChar)
              print ("Did Update Value For...")
              print ("Did Update Value For...")
            }
          }
          
          func brightnessUpdated () {
            var brightnessReceived: Int = Int()
            if let accDelegate = self.accDelegate {
              accDelegate.didReceiveBrightness(value: brightnessReceived)
              print ("Did Update Value For...")
              print ("Did Update Value For...\(brightnessReceived)")
            }
          }
          
          func hkNotifications(idIn: String) {
            if appSettings.key.homeKitAllowed == false { return }
            let timeStart = CFAbsoluteTimeGetCurrent()
            if let index = getDeviceIndexById(idIn) {
              if (globalDevices.deviceList[index].displayName).contains("Couch") == false { return }  // Test ONLY LR Couch Light
              print("hkNotificaitons Start \(globalDevices.deviceList[index].displayName)")
              let oneDevice = globalDevices.deviceList[index].hkDevice  // Device = Accessory from HomeKit
              if oneDevice == nil  { return }   //  no HomeKit Accessory Found
              if oneDevice!.isReachable == false && oneDevice!.isBlocked == true { return }  // Don't try to access this device}
              
              for indexServices in 0..<oneDevice!.services.count {
                let hkCharacteristics = oneDevice!.services[indexServices].characteristics
                for indexCharacteristics in 0..<hkCharacteristics.count {
                  let hkChar = hkCharacteristics[indexCharacteristics]
                  if hkChar.isNotificationEnabled {
                    print("hkNotificaitons are ALREADY enabled for: \(globalDevices.deviceList[index].displayName)")
                  } else {
                    if hkChar.characteristicType == "00000008-0000-1000-8000-0026BB765291" { // Test ONLY Brightness for now
                      hkChar.enableNotification(true, completionHandler: { error in
                        if error != nil {
                          print("\(hkChar), Error in Notification Sets \(error!.localizedDescription)")
                        } else {
                          print("hkNotificaitons are set to TRUE for: \(globalDevices.deviceList[index].displayName)")
                        }
                      }
                      )
                    }
                  }
                }
              }
            }
          }
        }
        
        
        • Re: Help with Delegate & HomeKit
          Claude31 Level 8 Level 8 (7,245 points)

          I don't understand.

           

          Line 22 you call didUpdateValueFor inside didUpdateValueFor.

          What do you want to achieve ?

           

          Where do you set accDelegate ? Is it in the not shown HomeKitManagerClass ?

           

          May be I'm wrong, but not sure you fully understand how delegation works. Am I wrong ?

          If so, this old thread may help explain (just read the beginning of this long thread, the end is really specific to the post):

          https://forums.developer.apple.com/thread/111569

            • Re: Help with Delegate & HomeKit
              Captain Joe Level 1 Level 1 (0 points)

              Claude,

              You are not wrong.  I don't understand delegation.  I will have to look at it bit to explain line 22.  I 'copied' the concept from something else that appears to work.

               

              But first, I will review the link you suggested.

               

              Thanks for getting this started!

               

              Steve.

                • Re: Help with Delegate & HomeKit
                  Claude31 Level 8 Level 8 (7,245 points)

                  That's normal. I found delegation (even though it is simple) pretty hard to grasp at the beginning.

                  The key is in the name delegation:

                  an object (of a ClassB) will delegate to another one (of ClassA) the task to do something.

                  For this, ClassA will conform to a protocol (MyProtocol) and implement the service (the func 'service' declared in protocol).

                  ClassB will define a delegate var (a var of type ClassA)

                       : var myDelegate : ClassA?

                  At some point, this delegate must be instanciated in ClassB: that can be done when preparing for a segue for instance from A -> B, or when pushing a VC… or when you create an object of type classB inside classA

                  in this last case, you will have in classA

                  let objectB = ClassB()

                  objectB.myDelegate = self // I tell that the object of ClassA (self) will be myDelegate

                   

                  And then, ClassB may ask:

                  "hey, my myDelegate, do the service for me":  I call

                       myDelegate?.service()

                  This will be executed as defined in ClassA.

                   

                  Note: you have seen thos often with TableViews which define their delegate (and dataSource) when created in a ViewController):

                       table.delegate = self

                   

                  -----------------------------------

                   

                  You've asked for some advices on coding style.

                   

                  I looked at one func:

                  - it is good to add some (not too many) blank lines to separte blockks of code : after a forced return, or blocks that have a common topic… or just the beginning of func

                   

                  - no need to write == true, just test directly

                       if something == true {

                  is equivalent to

                       if something {

                  As soon as the name is explicit to define a logicial value, it is clearer

                   

                  - in the same way replace

                       if something == false {

                  with

                       if !something {

                   

                  - elminate what is not used, such as line 5: timestart is never used

                   

                  - take profit of some Swift features: to loop through all elements of an array (remember, it is ordered),

                  instead of

                          for indexCharacteristics in 0..<hkCharacteristics.count {

                            let hkChar = hkCharacteristics[indexCharacteristics]

                  use directly

                          for hkChar in hkCharacteristics {

                   

                   

                    func hkNotifications(idIn: String) {
                      
                      if !appSettings.key.homeKitAllowed  { return }
                      
                      let timeStart = CFAbsoluteTimeGetCurrent()
                      if let index = getDeviceIndexById(idIn) {
                        if !globalDevices.deviceList[index].displayName.contains("Couch")  { return }  // Test ONLY LR Couch Light
                        print("hkNotificaitons Start \(globalDevices.deviceList[index].displayName)")
                  
                        let oneDevice = globalDevices.deviceList[index].hkDevice  // Device = Accessory from HomeKit
                        if oneDevice == nil  { return }   //  no HomeKit Accessory Found
                        if !oneDevice!.isReachable && oneDevice!.isBlocked { return }  // Don't try to access this device}
                         
                        for indexServices in 0..<onedevice!.services.count {<br="">        let hkCharacteristics = oneDevice!.services[indexServices].characteristics
                  //        for indexCharacteristics in 0..//          let hkChar = hkCharacteristics[indexCharacteristics]
                          for hkChar in hkCharacteristics {
                            if hkChar.isNotificationEnabled {
                              print("hkNotificaitons are ALREADY enabled for: \(globalDevices.deviceList[index].displayName)")
                            } else {
                              if hkChar.characteristicType == "00000008-0000-1000-8000-0026BB765291" { // Test ONLY Brightness for now
                                hkChar.enableNotification(true, completionHandler: { error in
                                  if error != nil {
                                    print("\(hkChar), Error in Notification Sets \(error!.localizedDescription)")
                                  } else {
                                    print("hkNotificaitons are set to TRUE for: \(globalDevices.deviceList[index].displayName)")
                                  }
                                }
                                )
                              }
                            }
                          }
                        }
                      }
                    }
                    • Re: Help with Delegate & HomeKit
                      Captain Joe Level 1 Level 1 (0 points)

                      Claude and friends,  Here are more thoughts...

                       

                      Here is my summary of the thread you suggested…

                      • A protocol named UpdateVC1 is declared outside of any class.
                      • The class VC1 uses that protocol named UpdateVC1
                      • In that VC1 class, there is a function named updateName, which corresponds to the func name in the protocol UpdateVC1
                      • The class VC2 has a var named delegate with an optional type of the same protocol name UpdateVC1
                      • When VC2 wants VC1 to know about the change in value of the name, it calls it's delegate.updateName.
                      • This passes the value from VC2 to VC1

                       

                      Is that generally accurate?

                       

                      In VC2, is the var 'delegate' a reserved word in swift or could it have been:

                      var anyOtherNameDelegate: UpdateVC1?

                      My app is only using swiftUI and I have no experience with ViewControllers.

                      My objective is that when the brightness value on a lamp changes from 10% to 50% I would like to be notified.

                       

                       

                      I think... HomeKit has a defined protocol named HMAccessoryDelegate with a method accessory(_:service:didUpdateValueFor:)

                      https://developer.apple.com/documentation/homekit/hmaccessorydelegate

                      https://developer.apple.com/documentation/homekit/hmaccessorydelegate/1615286-accessory

                       

                      In comparing my objective to the ViewController example, I believe that Apple HomeKit has already declared the protocol with several functions.

                       

                      One of the declared functions is: Accessory didUpdateValueFor

                      optional func accessory(_ accessory: HMAccessory, service: HMService, didUpdateValueFor characteristic: HMCharacteristic)

                       

                      Here is my most recent attempt...

                       

                       

                      import Foundation
                      import HomeKit
                      
                      let hkHomeManager = HomeKitManagerClass()
                      let hkAccessoryManager = HomeKitAccessoryClass()
                      let myCharTypes = readCharacteristicsCSV()
                      
                      class HomeKitAccessoryClass: NSObject, HMAccessoryDelegate {
                        
                        var hkHomeAccessory: HMAccessoryDelegate!
                        var delegate: HMAccessoryDelegate?
                        
                        func accessory (_ accessory: HMAccessory, service: HMService, didUpdateValueFor characteristic: HMCharacteristic) {
                          // When an accessory (HomeKit Device) changes a value,
                          //  this func should be called by Apple so that I can act on the changed value
                          print ("Did Update Value For...")
                          print ("Do other interesting things here")
                          print (accessory.name)
                          print (service.name)
                          print (characteristic.metadata)
                        }
                        
                        func hkNotifications(idIn: String) {
                          if !appSettings.key.homeKitAllowed { return }
                          if let index = getDeviceIndexById(idIn) {
                            if !(globalDevices.deviceList[index].displayName).contains("Couch") { return }  // Test ONLY LR Couch Light
                            print("hkNotificaitons Start \(globalDevices.deviceList[index].displayName)")
                            let oneDevice = globalDevices.deviceList[index].hkDevice  // Device = Accessory from HomeKit
                            if oneDevice == nil  { return }   //  no HomeKit Accessory Found
                            if oneDevice!.isReachable == false && oneDevice!.isBlocked { return }  // Don't try to access this device}
                            
                            for indexServices in 0..<oneDevice!.services.count {
                              let hkCharacteristics = oneDevice!.services[indexServices].characteristics
                              for indexCharacteristics in 0..<hkCharacteristics.count {
                                let hkChar = hkCharacteristics[indexCharacteristics]
                                //if ([thisCharacteristic.properties containsObject:HMCharacteristicPropertySupportsEventNotification]) {
                                //  [thisCharacteristic enableNotification:TRUE completionHandler:^(NSError *error) {
                                if hkChar.isNotificationEnabled {
                                  print("hkNotificaitons are ALREADY enabled for: \(globalDevices.deviceList[index].displayName)")
                                } else {
                                  if hkChar.characteristicType.contains("0")  { // == "00000008-0000-1000-8000-0026BB765291" { // Test ONLY Brightness for now
                                    hkChar.enableNotification(true, completionHandler: { error in
                                      if error != nil {
                                        print("\(hkChar), Error in Notification Sets \(error!.localizedDescription)")
                                      } else {
                                        print("hkNotificaitons are set to TRUE for: \(globalDevices.deviceList[index].displayName)")
                                      }
                                    }
                                    )
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                      
                      

                      And... 

                      Thanks for helping!

                       

                      Steve

                        • Re: Help with Delegate & HomeKit
                          Claude31 Level 8 Level 8 (7,245 points)

                          Is this attempt working ?

                           

                          It is not possible to test your partial code.

                          But why do you declare

                            var hkHomeAccessory: HMAccessoryDelegate!

                           

                          and not:

                            var hkHomeAccessory: HomeKitAccessoryClass?

                           

                           

                          You do not show where you set hkHomeAccessory.

                          So it is dangerous to declare as implicitly unwrapped ( ! ) ; better make it optional ( ? )

                           

                          For your questions:

                          In VC2, is the var 'delegate' a reserved word in swift or could it have been:

                               var anyOtherNameDelegate: UpdateVC1?

                          It is not a reserved name, can be any valid name.

                           

                          My app is only using swiftUI and I have no experience with ViewControllers.

                             You should have told before, that is a different story.

                               May read this (it is a bit technical): h ttps://medium.com/flawless-app-stories/whats-the-protocol-in-swiftui-94c871f082e5

                             You should move your post to SwiftUI section and make it clear you are in a SwiftUI environment.