HclX / WyzeSensePy

A python script communicating with WyzeSense gateway
MIT License
91 stars 15 forks source link

Add support for low battery notification events #17

Open raetha opened 3 years ago

raetha commented 3 years ago

I don't have details on how this works unfortunately, but Wyze in a recent-ish Testflight build of their app had low battery notifications listed in the release notes, making it possible. I'm still leaning towards the unknown event crash that resulted in PR #14 might actually be those events, but it would need to be determined how to parse them so that they hit the events functions and can be used by other projects.

AK5nowman commented 3 years ago

Are the "unknown event" crashes that you are referring to the event type 0xAB? The only event types that are handled on the camera, and therefore passed to their servers, are 0xA1 and 0xA2, though I suspect only 0xA2 are ever seen. Type 0xAB are not handled and there are no other types.

ignore me, i missed the changes up around line 128.

AK5nowman commented 3 years ago

@raetha do you have any logs of the raw data that would produce the "Invalid packet" because it doesn't have the proper length?

I'm wondering if it is not an invalid packet but instead an incomplete packet. I haven't worked with reading a stream of data from a usb device before but I'm assuming it is going to be similar to reading data from a socket stream. If that is true, its not all that uncommon to not receive the complete packet in a single call to read(). The way is currently stands, if an incomplete packet is received that packet will be ignored because in Dongle._Worker we increment our cursor by 2 when we receive "None" from Packet.Parse.

trankillity commented 3 years ago

This could be what I'm experiencing too. Whenever a battery gets "low" and the light starts blinking 3 times on the sensor, I start to receive the "Invalid packet" notifications. So it could be that the sensors themselves are sending the low battery signal packet rather than relying on software to do that via percentages.

These invalid packets eventually cause the sensor to stop reporting events too, requiring replacing the battery to get them to work again.

Potentially related to this, I believe the divisor might be off by a factor of 5 for reporting battery state. I've been using Wyze Sensors for around 2 years, and the sensors tend to start blinking around 85% reported battery, and stop working altogether around 80% (which would explain the factor of 5).

I'm using WyzeSense2MQTT by @raetha which implements this library.

tholland15 commented 3 years ago

I had 2 batteries die recently and I was slow to replace them. By the time I did, both sensors had lost their mac address (who stores mac addresses in volatile memory!?)

I've just created a node-red flow to check all of my sensors battery attribute every day and alert me if any are at 82% or below. I agree with @trankillity that the battery level is likely off by a factor of 5, as I have never seen one work below 80% and both of my dead ones had last reported a battery level of 80.

I've got a stockpile of these sensors sitting on the shelf, because other than the battery issue, they work GREAT. But the question is, are my unopened sensors going to silently brick on the shelf when the factory-installed battery in them dies, if that hasn't happened already?

I read on reddit that low-voltage is what erases the mac address, NOT 0 voltage. The poster recommended removing the battery from any sensors you were not actively using to save them, but this seems counter intuitive to me and would invalidate my assumption that the only memory on these was volatile memory protected by a capacitor, can any of you guys confirm this?

maage89 commented 3 years ago

Thanks for the heads-up @tholland15. I had 10 extra sensors, so today I took out the battery of all of them, so hopefully they'll survive.

I logged all the events from the sensors, and the lowest reporting I got was 74. The sensor which did that now has a zero address mac though... Sending an alert if sensors are below XX % is a good idea, but what if the sensors are not used in a long period of time? Mine only reports StateEvents with battery, so if the sensor never changes state, the alert will never come. Maybe one day I will see if I can deduce anything from the rawEvents I logged.

I agree, that it seems counter intuitive, that low battery erases the mac, while no battery is fine. I saw a post here with a possible explanation: https://github.com/kevinvincent/ha-wyzesense/issues/130#issuecomment-715928774

Now I am kinda stuck, since I can't seem to remove the zero mac sensor from the USB bridge, and the List function in the sample.py fails. I don't have the cam nearby, but when I get it, I will try to reconnect it and try to reset the bridge.

If someone knows a way to reset the bridge to factory and delete all previously paired devices, that would be awesome :)

tholland15 commented 3 years ago

@maage89 have you tested any of the 10 sensors you took the battery out of in the 5 days since you wrote that post? I took one of my own brand new (contact) sensors on Sunday or Monday, paired it with the bridge, then removed the battery and set it on my desk. Today (Friday) I decided to test the sensor and sure enough its MAC address is now null and the bridge does not respond to it at all.

Results from your test would be helpful, as I also wanted to remove the battery from all of my new sensors, but now I'm unsure (could I have done something wrong?).

Also to your other point regarding battery life, my in-use sensors, even on windows that are only opened a few times per year, seem to communicate their battery life at least 1 time per day. So I should still get low battery alerts from node-red, even on sensors that are not triggered often. At least it appears that way so far in my testing.

maage89 commented 3 years ago

@tholland15 I didn't think to try it until you just mentioned it now. I reinserted the batteries in my remaining sensors last night (friday), and this morning I succesfully paired all of them with the bridge. They all still hold a valid mac. There is only 5 left though, because I had to use some of them to replace my zero mac ones.

Okay, so it should be just a matter of decoding the messages from the sensors? Are you using this library through node-red? (I haven't used node-red, so I don't really know how it works..) I am running this on a raspberry pi with a python program. My plan was to look through the raw events, to see if I can decode some of them. But I am not very comfortable in hex/binary, so it might be a long shot..

maage89 commented 3 years ago

So after writing the message yesterday, I decided to try and look through my logs and see if I could decode the messages.

I started by looking at the state event messages, since I already knew their values for battery, mac, type, signal and state. Here is what I found: When a state event occurs, there will be 2 messages. The first one contains 29 bytes, 8 of which are the MAC address. Some of them in the beginning is the CMD message, but the rest I am not sure about. After the first one, the gateway.py sends an ack as a reply. Then the second message comes, which contains also the mac, but also the sensor type, battery, state and signal. There may be more, but these are the only ones I care about. Another ack is sent from gateway.py. Gateway.py converts the bytes to hex with the command "binascii.hexlify(s)", where s is the string of bytes being converted. If debug is enabled, you will see a message like this: DEBUG 2020-11-21 22:09:30,395 Trying to parse: b'55aa53193500000175eb016fb80ea23737383635543132010100040695' The b'xxxx' is pythons way of expressing bytes. If we split the message up into bytes it will look like this: github explain (I changed the message slightly so it's not really the MAC of my sensor)

  1. The top row (Light-blue) is just number of bytes starting from zero
  2. The second row (light-yellow) is the bytes.
  3. The third row is just a comment of what the bytes contain
  4. The fourth row is the byte converted from hex to decimal
  5. The fifth row (light-orange) is the byte converted from hex to ASCII-text.
  6. The sixth row marks which part of the msg is the payload.

I used this online converter to convert between everything: https://www.rapidtables.com/convert/number/ascii-hex-bin-dec-converter.html As you can see, there is still a lot of bytes I don't know what are. I think some of the bytes before the mac address is the timestamp. Also, note how the MAC is bytes converted to ASCII, but battery, state and signal is hex converted to decimal.

I am sure none of the above is new to the guy who wrote gateway.py, but it took my awhile to figure it out, so I might as well share it.

Once I had figured out the above, I took a look at the raw events. By looking at my logs, I noticed that they occur every 4 hours. There is only one msg and one ack, not two like the state events. Gateway.py and the sample.py extracts the mac address and prints something like this: [2020-11-21 22:09:07]['77865T12']RawEvent: type=raw_A1, data=b'\x01\x11a\x00\x01\x00\x00\x15E' The data printed is only part of the payload, because the MAC, etc. is already taken out. And this time, the data is not converted to hex, but we can do that in Python:

>>> import binascii
>>> binascii.hexlify(b'\x01\x11a\x00\x01\x00\x00\x15E')
b'011161000100001545'

And using the same table I figured out where the bytes was: github explain raw

So I'm just doing this to extract data from the raw event:

sensor_battery = e.Data[2]
sensor_state = e.Data[5]
sensor_signal = e.Data[8]
sensor_MAC = e.MAC
sensor_update_time = e.Timestamp.strftime("%Y-%m-%d %H:%M:%S")

I am not finished yet, as I still need to determine which byte contains the sensor type, but yesterday I only had contact sensors paired, so I didn't check motion sensors yet. But at least now I am able to update the battery state of my sensor every 4 hours when the raw events occur. But remember, the battery dies around 80%, so don't wait for it to go to 20% or something. I will probably replace mine around 85% next time, since I already lost 5 sensors.

Hopefully this helps someone :)

P.S. I don't know how to contribute code, etc. directly to gateway.py, but maybe someone else can do that :)

Edit: I think I found out which byte is the sensor type: It's the first byte after the MAC, so in this image it's the first byte (number 0): github explain raw If it's 01, it's a contact sensor, if it's 02, it's a motion sensor.

AK5nowman commented 3 years ago

@maage89 Awesome, can't believe I didn't see this data packet when I was digging, I even know a packet type A1 existed and still somehow missed it in the logs. check out this branch of @raetha wyzesense2mqtt. I'm testing it now but wanted to let you know.

The packet type AB is the only other packet type that I see. This packet type is not handled in the wyze firmware (at least of version 156). Appears it is sent every 2 minutes when there is on going motion on the motion sensor?

raetha commented 3 years ago

As a quick update, I've just merged the changes @AK5nowman made into the devel branch of my tool (and fork of this library). Assuming things go well there, I'll try to get a PR here from my fork with the changes.

Thanks to everyone that helped decode and implement this. I had assumed that the sensors were sending a dedicated low battery alert, but since it is more of a regular heartbeat type of message that makes it easier to work with and write triggers against the data.