Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

changes to support guest accounts #42

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
106 changes: 84 additions & 22 deletions pyhiveapi/apyhiveapi/api/hive_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@ class HiveApi:
def __init__(self, hiveSession=None, websession=None, token=None):
"""Hive API initialisation."""
self.cameraBaseUrl = "prod.hcam.bgchtest.info"
self.baseUrl = "https://beekeeper.hivehome.com/1.0"

self.urls = {
"properties": "https://sso.hivehome.com/",
"login": "https://beekeeper.hivehome.com/1.0/cognito/login",
"refresh": "https://beekeeper.hivehome.com/1.0/cognito/refresh-token",
"long_lived": "https://api.prod.bgchprod.info/omnia/accessTokens",
"base": "https://beekeeper-uk.hivehome.com/1.0",
"weather": "https://weather.prod.bgchprod.info/weather",
"holiday_mode": "/holiday-mode",
"all": "/nodes/all?products=true&devices=true&actions=true",
"alarm": "/security-lite?homeId=",
"all": f"{self.baseUrl}/nodes/all",
"alarm": f"{self.baseUrl}/security-lite",
"cameraImages": f"https://event-history-service.{self.cameraBaseUrl}/v1/events/cameras?latest=true&cameraId={{0}}",
"cameraRecordings": f"https://event-history-service.{self.cameraBaseUrl}/v1/playlist/cameras/{{0}}/events/{{1}}.m3u8",
"devices": "/devices",
"products": "/products",
"actions": "/actions",
"devices": f"{self.baseUrl}/devices",
"products": f"{self.baseUrl}/products",
"actions": f"{self.baseUrl}/actions",
"nodes": "/nodes/{0}/{1}",
}
self.timeout = 10
Expand All @@ -40,7 +41,28 @@ def __init__(self, hiveSession=None, websession=None, token=None):
self.session = hiveSession
self.token = token

def request(self, type, url, jsc=None, camera=False):
self.homeID = None
if self.session is not None:
self.homeID = self.session.config.homeID

def getParams(self, products=False, devices=False, actions=False):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some testing of the the sync code and the api didn't seem to work with the params when they where booleans. When I changed them to be lower case f and in quotes the api then worked as expected.

Suggested change
def getParams(self, products=False, devices=False, actions=False):
def getParams(self, products='false', devices='false', actions='false\):

The other option would be to the leave the params as booleans and update the dictionary to be as per below converting them to string and lowercasing them.

params = {
            "products": str(products).lower(),
            "devices": str(devices).lower(),
            "actions": str(actions).lower(),
        }

"""Get parameters."""
params = {
"products": products,
"devices": devices,
"actions": actions,
}
if self.homeID is not None:
params.update({"homeId": self.homeID})
return params
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need and else if here if someone requests to the send the homeId but its actually set to None. to do this you will also need to create a new exception in the Hive exceptions file and import it into this module

Suggested change
return params
elif sendhomeID and self.homeID is None:
raise HiveInvalidHomeId
return params

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would change the behaviour of the sync api to require a homeId in the case of one home in hive. I've tried to make sure my change only effects cases where a homeID is actually set.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about it, there's nowhere we don't want to send homeID if it isn't set; except getHomes, which doesn't mind it being set. So we don't need an arg for it.

Copy link
Contributor

@KJonline KJonline Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if your the account owner with a single home and you don’t need to send a homeID.

is your thinking to send one anyway?


def getHomeIdParam(self):
"""Get homeId parameter if set."""
if self.homeID is not None:
return {"homeId": self.homeID}
return {}

def request(self, type, url, jsc=None, camera=False, params={}):
"""Make API request."""
if self.session is not None:
if camera:
Expand Down Expand Up @@ -73,11 +95,19 @@ def request(self, type, url, jsc=None, camera=False):

if type == "GET":
return requests.get(
url=url, headers=self.headers, data=jsc, timeout=self.timeout
url=url,
headers=self.headers,
data=jsc,
timeout=self.timeout,
params=params,
)
if type == "POST":
return requests.post(
url=url, headers=self.headers, data=jsc, timeout=self.timeout
url=url,
headers=self.headers,
data=jsc,
timeout=self.timeout,
params=params,
)

def refreshTokens(self, tokens={}):
Expand All @@ -97,8 +127,8 @@ def refreshTokens(self, tokens={}):
data = json.loads(info.text)
if "token" in data and self.session:
self.session.updateTokens(data)
self.urls.update({"base": data["platform"]["endpoint"]})
self.urls.update({"camera": data["platform"]["cameraPlatform"]})
self.baseUrl = info["platform"]["endpoint"]
self.cameraBaseUrl = info["platform"]["cameraPlatform"]
self.json_return.update({"original": info.status_code})
self.json_return.update({"parsed": info.json()})
except (OSError, RuntimeError, ZeroDivisionError):
Expand Down Expand Up @@ -132,23 +162,47 @@ def getLoginInfo(self):
def getAll(self):
"""Build and query all endpoint."""
json_return = {}
url = self.urls["base"] + self.urls["all"]
url = self.urls["all"]
params = self.getParams(
products=True, devices=True, actions=True
)
try:
info = self.request("GET", url)
info = self.request("GET", url, params=params)
json_return.update({"original": info.status_code})
json_return.update({"parsed": info.json()})
except (OSError, RuntimeError, ZeroDivisionError):
self.error()

return json_return

def getHomes(self):
"""Build and query all endpoint."""
json_return = {}
url = self.urls["all"]
params = self.getParams()
try:
info = self.request("GET", url, params=params)
all = info.json()
json_return.update({"original": info.status_code})
json_return.update({"parsed": all["homes"]})
except (OSError, RuntimeError, ZeroDivisionError):
self.error()

return json_return

def getAlarm(self, homeID=None):
"""Build and query alarm endpoint."""
if self.session is not None:
homeID = self.session.config.homeID
url = self.urls["base"] + self.urls["alarm"] + homeID
url = self.urls["alarm"]
params = {}
if homeID:
params = {"homeID": homeID}
if self.homeID:
# ignore homeID if set in session
params = self.getHomeIdParam()
try:
info = self.request("GET", url)
info = self.request("GET", url, params=params)
self.json_return.update({"original": info.status_code})
self.json_return.update({"parsed": info.json()})
except (OSError, RuntimeError, ZeroDivisionError):
Expand Down Expand Up @@ -186,9 +240,10 @@ def getCameraRecording(self, device=None, eventId=None):

def getDevices(self):
"""Call the get devices endpoint."""
url = self.urls["base"] + self.urls["devices"]
url = self.urls["devices"]
params = self.getParams(devices=True)
try:
response = self.request("GET", url)
response = self.request("GET", url, params=params)
self.json_return.update({"original": response.status_code})
self.json_return.update({"parsed": response.json()})
except (OSError, RuntimeError, ZeroDivisionError):
Expand All @@ -198,9 +253,10 @@ def getDevices(self):

def getProducts(self):
"""Call the get products endpoint."""
url = self.urls["base"] + self.urls["products"]
url = self.urls["products"]
params = self.getParams(products=True)
try:
response = self.request("GET", url)
response = self.request("GET", url, params=params)
self.json_return.update({"original": response.status_code})
self.json_return.update({"parsed": response.json()})
except (OSError, RuntimeError, ZeroDivisionError):
Expand All @@ -210,11 +266,13 @@ def getProducts(self):

def getActions(self):
"""Call the get actions endpoint."""
url = self.urls["base"] + self.urls["actions"]
url = self.urls["all"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come this has been updated to point to all? getDevices & getProducts still points to the individual endpoints. if we can get all data from the all endpoint maybe we should remove the individual endpoints. What do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea, I did this in July. Sorry.

params = self.getHomeIdParam()
try:
response = self.request("GET", url)
response = self.request("GET", url, params=params)
all = response.json()
self.json_return.update({"original": response.status_code})
self.json_return.update({"parsed": response.json()})
self.json_return.update({"parsed": all["actions"]})
except (OSError, RuntimeError, ZeroDivisionError):
self.error()

Expand Down Expand Up @@ -256,6 +314,10 @@ def getWeather(self, weather_url):

return self.json_return

def setHome(self, homeID):
"""Set the homeID."""
self.homeID = homeID

def setState(self, n_type, n_id, **kwargs):
"""Set the state of a Device."""
jsc = (
Expand Down
78 changes: 64 additions & 14 deletions pyhiveapi/apyhiveapi/api/hive_async_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ def __init__(self, hiveSession=None, websession: Optional[ClientSession] = None)
"login": f"{self.baseUrl}/cognito/login",
"refresh": f"{self.baseUrl}/cognito/refresh-token",
"holiday_mode": f"{self.baseUrl}/holiday-mode",
"all": f"{self.baseUrl}/nodes/all?products=true&devices=true&actions=true",
"alarm": f"{self.baseUrl}/security-lite?homeId=",
"all": f"{self.baseUrl}/nodes/all",
"homes": f"{self.baseUrl}/nodes/all",
"alarm": f"{self.baseUrl}/security-lite",
"cameraImages": f"https://event-history-service.{self.cameraBaseUrl}/v1/events/cameras?latest=true&cameraId={{0}}",
"cameraRecordings": f"https://event-history-service.{self.cameraBaseUrl}/v1/playlist/cameras/{{0}}/events/{{1}}.m3u8",
"devices": f"{self.baseUrl}/devices",
Expand All @@ -44,10 +45,30 @@ def __init__(self, hiveSession=None, websession: Optional[ClientSession] = None)
"parsed": "No response to Hive API request",
}
self.session = hiveSession
self.homeID = None
if self.session is not None:
self.homeID = self.session.config.homeID
self.websession = ClientSession() if websession is None else websession

def getParams(self, products=False, devices=False, actions=False):
"""Get parameters."""
params = {
"products": 'true' if products else 'false',
"devices": 'true' if devices else 'false',
"actions": 'true' if actions else 'false',
}
if self.homeID:
Copy link
Contributor

@KJonline KJonline Oct 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seemed to have catered for it here but not in the sync api

params.update({"homeId": self.homeID})
return params

def getHomeIdParam(self):
"""Get homeId parameter if set."""
if self.homeID is not None:
return {"homeId": self.homeID}
return {}

async def request(
self, method: str, url: str, camera: bool = False, **kwargs
self, method: str, url: str, camera: bool = False, params={}, **kwargs
) -> ClientResponse:
"""Make a request."""
data = kwargs.get("data", None)
Expand All @@ -73,7 +94,7 @@ async def request(
raise NoApiToken

async with self.websession.request(
method, url, headers=headers, data=data
method, url, headers=headers, data=data, params=params
) as resp:
await resp.text()
if operator.contains(str(resp.status), "20"):
Expand Down Expand Up @@ -142,21 +163,40 @@ async def getAll(self):
"""Build and query all endpoint."""
json_return = {}
url = self.urls["all"]
params = self.getParams(
products=True, devices=True, actions=True
)
try:
resp = await self.request("get", url)
resp = await self.request("get", url, params=params)
json_return.update({"original": resp.status})
json_return.update({"parsed": await resp.json(content_type=None)})
except (OSError, RuntimeError, ZeroDivisionError):
await self.error()

return json_return

async def getHomes(self):
"""Build and query all endpoint for homes."""
json_return = {}
url = self.urls["all"]
params = self.getParams()
try:
resp = await self.request("get", url, params=params)
all = await resp.json(content_type=None)
json_return.update({"original": resp.status})
json_return.update({"parsed": all["homes"]})
except (OSError, RuntimeError, ZeroDivisionError):
await self.error()

return json_return

async def getAlarm(self):
"""Build and query alarm endpoint."""
json_return = {}
url = self.urls["alarm"] + self.session.config.homeID
url = self.urls["alarm"]
params = self.getHomeIdParam()
try:
resp = await self.request("get", url)
resp = await self.request("get", url, params=params)
json_return.update({"original": resp.status})
json_return.update({"parsed": await resp.json(content_type=None)})
except (OSError, RuntimeError, ZeroDivisionError):
Expand Down Expand Up @@ -197,8 +237,9 @@ async def getDevices(self):
"""Call the get devices endpoint."""
json_return = {}
url = self.urls["devices"]
params = self.getParams(devices=True)
try:
resp = await self.request("get", url)
resp = await self.request("get", url, params=params)
json_return.update({"original": resp.status})
json_return.update({"parsed": await resp.json(content_type=None)})
except (OSError, RuntimeError, ZeroDivisionError):
Expand All @@ -210,8 +251,9 @@ async def getProducts(self):
"""Call the get products endpoint."""
json_return = {}
url = self.urls["products"]
params = self.getParams(products=True)
try:
resp = await self.request("get", url)
resp = await self.request("get", url, params=params)
json_return.update({"original": resp.status})
json_return.update({"parsed": await resp.json(content_type=None)})
except (OSError, RuntimeError, ZeroDivisionError):
Expand All @@ -222,11 +264,13 @@ async def getProducts(self):
async def getActions(self):
"""Call the get actions endpoint."""
json_return = {}
url = self.urls["actions"]
url = self.urls["all"]
Copy link
Contributor

@KJonline KJonline Oct 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here as the sync one

params = self.getParams(actions=True)
try:
resp = await self.request("get", url)
resp = await self.request("get", url, params=params)
all = await resp.json(content_type=None)
json_return.update({"original": resp.status})
json_return.update({"parsed": await resp.json(content_type=None)})
json_return.update({"parsed": all["actions"]})
except (OSError, RuntimeError, ZeroDivisionError):
await self.error()

Expand Down Expand Up @@ -270,6 +314,10 @@ async def getWeather(self, weather_url):

return json_return

def setHome(self, homeID):
"""Set the home ID."""
self.homeID = homeID

async def setState(self, n_type, n_id, **kwargs):
"""Set the state of a Device."""
json_return = {}
Expand Down Expand Up @@ -306,10 +354,12 @@ async def setAlarm(self, **kwargs):
+ "}"
)

url = f"{self.urls['alarm']}{self.session.config.homeID}"
url = self.urls["alarm"]
params = self.getHomeIdParam()

try:
await self.isFileBeingUsed()
resp = await self.request("post", url, data=jsc)
resp = await self.request("post", url, data=jsc, params=params)
json_return["original"] = resp.status
json_return["parsed"] = await resp.json(content_type=None)
except (FileInUse, OSError, RuntimeError, ConnectionError) as e:
Expand Down
2 changes: 1 addition & 1 deletion pyhiveapi/apyhiveapi/api/hive_auth_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ async def refresh_token(self, token):
if self.client is None:
await self.async_init()
result = None
auth_params = ({"REFRESH_TOKEN": token},)
auth_params = {"REFRESH_TOKEN": token}
if self.device_key is not None:
auth_params = {
"REFRESH_TOKEN": token,
Expand Down
Loading