From ce5ca76e5d45e9b2499deccf5cf0b356a5e0e309 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:54:11 -0500 Subject: [PATCH 1/5] Create get_tokens.py Ajout d'un script pour demander un token d'authentification avec l'URL de callback de HA. --- doc/get_tokens.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 doc/get_tokens.py diff --git a/doc/get_tokens.py b/doc/get_tokens.py new file mode 100644 index 0000000..df47013 --- /dev/null +++ b/doc/get_tokens.py @@ -0,0 +1,153 @@ +import base64 +import hashlib +import json +import os +import re +import subprocess +from urllib.parse import urlencode, urlunparse, urlparse, parse_qs +import requests +import sys + +CLIENT_ID = "1ca9f585-4a55-4085-8e30-9746a65fa561" +AUTH_HOST = "connexion.hiloenergie.com" +AUTH_PATH = "/HiloDirectoryB2C.onmicrosoft.com/B2C_1A_SIGN_IN/oauth2/v2.0/authorize" +TOKEN_PATH = "/HiloDirectoryB2C.onmicrosoft.com/B2C_1A_SIGN_IN/oauth2/v2.0/token" +SCOPE = "openid https://HiloDirectoryB2C.onmicrosoft.com/hiloapis/user_impersonation offline_access" +REDIRECT_URL = "https://my.home-assistant.io/redirect/oauth" +API_HOST = "api.hiloenergie.com" +DEVICEHUB_PATH = "/DeviceHub/negotiate" +CHALLENGEHUB_PATH = "/ChallengeHub/negotiate" + + +def create_url(base_url, path, params): + query_string = urlencode(params) + url = urlunparse(("https", base_url, path, "", query_string, "")) + return url + + +def get_verifier_and_challenge(): + code_verifier = base64.urlsafe_b64encode(os.urandom(40)).decode('utf-8') + code_verifier = re.sub('[^a-zA-Z0-9]+', '', code_verifier) + code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest() + code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8') + code_challenge = code_challenge.replace('=', '') + return code_verifier, code_challenge + + +def get_state(): + return hashlib.sha256(os.urandom(40)).hexdigest() + + +def build_auth_url(state, code_challenge): + params = { + "response_type": "code", + "client_id": CLIENT_ID, + "scope": SCOPE, + "redirect_uri": REDIRECT_URL, + "state": state, + "code_challenge": code_challenge, + "code_challenge_method": "S256", + } + return create_url(AUTH_HOST, AUTH_PATH, params) + + +def get_code(auth_url): + print(f"Go there and authenticate:\n{auth_url}\n\n") + input("After completing authentication, copy the redirected URL from your browser and press Enter...") + + try: + # Read URL from clipboard + redirect_w_token = subprocess.run( + ['pbpaste'], capture_output=True, text=True, check=True + ).stdout.strip() + except Exception as e: + print("Failed to read clipboard:", e) + redirect_w_token = input("Fallback: Paste the redirected URL here:\n") + + print("Captured URL:", redirect_w_token[:100], "...") # show first 100 chars for sanity check + + parsed_redirect = urlparse(redirect_w_token) + query = parse_qs(parsed_redirect.query) + + if 'code' not in query: + raise ValueError("No 'code' parameter found in the URL. Make sure you copied the redirected URL.") + + return query['code'][0] + + +def exchange_code_for_token(code, code_verifier): + url = create_url(AUTH_HOST, TOKEN_PATH, '') + resp = requests.get( + url=url, + params={ + "grant_type": "authorization_code", + "client_id": CLIENT_ID, + "redirect_uri": REDIRECT_URL, + "code": code, + "code_verifier": code_verifier, + }, + allow_redirects=False + ) + + if resp.status_code != 200: + print("Failed to exchange code for token:", resp.status_code, resp.text) + sys.exit(1) + + return resp.json() + + +def exchange_rt_for_token(refresh_token): + url = create_url(AUTH_HOST, TOKEN_PATH, '') + resp = requests.get( + url=url, + params={ + "grant_type": "refresh_token", + "refresh_token": refresh_token + }, + allow_redirects=False + ) + + if resp.status_code != 200: + print("Failed to refresh token:", resp.status_code, resp.text) + sys.exit(1) + + return resp.json() + + +def get_challengehub_ws(token): + url = create_url(API_HOST, CHALLENGEHUB_PATH, '') + resp = requests.post(url=url, headers={'Authorization': f"Bearer {token}"}) + print(resp.status_code) + return resp.json() + + +def get_devicehub_ws(token): + url = create_url(API_HOST, DEVICEHUB_PATH, '') + resp = requests.post(url=url, headers={'Authorization': f"Bearer {token}"}) + print(resp.status_code) + return resp.json() + + +if __name__ == "__main__": + token = None + + if len(sys.argv) == 2: + print("Using provided refresh token") + token = exchange_rt_for_token(sys.argv[1]) + else: + code_verifier, challenge = get_verifier_and_challenge() + state = get_state() + url = build_auth_url(state, challenge) + code = get_code(url) + token = exchange_code_for_token(code, code_verifier) + + if 'access_token' not in token: + print("No access token received. Full response:") + print(json.dumps(token, indent=True)) + sys.exit(1) + + print(f"Got Token:\n{json.dumps(token, indent=True)}") + challengehub_ws_info = get_challengehub_ws(token['access_token']) + print(f"ChallengeHub: {json.dumps(challengehub_ws_info, indent=True)}") + devicehub_ws_info = get_devicehub_ws(token['access_token']) + print(f"DeviceHub: {json.dumps(devicehub_ws_info, indent=True)}") From ca38246f5dc12d03cce07ad339473a69e4a3a3d7 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:55:49 -0500 Subject: [PATCH 2/5] Archiving Move to archive --- doc/{ => Old JSON (Pre 2025-26)}/ChallengeAdded.json | 0 .../ChallengeConsumptionUpdatedValuesReceived.json | 0 .../ChallengeDetailsInitialValuesReceived.json | 0 .../ChallengeListInitialValuesReceived.json | 0 .../ChallengeListUpdatedValuesReceived.json | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename doc/{ => Old JSON (Pre 2025-26)}/ChallengeAdded.json (100%) rename doc/{ => Old JSON (Pre 2025-26)}/ChallengeConsumptionUpdatedValuesReceived.json (100%) rename doc/{ => Old JSON (Pre 2025-26)}/ChallengeDetailsInitialValuesReceived.json (100%) rename doc/{ => Old JSON (Pre 2025-26)}/ChallengeListInitialValuesReceived.json (100%) rename doc/{ => Old JSON (Pre 2025-26)}/ChallengeListUpdatedValuesReceived.json (100%) diff --git a/doc/ChallengeAdded.json b/doc/Old JSON (Pre 2025-26)/ChallengeAdded.json similarity index 100% rename from doc/ChallengeAdded.json rename to doc/Old JSON (Pre 2025-26)/ChallengeAdded.json diff --git a/doc/ChallengeConsumptionUpdatedValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeConsumptionUpdatedValuesReceived.json similarity index 100% rename from doc/ChallengeConsumptionUpdatedValuesReceived.json rename to doc/Old JSON (Pre 2025-26)/ChallengeConsumptionUpdatedValuesReceived.json diff --git a/doc/ChallengeDetailsInitialValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeDetailsInitialValuesReceived.json similarity index 100% rename from doc/ChallengeDetailsInitialValuesReceived.json rename to doc/Old JSON (Pre 2025-26)/ChallengeDetailsInitialValuesReceived.json diff --git a/doc/ChallengeListInitialValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeListInitialValuesReceived.json similarity index 100% rename from doc/ChallengeListInitialValuesReceived.json rename to doc/Old JSON (Pre 2025-26)/ChallengeListInitialValuesReceived.json diff --git a/doc/ChallengeListUpdatedValuesReceived.json b/doc/Old JSON (Pre 2025-26)/ChallengeListUpdatedValuesReceived.json similarity index 100% rename from doc/ChallengeListUpdatedValuesReceived.json rename to doc/Old JSON (Pre 2025-26)/ChallengeListUpdatedValuesReceived.json From 0ebbe357c809cfd917f717a7285c07cdd51603b7 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:44:11 -0500 Subject: [PATCH 3/5] Update Readme with get_tokens.py usage details Started adding instructions for using get_tokens.py and Postman. --- doc/Readme.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 doc/Readme.md diff --git a/doc/Readme.md b/doc/Readme.md new file mode 100644 index 0000000..20fbcd2 --- /dev/null +++ b/doc/Readme.md @@ -0,0 +1,14 @@ +# Using get_tokens.py +This python script will let you use it to log in to your Hilo account. + +From there, you will be asked to copy the full HA redirect(callback) URL and go back to your python script and press enter. + +Negotiation will take place and you will receive three distinct tokens: +- Your access token +- Your devicehub token +- Your challengehub token + +Take care not to share your tokens as they are not encrypted and contain personally identifiable information. + +# Using Postman to send payloads to devicehub or challengehub +## Prerequisite: you need to have your tokens on hand, they are relatively short lived so once you have them, get to connecting! From ae0e26fb55aeb8e9e72679defd1ea847d321d1b6 Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:52:43 -0500 Subject: [PATCH 4/5] Update Readme with token retrieval instructions Added detailed steps for obtaining tokens using get_tokens.py. --- doc/Readme.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/doc/Readme.md b/doc/Readme.md index 20fbcd2..36965e2 100644 --- a/doc/Readme.md +++ b/doc/Readme.md @@ -10,5 +10,24 @@ Negotiation will take place and you will receive three distinct tokens: Take care not to share your tokens as they are not encrypted and contain personally identifiable information. +1. Run get_tokens.py, you will be provided with a link. Either click it or copy it in your favourite browser. +image + +2. Login using your Hilo Credentials + image + +4. Once you get to this page, select the URL and copy it to clipboard +image +image + +5. Go back to get_tokens.py and press enter, you'll get all 3 tokens: +image +image +image + + + # Using Postman to send payloads to devicehub or challengehub ## Prerequisite: you need to have your tokens on hand, they are relatively short lived so once you have them, get to connecting! + + From 8b6a3b0516961f371fbe0845b8ed3ea013e5723e Mon Sep 17 00:00:00 2001 From: "Ian C." <108159253+ic-dev21@users.noreply.github.com> Date: Thu, 11 Dec 2025 19:10:27 -0500 Subject: [PATCH 5/5] Fix numbering in Readme steps --- doc/Readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/Readme.md b/doc/Readme.md index 36965e2..58bd22d 100644 --- a/doc/Readme.md +++ b/doc/Readme.md @@ -14,13 +14,14 @@ Take care not to share your tokens as they are not encrypted and contain persona image 2. Login using your Hilo Credentials + image -4. Once you get to this page, select the URL and copy it to clipboard +3. Once you get to this page, select the URL and copy it to clipboard image image -5. Go back to get_tokens.py and press enter, you'll get all 3 tokens: +4. Go back to get_tokens.py and press enter, you'll get all 3 tokens: image image image