shauntarves / wyze-sdk

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

Issue with Setting Guest Access Code and Detecting Keypad for Wyze Lock #184

Open gitayam opened 4 months ago

gitayam commented 4 months ago

I am experiencing issues with setting a guest access code for a Wyze Lock using the Wyze SDK. Specifically, the SDK fails to detect the keypad associated with the lock, even though the keypad is enabled and visible in the Wyze app.

Context

Device Nickname: HOME_NAME Keypad Status: Enabled (visible in the Wyze app) Firmware Version: 1.0.4.0

Steps to Reproduce:

Use the Wyze SDK to list devices. Attempt to set a guest access code for the lock. Code Snippet:

import os
import time
import argparse
import logging
import requests
from dotenv import load_dotenv
from wyze_sdk import Client
from wyze_sdk.errors import WyzeApiError
from wyze_sdk.models.devices.locks import LockKeyPermission, LockKeyPermissionType

# Setup logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Load environment variables from .env file
load_dotenv()

WYZE_ACCESS_TOKEN = os.getenv('WYZE_ACCESS_TOKEN')
WYZE_REFRESH_TOKEN = os.getenv('WYZE_REFRESH_TOKEN')
LOCK_MAC_ADDRESS = os.getenv('LOCK_MAC_ADDRESS')  # Load MAC address from .env
ACCESS_CODE = "1234"  # Replace with desired access code
ACCESS_CODE_NAME = "Testtt"  # Replace with desired access code name
START_TIME = int(time.time()) * 1000  # Current time in milliseconds
END_TIME = START_TIME + 24 * 60 * 60 * 1000  # 24 hours from now in milliseconds

# Function to refresh the access token
def refresh_access_token(refresh_token):
    url = "https://api.wyzecam.com/app/user/refresh_token"
    payload = {
        "app_ver": "wyze_developer_api",
        "app_version": "wyze_developer_api",
        "phone_id": "wyze_developer_api",
        "refresh_token": refresh_token,
        "sc": "wyze_developer_api",
        "sv": "wyze_developer_api",
        "ts": int(time.time() * 1000)
    }
    headers = {
        "Content-Type": "application/json"
    }

    try:
        response = requests.post(url, json=payload, headers=headers)
        response.raise_for_status()
        data = response.json()
        new_access_token = data['data']['access_token']
        new_refresh_token = data['data']['refresh_token']
        logging.info("Access token refreshed successfully")
        return new_access_token, new_refresh_token
    except requests.RequestException as e:
        logging.error(f"Failed to refresh token: {e}")
        raise

# Function to get the client
def get_client():
    global WYZE_ACCESS_TOKEN, WYZE_REFRESH_TOKEN
    try:
        client = Client(token=WYZE_ACCESS_TOKEN)
        # Test if the access token is valid
        client.devices_list()
        logging.info("Access token is valid")
    except WyzeApiError as e:
        if "AccessTokenError" in str(e) or "access token expired" in str(e):
            logging.warning("Access token expired, refreshing...")
            try:
                WYZE_ACCESS_TOKEN, WYZE_REFRESH_TOKEN = refresh_access_token(WYZE_REFRESH_TOKEN)
                os.environ['WYZE_ACCESS_TOKEN'] = WYZE_ACCESS_TOKEN
                os.environ['WYZE_REFRESH_TOKEN'] = WYZE_REFRESH_TOKEN
                client = Client(token=WYZE_ACCESS_TOKEN)
            except Exception as refresh_error:
                logging.error(f"Failed to refresh access token: {refresh_error}")
                raise e  # Re-raise the original error if refreshing fails
        else:
            logging.error(f"WyzeApiError: {e}")
            raise e  # Re-raise the original error if it's not an access token issue
    return client

def set_guest_access_code(client, lock_mac, access_code, code_name, start_time, end_time):
    try:
        # Fetch the lock device
        device = next((d for d in client.devices_list() if d.mac == lock_mac), None)
        if not device:
            logging.error(f"No device found with MAC address {lock_mac}")
            return

        logging.info(f"Setting guest access code for device: {device.nickname}")

        permission = LockKeyPermission(
            type=LockKeyPermissionType.DURATION,
            begin=start_time,
            end=end_time
        )

        response = client.locks.create_access_code(
            device_mac=lock_mac,
            access_code=access_code,
            name=code_name,
            permission=permission
        )

        logging.info(f"API Response: {response}")
    except WyzeApiError as e:
        logging.error(f"Failed to set guest access code: {e}")
    except Exception as e:
        logging.error(f"Unexpected error while setting guest access code: {e}")

def main():
    parser = argparse.ArgumentParser(description="Wyze Lock Guest Access Code Setter")
    args = parser.parse_args()

    try:
        client = get_client()
        set_guest_access_code(client, LOCK_MAC_ADDRESS, ACCESS_CODE, ACCESS_CODE_NAME, START_TIME, END_TIME)
    except Exception as e:
        logging.error(f"Script failed: {e}")

if __name__ == "__main__":
    main()

Observed Behavior:

The SDK fails to detect the keypad associated with the lock, despite the keypad being enabled and visible in the Wyze app. The error message encountered: 'NoneType' object has no attribute 'is_enabled'

Expected Behavior:

The SDK should detect the keypad associated with the lock. The guest access code should be set successfully.

Environment:

Wyze SDK version: 2.1.0 Python version: Python 3.12.4 Operating System: MacOS Using .env with

WYZE_EMAIL=your_email@example.com # required the first time to obtain the access token
WYZE_PASSWORD=your_password # required the first time to obtain the access token
WYZE_TOTP_KEY=your_totp_key # required to login when account has MFA enabled
WYZE_API_KEY=your_api_key
WYZE_KEY_ID=your_key_id
WYZE_ACCESS_TOKEN=your_access_token # provided by the script
WYZE_REFRESH_TOKEN=your_refresh_token # provided by the script

HOME_1_NAME="Name House"
HOME_1_ICAL_URL=your_home_1_airbnb_ical_url
HOME_1_LOCK_DEVICE_MAC=your_home_1_lock_device_mac
HOME_1_KEYPAD_SERIAL_NUMBER=your_home_1_keypad_device_mac
HOME_1_CHECK_IN_TIME=16:00
HOME_1_CHECK_OUT_TIME=11:00
carverofchoice commented 4 months ago

Fixing the Keypad would be a great update and useful in several use-cases.