From 454ec3bb753c71913d35c332e1e3f9b902c1e9c6 Mon Sep 17 00:00:00 2001 From: Ali Bosworth Date: Sat, 21 Feb 2026 11:21:45 -0800 Subject: [PATCH 1/2] Add setup and testing instructions to Readme file, add easier test auth A few things to make it easier for someone else to jump into this project. - Add README documentation for setup, credential creation, and running tests - Add python-dotenv to setup.py dependencies and an example.env for reference --- .gitignore | 3 +++ README.rst | 38 ++++++++++++++++++++++++++++++++++++++ example.env | 3 +++ lyric/test.py | 21 +++------------------ lyric/test_common.py | 37 +++++++++++++++++++++++++++++++++++++ setup.py | 3 ++- 6 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 example.env create mode 100644 lyric/test_common.py diff --git a/.gitignore b/.gitignore index 02ae5b4..b2d4084 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ secret.txt +.env +.venv +token.txt *.py[cod] diff --git a/README.rst b/README.rst index fcbb6ae..40bf494 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,44 @@ Python API for the Honeywell Lyric™ Thermostat ========================================================= +Setup +===== + +:: + + python3 -m venv .venv + source .venv/bin/activate + pip install -e . + +This installs the package and its dependencies (``requests``, ``requests_oauthlib``, ``python-dotenv``). + + +Testing +======= + +Create an app at the `Honeywell Developer Portal `_ +to obtain your ``LYRIC_CLIENT_ID`` and ``LYRIC_CLIENT_SECRET``. Set the callback/redirect +URL to match ``LYRIC_REDIRECT_URI`` (e.g. ``https://localhost``). + +Duplicate example.env as .env and populate it (or otherwise set these environment variables): + + LYRIC_CLIENT_ID=your_client_id + LYRIC_CLIENT_SECRET=your_client_secret + LYRIC_REDIRECT_URI=https://localhost + +On first run, the script will print an authorization URL. Open it in a browser, +log in with your Honeywell account (separate from Honeywell developer account), +and complete the authorization flow, which will result in being redirected to +a url containing "code" and "state" parameters (the URL will not work), paste +this URL back into the CLI. + +A ``token.txt`` file will be created to cache the token for subsequent runs. + +**General device test** — prints locations, thermostats, and water leak detectors:: + + python -m lyric.test + + History ======= diff --git a/example.env b/example.env new file mode 100644 index 0000000..2742102 --- /dev/null +++ b/example.env @@ -0,0 +1,3 @@ +LYRIC_CLIENT_ID=your_client_id +LYRIC_CLIENT_SECRET=your_client_secret +LYRIC_REDIRECT_URI=https://localhost diff --git a/lyric/test.py b/lyric/test.py index eb1c341..4f4d5d4 100755 --- a/lyric/test.py +++ b/lyric/test.py @@ -2,25 +2,10 @@ """Battery of tests for lyric module.""" -from . import lyric as lyric_local +import json +from .test_common import get_api -client_id = r"" -client_secret = r"" - -redirect_uri = "https://hass.deproducties.com/api/lyric" -app_name = "Sample" -token_cache_file = "token.txt" - -lapi = lyric_local.Lyric( - client_id=client_id, - client_secret=client_secret, - token_cache_file=token_cache_file, - redirect_uri=redirect_uri, - app_name=app_name, -) - -if lapi._token is None: - print(lapi.getauthorize_url) +lapi = get_api() print(lapi._locations) diff --git a/lyric/test_common.py b/lyric/test_common.py new file mode 100644 index 0000000..6c3b335 --- /dev/null +++ b/lyric/test_common.py @@ -0,0 +1,37 @@ +# -*- coding:utf-8 -*- + +"""Shared setup for test scripts.""" + +import os +from dotenv import load_dotenv +from . import Lyric + +load_dotenv() + +TOKEN_CACHE_FILE = "token.txt" + + +def get_api(): + """Create and authorize a Lyric API instance.""" + + lapi = Lyric( + client_id=os.environ["LYRIC_CLIENT_ID"], + client_secret=os.environ["LYRIC_CLIENT_SECRET"], + token_cache_file=TOKEN_CACHE_FILE, + redirect_uri=os.environ["LYRIC_REDIRECT_URI"], + app_name="Sample", + ) + + if lapi._token is None: + # Allow http://localhost for local OAuth redirect + os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1" + print("Open this URL in a browser to authorize:") + print(lapi.getauthorize_url) + redirect_url = input( + "\nAfter you authorize you will be redirected to a URL containing" + " a \"code\" and \"state\" parameter, paste that full URL here: " + ).strip() + lapi.authorization_response(redirect_url) + print("Token saved to %s" % TOKEN_CACHE_FILE) + + return lapi diff --git a/setup.py b/setup.py index 0fe2300..a161c6d 100755 --- a/setup.py +++ b/setup.py @@ -20,5 +20,6 @@ url='https://github.com/bramkragten/python-lyric/', packages=['lyric'], install_requires=['requests>=1.0.0', - 'requests_oauthlib>=0.7.0'] + 'requests_oauthlib>=0.7.0', + 'python-dotenv>=0.9.0'] ) From f418b4a0107b64608b7388b76971871304c4f386 Mon Sep 17 00:00:00 2001 From: Ali Bosworth Date: Sat, 21 Feb 2026 11:24:04 -0800 Subject: [PATCH 2/2] Make test.py easier to read --- lyric/test.py | 56 +++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/lyric/test.py b/lyric/test.py index 4f4d5d4..9ea4ab1 100755 --- a/lyric/test.py +++ b/lyric/test.py @@ -11,39 +11,43 @@ for location in lapi.locations: print(location) - print(location.id) - print(location.name) - print(location.city) - print(location.geoFences) - print(location.geoFences[0]["geoOccupancy"]["withinFence"]) - + print("Location ID:", location.id) + print("Name:", location.name) + print("City:", location.city) + print("GeoFences:", location.geoFences) + if location.geoFences: + print(location.geoFences[0]["geoOccupancy"]["withinFence"]) + + print ("===Users===") for user in location.users: print(user) - print(user.id) - print(user.name) - print(user.firstname) - print(user.lastname) + print("User ID:", user.id) + print("Name:", user.name) + print("First:", user.firstname) + print("Last:", user.lastname) + print ("===Devices===") for device in location.devices: print(device) - print(device.id) - print(device.name) - print(device.deviceType) - print(device.indoorTemperature) - print(device.temperatureSetpoint) - device.temperatureSetpoint = 18 + print("Device ID:", device.id) + print("Name:", device.name) + print("Type:", device.deviceType) + print("Indoor Temp:", device.indoorTemperature) + print("Setpoint:", device.temperatureSetpoint) + print ("===Thermostats===") for thermostat in location.thermostats: print(thermostat) - print(thermostat.id) - print(thermostat.name) - print(thermostat.deviceType) - print(thermostat.outdoorTemperature) - print(thermostat.indoorHumidityStatus) - print(thermostat.temperatureSetpoint) - + print("Thermostat ID:", thermostat.id) + print("Name:", thermostat.name) + print("Type:", thermostat.deviceType) + print("Outdoor Temp:", thermostat.outdoorTemperature) + print("Humidity Status:", thermostat.indoorHumidityStatus) + print("Setpoint:", thermostat.temperatureSetpoint) + + print ("===Water Leak Detectors===") for waterLeakDetector in location.waterLeakDetectors: print(waterLeakDetector) - print(waterLeakDetector.id) - print(waterLeakDetector.name) - print(waterLeakDetector.deviceType) + print("Water Leak Detector ID:", waterLeakDetector.id) + print("Name:", waterLeakDetector.name) + print("Type:", waterLeakDetector.deviceType)