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.
+
+
+2. Login using your Hilo Credentials
+
+
+4. Once you get to this page, select the URL and copy it to clipboard
+
+
+
+5. Go back to get_tokens.py and press enter, you'll get all 3 tokens:
+
+
+
+
+
+
# 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
2. Login using your Hilo Credentials
+
-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
-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: