shauntarves/wyze-sdk

Token expiration

Closed this issue · 5 comments

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?

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.

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.

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.

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

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.