Bouke / HAP

Swift implementation of the Homekit Accessory Protocol
https://boukehaarsma.nl/HAP/
MIT License
364 stars 50 forks source link

Characteristic Function not called when updating powerState or brightness of a Accessory.Lightbulb subclass #130

Closed ctmanley closed 3 years ago

ctmanley commented 3 years ago

I'm using HAP for an application to implement HomeKit for devices on a Control4 home automation system. Currently the HAP bridge can run on a Mac and on an Xcode simulated iPad.

The list of Control4 devices is read from the C4 controller on startup. This list is presented through the user interface where each device can be added dynamically to the bridge or removed if active. So far I've written the code for lights and locks. These work as expected through the home app and with Siri.

When a device is active and selected, UI controls are presented for managing the device. For a light these consists of an on/off switch and a slider for brightness.

It is my understanding that the following code will turn on the light and set the brightness to 50%. I've included the C4HKLight class definition below. The C4Light class is used to control the light through the C4 controller when the characteristic function is called.

let light = C4HKLight(C4Light())
light.lightbulb.powerState.value = true
light.lightbulb.brightness?.value = 50

But when this is executed, the light does not change from whatever state it is in. The device icon in the home app on my iPhone does change to reflect the new setting. After a couple of seconds the icon in the Home app changes back to the state it was in before the code above was executed. The overridden characteristic function in the C4HKLight class is never executed.

If I use the home app to turn on/off or dim the light, the light is turned on/off or dimmed and the overridden characteristic function is executed.

So, the characteristic function in my subclass is called when I adjust the light in the home app but not when I make light value changes from within my application.

Anyone know what the problem may be or can you offer a suggestion for debugging it?

Thanks.

I have HAP included in my Xcode project as a Swift Package Dependency. I have not modified it in any way.

The C4HKLight class is defined as follows.

class C4HKLight: Accessory.Lightbulb {
    var c4Light: C4Light

    init(c4Light: C4Light) {
        self.c4Light = c4Light
        super.init(info: Service.Info(name: c4Light.itemName, serialNumber: "\(c4Light.itemID)"), isDimmable: c4Light.isDimmable)
    }

    override func characteristic<T>(_ characteristic: GenericCharacteristic<T>,
                                    ofService service: Service,
                                    didChangeValue newValue: T?) {
        if characteristic === lightbulb.powerState {
            logger.info("power state: \(String(describing: newValue))")
            if newValue as! Bool  {
                c4Light.lightOn()
            } else {
                c4Light.lightOff()
            }
        }
        if characteristic === lightbulb.brightness {
            c4Light.setLightLevel(level: newValue as! Int)
        }
        print("****** characteristic: \(characteristic)")
        super.characteristic(characteristic, ofService: service, didChangeValue: newValue)
    }
}
AdmiralTriggerHappy commented 3 years ago

I think you need to call a didChange function from the if characteristic line Here is some an example from a project I'm working on

    override func characteristic<T>(_ characteristic: GenericCharacteristic<T>,
                           ofService service: Service,
                           didChangeValue newValue: T?) {

    if characteristic === lightbulb.powerState {
        didChangePowerState(newValue: Bool(newValue as! Bool))
    }

    if characteristic === lightbulb.brightness {
            didChangeBrightness(newValue: Int(newValue as! Int))
    }
    if characteristic === lightbulb.hue {
            didChangeHue(newValue: Float(newValue as! Float))
        }

    if characteristic === lightbulb.saturation {
        didChangeSaturation(newValue: Float(newValue as! Float))
    }

        super.characteristic(characteristic, ofService: service, didChangeValue: newValue)
    } 

That calls

     func didChangePowerState(newValue: Bool) {

     self.powerState = newValue

     if !self.powerState
    {
            RGBValues = [0,0,0]
    }
     else
    {
     convert()
    }
     sendData()
    }

So trying create a function like

     func didChangePowerState(newValue: Bool) {

       if newValue  {
                c4Light.lightOn()
            } else {
                c4Light.lightOff()
            }

    }
ctmanley commented 3 years ago

The problem is that the characteristic function is not being executed. The c4Light functions work fine when the characteristic function is called.

When I manage the light from within the home app the characteristic function is called and the c4Light functions are called properly.

When I executed the following within my code the characteristic function in the C4HKLight subclass is not called.

let light = C4HKLight(C4Light())
light.lightbulb.powerState.value = true
light.lightbulb.brightness?.value = 50
Bouke commented 3 years ago

From the documentation:

Characteristic’s value was changed by controller. Used for bubbling up to the device, which will notify the delegate.

The controller here means the Home app on iOS / macOS.

So when you're programmatically changing the value, this method is not called. You need to expose some functionality on your C4HKLight implementation to actually make changes to the physical device.

Something like the following pseudo-code:


// in class C4HKLight
func updateLight() {
    if (light.lightbulb.powerState.value) {
        c4Light.lightOn()
    } else {
        c4Light.lightOff()
    }
}

// in your code
let light = C4HKLight(C4Light())
light.lightbulb.powerState.value = true
light.updateLight()
ctmanley commented 3 years ago

Thanks. I wasn't sure from the documentation that characteristic wasn't also called on a value change.