giejay / nukiPyBridge

30 stars 9 forks source link

API Changes FW 2.8.15 #5

Closed Pingbo closed 4 years ago

Pingbo commented 4 years ago

With the new Firmware, Nuki introduced the Battery Percentage Fields:

https://developer.nuki.io/page/nuki-smart-lock-api-220/2#heading--keyturner-states

This breaks the state function, so it need some adjustments to read Out the required fields for Battery criticality and perhaps the Battery Percentage can be implemented as well

giejay commented 4 years ago

I quickly scanned through the documentation, does this mean the field offset of the fields starting from:

https://github.com/giejay/nukiPyBridge/blob/5e88c1a3e6e79cf8ce537b15c2ab365c95259bd6/nuki_messages.py#L252

Will have to change? What is exactly now breaking? Can you show me the new payload (Did not update my Nuki's yet)?

Pingbo commented 4 years ago

Yep exactly, here some Infos from my nuki with updated FW:

payload = 020103e4070a0c08150a00007c0e00020300020000

Output from brigde: Nuki_STATES Nuki Status: Door Mode Lock Status: Locked Trigger: 03 Current Time: 12-10-2020 08:21:10 Time Offset: 0 Critical Battery: 7C Doorsensor State: door closed

Battery displayed in App: 62% So my Assumtion is that 7C hex is 01111100 binary The first flag is battery criticality, 2-7 is the Percentage... In my case 111110 is 62%

Currently i have no idea how to solve this in a elegant way

Perhaps something like this: (But this seems complicated)

self.criticalBattery = bin(int(payload[24:26], 16))[2:].zfill(8)
            if self.criticalBattery[:1] == '0':
                self.criticalBattery = 'OK'
            elif self.criticalBattery[:1] == '1':
                self.criticalBattery = 'Critical'
self.BatteryPercentage = int(bin(int(payload[24:26], 16))[2:].zfill(8)[1:7].zfill(8),2)

self.criticalBattery reads out the hex, formats to binary and gets the first bit self.BatteryPercentage reads out the hex, formats to binary, gets 2-7 bit and formats it to decimal

Or a little bit cleaner:

self.Battery = bin(int(payload[24:26], 16))[2:].zfill(8)
            if self.Battery[:1] == '0':
                self.criticalBattery = 'OK'
            elif self.Battery[:1] == '1':
                self.criticalBattery = 'Critical'
            self.BatteryPercentage = int(self.Battery[1:7],2)

And for sure edit the print:

def show(self):
        return "Nuki_STATES\n\tNuki Status: %s\n\tLock Status: %s\n\tTrigger: %s\n\tCurrent Time: %s\n\tTime Offset: %s\n\tCritical Battery: %s\n\tBattery Percentage: %d\n\tDoorsensor State: %s" % (self.nukiState,self.lockState,self.trigger,self.currentTime,self.timeOffset,self.criticalBattery,self.BatteryPercentage,self.Doorsensor)

I assume that Battery Percentage: %d will need the %d, but i haven't tested it yet

giejay commented 4 years ago

Can you check your actual battery percentage in the app? It's not charging and not critical I guess

The documentation states it's in steps of 2 so that's why I'm surprised that the decimal number is 62

giejay commented 4 years ago

In your code, aren't you missing the charging flag? Shouldn't it be 2:7 instead of 1:7?

Otherwise, looks good! And since we are using Python 3.7, we can use: f'{0x7C:0>8b}' to get a binary string I think...

giejay commented 4 years ago

Btw. 01111100, from right to left means 011111 is the percentage, that's 31 in decimal, multiplied by 2, is your percentage. So we should also multiply it as stated in the docs.

Pingbo commented 4 years ago

Seems like i'm wrong, cause 6Bit (2-7) are not able to show 100%.... I now have tested with other Batteries: payload = 020100e4070a0c0923020000b00e00000000020000

Nuki_STATES
        Nuki Status: Door Mode
        Lock Status: Locked
        Trigger: Bluetooth
        Current Time: 12-10-2020 09:35:02
        Time Offset: 0
        Critical Battery: B0
        Doorsensor State: door closed

So this gets me to the assumtion the the Percantage are the first 7 Bit, because: B0 = 10110000 1011000 = 88 Percentage in App = 88

Same counts for the example with 62%: 7c = 01111100 0111110 = 62

So the code will look like this:

self.Battery = bin(int(payload[24:26], 16))[2:].zfill(8)
            if self.Battery[??????????] == '0':
                self.criticalBattery = 'OK'
            elif self.Battery[??????????] == '1':
                self.criticalBattery = 'Critical'
            self.BatteryPercentage = int(self.Battery[:7],2)

Currently i have no almost empty Batteries, so i have no clue where the criticalBattery is....

For Charging and Critical Battery Flag i have no idea whats going on here. I'm sadly not able to test it. Charging is probably only for the Nuki Power Pack?

I've already tested a little bit with f-strings, but i wasn't able to find a good solution. But if you have a good one, i'm fine with it :)

giejay commented 4 years ago

Since it's binary, the first bit is the most right one.

So: 10110000

Last zero is battery critical Second to last (0) is charging.

The first 6 bits are 44 so 88.

100 procent battery would be 110010 (50 decimal)

Pingbo commented 4 years ago

God, you are so right.... the first bit is the right one not the left.... This means for 88%: B0 = 10110000 101100 = 44 * 2 = 88

7c = 01111100 011111 = 31 * 2 = 62

Code with Charging Flag without f-strings:

self.Battery = bin(int(payload[24:26], 16))[2:].zfill(8)
            if self.Battery[7:8] == '0':
                self.criticalBattery = 'OK'
            elif self.Battery[7:8] == '1':
                self.criticalBattery = 'Critical'
            if self.Battery[6:7] == '0':
                self.chargingBattery = 'Not Charging'
            elif self.Battery[6:7] == '1':
                self.chargingBattery = 'Charging'
            self.BatteryPercentage = int(self.Battery[:6],2)*2

And the print function:

def show(self):
        return "Nuki_STATES\n\tNuki Status: %s\n\tLock Status: %s\n\tTrigger: %s\n\tCurrent Time: %s\n\tTime Offset: %s\n\tCritical Battery: %s\n\tCharging Battery: %s\n\tBattery Percentage: %d\n\tDoorsensor State: %s" % (self.nukiState,self.lockState,self.trigger,self.currentTime,self.timeOffset,self.criticalBattery,self.chargingBattery,self.BatteryPercentage,self.Doorsensor)
giejay commented 4 years ago

This should be it, can you test this code?

Pingbo commented 4 years ago

Yes i able to test it, Output looks good:

Nuki State Request sent: Nuki_REQ
        Payload: Nuki_STATES
response received:
payload = 020100e4070a0c0a1a220000b00e00000000020000
Nuki_STATES
        Nuki Status: Door Mode
        Lock Status: Locked
        Trigger: Bluetooth
        Current Time: 12-10-2020 10:26:34
        Time Offset: 0
        Critical Battery: OK
        Charging Battery: Not Charging
        Battery Percentage: 88
        Doorsensor State: door closed
Pingbo commented 4 years ago

Perhaps you can open a new Branch for the "old" FW and update the master? That people can use the right one for their needs

giejay commented 4 years ago

I have checked in the code. Also verified if the change works with firmware 2.7 and it does not break, so I just pushed to master.

Also added retry logic for the state endpoint.