|
1 | 1 | """Unofficial python library for the Blue Riiot Blue Connect API.""" |
2 | 2 |
|
3 | | - |
4 | 3 | import asyncio |
5 | | -import time |
6 | | -import json |
7 | 4 | import logging |
| 5 | +from typing import List |
8 | 6 |
|
9 | | -import aiohttp |
10 | | -from aws_request_signer import AwsRequestSigner |
| 7 | +from .api import BlueConnectApi |
| 8 | +from .models import ( |
| 9 | + BlueDevice, |
| 10 | + SwimmingPool, |
| 11 | + SwimmingPoolFeedMessage, |
| 12 | + SwimmingPoolMeasurement, |
| 13 | + TemperatureUnit, |
| 14 | +) |
11 | 15 |
|
12 | | -AWS_REGION = "eu-west-1" |
13 | | -AWS_SERVICE = "execute-api" |
14 | | -BASE_HEADERS = { |
15 | | - "User-Agent": "BlueConnect/3.2.1", |
16 | | - "Accept-Language": "en-DK;q=1.0, da-DK;q=0.9", |
17 | | - "Accept": "*/*" |
18 | | -} |
19 | | -BASE_URL = "https://api.riiotlabs.com/prod/" |
20 | 16 | LOGGER = logging.getLogger() |
21 | 17 |
|
22 | 18 |
|
23 | | -class BlueConnectApi(): |
24 | | - """Class that holds the connection to the Blue Connect API.""" |
25 | | - _username = None |
26 | | - _password = None |
27 | | - _language = None |
28 | | - _token_info = {} |
29 | | - _loop = None |
30 | | - _http_session = None |
31 | | - _user_info = {} |
32 | | - _swimming_pool_info = {} |
33 | | - _swimming_pool_status = {} |
34 | | - _swimming_pool_feed = {} |
35 | | - _swimming_pool_ph = {} |
36 | | - _swimming_pool_orp = {} |
37 | | - _swimming_pool_temp = {} |
38 | | - _swimming_pool_device = {} |
| 19 | +class BlueConnectSimpleAPI: |
| 20 | + """Class that provides a common structure around the Blue Connect API for a single/main swimming pool.""" |
39 | 21 |
|
40 | 22 | def __init__(self, username: str, password: str, language: str = "en") -> None: |
41 | 23 | """Inititialize the api connection, a valid username and password must be provided.""" |
42 | | - self._username = username |
43 | | - self._password = password |
| 24 | + self._api = BlueConnectApi(username, password) |
44 | 25 | self._language = language |
45 | | - self._loop = asyncio.get_event_loop() |
46 | | - self._http_session = aiohttp.ClientSession( |
47 | | - loop=self._loop, connector=aiohttp.TCPConnector() |
48 | | - ) |
49 | | - |
50 | | - def close(self): |
51 | | - """Close connection to the api.""" |
52 | | - asyncio.create_task(self.close_async()) |
| 26 | + self._temperature_unit = None |
| 27 | + self._pool_info = None |
| 28 | + self._pool_feed_message = None |
| 29 | + self._pool_blue_device = None |
| 30 | + self._pool_measurements = [] |
53 | 31 |
|
54 | | - async def close_async(self): |
| 32 | + def close(self) -> None: |
55 | 33 | """Close connection to the api.""" |
56 | | - await self._http_session.close() |
| 34 | + self._api.close() |
57 | 35 |
|
58 | | - async def fetch_data(self): |
| 36 | + async def fetch_data(self) -> None: |
59 | 37 | """Fetch latest state from API.""" |
60 | | - self._user_info = await self.get_user_info() |
61 | | - self._swimming_pool_info = await self.get_swimming_pool_info(self.main_swimming_pool_id) |
62 | | - self._swimming_pool_status = await self.get_swimming_pool_status(self.main_swimming_pool_id) |
63 | | - self._swimming_pool_feed = await self.get_swimming_pool_feed(self.main_swimming_pool_id) |
64 | | - self._swimming_pool_device = await self.get_swimming_pool_blue_device(self.main_swimming_pool_id) |
65 | | - # get levels from status task infos |
66 | | - for task in self.swimming_pool_status["tasks"]: |
67 | | - if task["task_identifier"].startswith("ORP_"): |
68 | | - self._swimming_pool_orp = json.loads(task["data"]) |
69 | | - if task["task_identifier"].startswith("TEMPERATURE_"): |
70 | | - self._swimming_pool_temp = json.loads(task["data"]) |
71 | | - if task["task_identifier"].startswith("PH_"): |
72 | | - self._swimming_pool_ph = json.loads(task["data"]) |
73 | | - |
74 | | - @property |
75 | | - def main_swimming_pool_id(self): |
76 | | - """Return ID of the main swimming pool.""" |
77 | | - return self.user_preferences.get("main_swimming_pool_id") |
78 | | - |
79 | | - @property |
80 | | - def swimming_pool_name(self): |
81 | | - """Return name of the main swimming pool.""" |
82 | | - return self.swimming_pool_info.get("name") |
83 | | - |
84 | | - @property |
85 | | - def temperature_unit(self): |
86 | | - """Return temperature_unit preference of the logged in user.""" |
87 | | - return self.user_preferences.get("display_temperature_unit") |
88 | | - |
89 | | - @property |
90 | | - def swimming_pool_info(self): |
91 | | - """Return info for the main swimming pool.""" |
92 | | - return self._swimming_pool_info |
93 | | - |
94 | | - @property |
95 | | - def swimming_pool_status(self): |
96 | | - """Return info for the main swimming pool.""" |
97 | | - return self._swimming_pool_status |
98 | | - |
99 | | - @property |
100 | | - def swimming_pool_feed(self): |
101 | | - """Return status feed for the main swimming pool.""" |
102 | | - return self._swimming_pool_feed |
| 38 | + user_info = await self._api.get_user() |
| 39 | + main_pool_id = user_info.user_preferences.main_swimming_pool_id |
| 40 | + self._temperature_unit = user_info.user_preferences.display_temperature_unit |
| 41 | + self._pool_info = await self._api.get_swimming_pool(main_pool_id) |
| 42 | + self._pool_feed_message = (await self._api.get_swimming_pool_feed(main_pool_id, self._language)).current_message |
| 43 | + blue_devices = await self._api.get_swimming_pool_blue_devices(main_pool_id) |
| 44 | + self._pool_blue_device = blue_devices[0] if blue_devices else None |
| 45 | + if self._pool_blue_device: |
| 46 | + self._pool_measurements = (await self._api.get_last_measurements( |
| 47 | + main_pool_id, self._pool_blue_device.serial)).measurements |
103 | 48 |
|
104 | 49 | @property |
105 | | - def swimming_pool_device(self): |
106 | | - """Return Blue Connect device info for the main swimming pool.""" |
107 | | - return self._swimming_pool_device |
| 50 | + def pool(self) -> SwimmingPool: |
| 51 | + """Return full details of the (main) swimming pool.""" |
| 52 | + return self._pool_info |
108 | 53 |
|
109 | 54 | @property |
110 | | - def user_info(self): |
111 | | - """Return info about logged in user.""" |
112 | | - return self._user_info |
| 55 | + def temperature_unit(self) -> TemperatureUnit: |
| 56 | + """Return temperature unit of the temperature measurements.""" |
| 57 | + return self._temperature_unit |
113 | 58 |
|
114 | 59 | @property |
115 | | - def user_preferences(self): |
116 | | - """Return preferences of logged in user.""" |
117 | | - return self._user_info.get("userPreferences", {}) |
| 60 | + def feed_message(self) -> SwimmingPoolFeedMessage: |
| 61 | + """Return the (latest) feed/health message for the (main) swimming pool.""" |
| 62 | + return self._pool_feed_message |
118 | 63 |
|
119 | 64 | @property |
120 | | - def swimming_pool_ph(self): |
121 | | - """Return current PH info of the main swimming pool.""" |
122 | | - return self._swimming_pool_ph |
| 65 | + def blue_device(self) -> BlueDevice: |
| 66 | + """Return Blue Connect device info for the (main) swimming pool.""" |
| 67 | + return self._pool_blue_device |
123 | 68 |
|
124 | 69 | @property |
125 | | - def swimming_pool_orp(self): |
126 | | - """Return current ORP info of the main swimming pool.""" |
127 | | - return self._swimming_pool_orp |
128 | | - |
129 | | - @property |
130 | | - def swimming_pool_temp(self): |
131 | | - """Return current Temperature info of the main swimming pool.""" |
132 | | - return self._swimming_pool_temp |
133 | | - |
134 | | - async def get_user_info(self): |
135 | | - """Retrieve details of logged in user.""" |
136 | | - return await self.__get_data("user") |
137 | | - |
138 | | - async def get_swimming_pool_info(self, swimming_pool_id: str): |
139 | | - """Retrieve details for a specific swimming pool.""" |
140 | | - return await self.__get_data(f"swimming_pool/{swimming_pool_id}") |
141 | | - |
142 | | - async def get_swimming_pool_status(self, swimming_pool_id: str): |
143 | | - """Retrieve status for a specific swimming pool.""" |
144 | | - return await self.__get_data(f"swimming_pool/{swimming_pool_id}/status") |
145 | | - |
146 | | - async def get_swimming_pool_blue_device(self, swimming_pool_id: str): |
147 | | - """Retrieve Blue device info for a specific swimming pool.""" |
148 | | - return await self.__get_data(f"swimming_pool/{swimming_pool_id}/blue") |
149 | | - |
150 | | - async def get_swimming_pool_feed(self, swimming_pool_id: str): |
151 | | - """Retrieve feed for a specific swimming pool, defaults to user's main swimming pool.""" |
152 | | - return await self.__get_data(f"swimming_pool/{swimming_pool_id}/feed?lang={self._language}") |
153 | | - |
154 | | - async def __get_credentials(self): |
155 | | - """Retrieve auth credentials by logging in with username/password.""" |
156 | | - if self._token_info and self._token_info["expires"] > time.time(): |
157 | | - # return cached credentials if still valid |
158 | | - return self._token_info["credentials"] |
159 | | - # perform log-in to get credentials |
160 | | - url = BASE_URL + "user/login" |
161 | | - async with self._http_session.post( |
162 | | - url, json={"email": self._username, "password": self._password} |
163 | | - ) as response: |
164 | | - if response.status != 200: |
165 | | - LOGGER.exception(await response.text()) |
166 | | - return None |
167 | | - result = await response.json() |
168 | | - self._token_info = result |
169 | | - self._token_info["expires"] = time.time() + 3500 |
170 | | - return result["credentials"] |
171 | | - |
172 | | - async def __get_data(self, endpoint, params={}): |
173 | | - """Get data from api.""" |
174 | | - url = BASE_URL + endpoint |
175 | | - headers = BASE_HEADERS.copy() |
176 | | - # sign the request |
177 | | - creds = await self.__get_credentials() |
178 | | - if not creds: |
179 | | - return None |
180 | | - request_signer = AwsRequestSigner( |
181 | | - AWS_REGION, creds["access_key"], creds["secret_key"], AWS_SERVICE |
182 | | - ) |
183 | | - headers.update( |
184 | | - request_signer.sign_with_headers("GET", url, headers) |
185 | | - ) |
186 | | - headers["X-Amz-Security-Token"] = creds["session_token"] |
187 | | - async with self._http_session.get( |
188 | | - url, headers=headers, params=params, verify_ssl=False |
189 | | - ) as response: |
190 | | - assert response.status == 200 |
191 | | - return await response.json() |
| 70 | + def measurements(self) -> List[SwimmingPoolMeasurement]: |
| 71 | + """Return all last/current measurements for the (main) swimming pool.""" |
| 72 | + return self._pool_measurements |
0 commit comments