ikalchev / HAP-python

A python implementation of the HomeKit Accessory Protocol (HAP)
Other
607 stars 116 forks source link

notify multiple HomeKit Apps when switch state has changed #427

Open th0u opened 1 year ago

th0u commented 1 year ago

Hi, I use your HAP Lib (4.5.0) and implemented a simple accessory (on-off switch) to be included in the bridge and into Homekit. Thanks to some samples and clear documentation... an easy one. The accessory has setter and getter_callback to set or get the state. Basically it works well as expected.

The remote controllable switch can also be switched on/off manually and the switch does not self publish state changes. This is basically no problem in my scenario, as whenever I open the HomeKit app, the Bridge infrastructure is triggered to call the getter_callback which synchronizes the state from switch to the accessory instance and so HomeKit shows the current state. Synchronizing the state within HomeKit also works "live" e.g. when opening HomeKit app on two different Devices and when I toggle the switch on one device then the change is reflected in HomeKit App on other device virtually instantly.

I have these use cases:

1 Switch toggled using HomeKit

Expected behavior:

Observed behavior: (a), (b), (c). All OK!

2 Remote Switch toggled manually while HomeKit apps are not in foreground on iOS

Expected behavior:

Observed behavior: (a), (b). All OK!

Now the unexpected outcome:

3 Remote switch toggled manually, while HomeKit App#1 on iOS Device #1 is in background and HomeKit App #2 on device #2 is in foreground.

Expected behavior:

Observed Behavoir: (a), (b), (c). Missing (d).

So the missing one is (d) in Use case #3. The bridge updated itself while opening HomekitApp#1 but the update is not propagated to Homekit App#2. As this is working in use case #2, I have the hope that this could also work for use case #3.

So, is there a way to notify all active Homekit Apps the update their state view?

Thnx , T

Pythonaire commented 1 year ago

I think, the "HomeKit apps" will not be notified generally. The brigde is connected to the HomeKit and send/receive states. The App connect the HomeKit an send/receive the states to/from the HomeKit. The Homekit mediates only. It seems to me, the second app have no reliable connection to the homekit. Can the second App switch and the first App see the action?

th0u commented 1 year ago

The effect is the same in Use case #3 even when switching the roles of Device/Homekit App #1 and #2. I can rule out connectivity issues.

Pythonaire commented 1 year ago

The effect is the same in Use case #3 even when switching the roles of Device/Homekit App #1 and #2. I can rule out connectivity issues.

Question: both clients Device/Homekit App #1 and #2 are on the same iOS release? And if you swipe down on the Device/Homekit (try refresh the view) nothing changed?

I have an equal use case, but using HTTPServer (example: /accessories/Http.py) to get the device state in reverse. If i'm switching manually, the device send {"aid":XXX,"services": {"Switch": {"On": true}}}' or false to the bridge. It works as expected.

th0u commented 1 year ago

@Pythonaire : Yes, both devices are on same iOS. I observed this behaviour during after multiple test/wipe/test iterations.

The http example hint is unfortunately still not an option, as said in my initial post, the switch does not have the capabilities to send something back.

Pythonaire commented 1 year ago

@Pythonaire : Yes, both devices are on same iOS. I observed this behaviour during after multiple test/wipe/test iterations.

The http example hint is unfortunately still not an option, as said in my initial post, the switch does not have the capabilities to send something back.

could you try additional notify() in conjunction with get_value()?

Pythonaire commented 1 year ago

That's my switch, with setter_callback and getter_callback, it works as expected.

class TestSwitch(Accessory):
    category = CATEGORY_SWITCH
    def __init__(self, *args, **kwargs): 
        super().__init__(*args, **kwargs)
        self.set_info_service(firmware_revision='0.0.3', model='XXX', manufacturer= 'XXX', serial_number="XXX")
        switch = self.add_preload_service('Switch', chars=['On'])
        self.UnitState = switch.configure_char('On') # bool needed
        self.UnitState.set_value(self.getState())
        self.UnitState.setter_callback = self.setState
        self.UnitState.getter_callback = self.getState()

    def getState(self):
        try:
            state = requests.get(url).json() # return int
        except ConnectTimeout as e:
            logging.info('**** request to {0} timed out: {1}'.format(url, e)) 
            state = 0
        return bool(state)

    def setState(self, value): # value is bool
        state = int(value)
        try:
            requests.post(url, data = str(value)) # need str
        except ConnectTimeout as e:
            state = 0
            logging.info('**** request.post got exception {0}'.format(e)) 
        return bool(state)