Help with Delegate & HomeKit

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)")
                }
              }
              )
            }
          }
        }
      }
    }
  }
}

Replies

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

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.

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)")
                }
              }
              )
            }
          }
        }
      }
    }
  }

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

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.

Sorry for the delay. I did get it working and plan to reply tonight with details. Lots of family / holiday stuff the past week.


Thanks for the help!

Great, enjoy the family.


And don't forget to close the thread when tested.

Thanks for the help.

I did not need

var hkHomeAccessory: HMAccessoryDelegate!

var delegate: HMAccessoryDelegate?


I added...

oneAccessory!.delegate = self


In earlier trials, I had tried to add a delegate to the Characteristic.


Here is the code that is currently working.


import Foundation
import HomeKit

let hkHomeManager = HomeKitManagerClass()
let hkAccessoryManager = HomeKitAccessoryClass()
let myCharTypes = readCharacteristicsCSV()

class HomeKitAccessoryClass: NSObject, HMAccessoryDelegate {
  //   accessoryChangedValue
  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 ("Do other interesting things here")
    let printout: String = "DidUpdateValue, \(accessory.name), \(service.name), \(characteristic.characteristicType), \(characteristic.localizedDescription), \(String(describing: characteristic.value))"
    //print (printout)
    globalCSVOut.append(printout)
  }
  
  func hkNotifications(idIn: String) {
    if !appSettings.key.homeKitAllowed { return }
    if let index = getDeviceIndexById(idIn) {
      let oneAccessory = globalDevices.deviceList[index].hkAccessory  // Device = Accessory from HomeKit
      if oneAccessory == nil  { return }   //  no HomeKit Accessory Found
      if oneAccessory!.isReachable == false && oneAccessory!.isBlocked { return }  // Don't try to access this device}
      oneAccessory!.delegate = self
      print("hkNotificaitons Start \(globalDevices.deviceList[index].displayName)")
      for oneService in oneAccessory!.services {
        let hkCharacteristics = oneService.characteristics
        for indexCharacteristics in 0..<hkCharacteristics.count {
          let hkChar = hkCharacteristics[indexCharacteristics]
          if hkChar.isNotificationEnabled {
            do {}
            //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 {
                  do {}
                  //print("hkNotificaitons are set to TRUE for: \(globalDevices.deviceList[index].displayName)")
                }
                else {
                  //print("\(oneAccessory?.name) \(hkChar.characteristicType), Error in Notification Set \(error!.localizedDescription)")
                }
              }
              )
            }
          }
        }
      }
    }
  }

Here is the code that is currently working.


It works ? Great. If so, don't forget to close the thread.