shauntarves / wyze-sdk

A modern Python client for controlling Wyze devices.
The Unlicense
298 stars 49 forks source link

Token expiration #72

Closed duckredbeard closed 2 years ago

duckredbeard commented 2 years ago

I added a few wyze lights controls to my home security python program. The doors at the top and bottom of the staircase have reed switches that my home security sees as buttons. When a door opens, two wyze bulbs turn on and time out for 30 minutes. If the door closes, they time out at 6 minutes. Everything works as I describe but only for a few days.

What I am finding is that the program throws the following error after a few days and the bulbs do not respond until I restart the script:

wyze_sdk.errors.WyzeApiError: The access token has expired. Please refresh the token and try again. The server responded with: {'ts': 1650590646583, 'code': '2001', 'msg': 'AccessTokenError', 'data': {}}

Behold the script:

from gpiozero import Button, LED from signal import pause from time import sleep from datetime import datetime from datetime import timedelta import requests

from wyze_sdk import Client from wyze_sdk.errors import WyzeApiError client = Client(email="NunYaBidness@janky.com", password="AintGettinIt")

garagepassage = Button(20) # Input from garage passage door stairspassage = Button(18) # Input from door at top of stairs

now = datetime.now() current_time = now.strftime("%H:%M:%S") print("Door Started at ", current_time) r = requests.post("https://autoremotejoaomgcd.appspot.com/sendnotification?key=NothingToSeeHere&title=Just%20Saying&text=Switch%20monitoring%20has%20begun")

bulbD1 = client.bulbs.info(device_mac=XXXXXXXXXXXX') # One of the Den bulbs

bulbS1 = client.bulbs.info(device_mac='XXXXXXXXXXXXXX') bulbS2 = client.bulbs.info(device_mac='XXXXXXXXXX') plugSLED = client.plugs.info(device_mac='XXXXXXXXXXXXXXX')

def open(): now = datetime.now() current_time = now.strftime("%H:%M:%S") print("Door open at " + current_time)

client.bulbs.turn_on(device_mac=bulbD1.mac, device_model=bulbD1.product.model)

client.bulbs.turn_on(device_mac=bulbS1.mac, device_model=bulbS1.product.model)
client.bulbs.turn_on(device_mac=bulbS2.mac, device_model=bulbS2.product.model)
client.plugs.turn_on(device_mac=plugSLED.mac, device_model=plugSLED.product.model)
#client.bulbs.turn_off(device_mac=bulbD1.mac, device_model=bulbD1.product.model, after=timedelta(hours=.5))
client.bulbs.turn_off(device_mac=bulbS1.mac, device_model=bulbS1.product.model, after=timedelta(hours=.5))    
client.bulbs.turn_off(device_mac=bulbS2.mac, device_model=bulbS2.product.model, after=timedelta(hours=.5))
client.plugs.turn_off(device_mac=plugSLED.mac, device_model=plugSLED.product.model, after=timedelta(hours=.5))
print("on")

def closed(): now = datetime.now() current_time = now.strftime("%H:%M:%S") print("Door closed at " + current_time)

client.bulbs.turn_off(device_mac=bulbD1.mac, device_model=bulbD1.product.model, after=timedelta(hours=.05))

client = Client(email="NunYaBidness@janky.com", password="AintGettinIt")
client.bulbs.turn_off(device_mac=bulbS1.mac, device_model=bulbS1.product.model, after=timedelta(hours=.1))
client.bulbs.turn_off(device_mac=bulbS2.mac, device_model=bulbS2.product.model, after=timedelta(hours=.1))
client.plugs.turn_off(device_mac=plugSLED.mac, device_model=plugSLED.product.model, after=timedelta(hours=.1))
#client = Client(email="NunYaBidness@janky.com", password="AintGettinIt")
print("off")

stairspassage.when_pressed = closed stairspassage.when_released = open garagepassage.when_pressed = closed garagepassage.when_released = open

pause()

I thought having the "client = Client(email="NunYaBidness@janky.com", password="AintGettinIt")" imbedded in the "def closed()" would refresh it upon each close event but that isn't working. How can I refresh this token without having to restart the python?

anthonystabile commented 2 years ago

As a workaround, I restart my python instance daily via crontab; however, I did experiment with calling client.refresh_token() within a try catch block.

My code is having issues catching the error. Maybe Shaun can provide more insight in best practices.

duckredbeard commented 2 years ago

The concern I have with restarting this daily is that I don't know how to kill it before each restart. Wouldn't want to have many instances of it running, even though each was dead.

anshuarya commented 2 years ago
client = Client(email=os.getenv('WYZE_EMAIL'), password=os.getenv('WYZE_PASS'))
 -- Loop here --
     <check sensor status>
     response=client.refresh_token()
     client._token = response["data"]["access_token"]
     client._refresh_token = response["data"]["refresh_token"]

I use the code block above to refresh the token in a loop (every 10 min in my case). My script usually stays alive/connected indefinitely. Internet dropping or some other error typically kills it first.

duckredbeard commented 2 years ago

My experience with python is quite limited. How do I make this loop inside what I posted in my original post?

shauntarves commented 2 years ago

Hey @duckredbeard and @anshuarya,

Thanks for using this library. I'm happy to help with any kind of issues you're seeing, but if I understand correctly, this is just a matter of understanding how to use the refresh token capability in your own script?

As a bit of background, the Wyze app/api use oauth for authentication and authorization. With the oauth protocol, you basically exchange your username/password credentials for an access token. That token has an expiration time to provide a fixed window of validity to prevent tokens that last indefinitely and leave a user exposed if they are compromised.

To deal with the "hey, I don't want this thing to expire because I use it" scenario, tokens can be refreshed. That's what the "refresh_token" method does. What you'll notice in @anshuarya's code above is that each time you refresh, you want to store the NEW refresh token you get back so that it's available the next time you want to refresh.

10 minutes might be a bit excessive for refreshing, but something like 30 minutes is probably a good place to start.

It sounds like what you're after is an answer to a more general question of "how do I create a recurring task in python?" The answer is that there are so many ways to do it, you should just google around a bit and find a simple solution that you understand and works for your use case. I would suggest starting here.