blueair-api 1.36.0__py3-none-any.whl → 1.36.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
blueair_api/const.py CHANGED
@@ -11,17 +11,29 @@ API_KEY = "eyJhbGciOiJIUzI1NiJ9.eyJncmFudGVlIjoiYmx1ZWFpciIsImlhdCI6MTQ1MzEyNTYz
11
11
 
12
12
  AWS_APIKEYS = {
13
13
  "us": {
14
- "gigyaRegion": "us1",
14
+ "gigyaRegion": "accounts.us1.gigya.com",
15
15
  "restApiId": "on1keymlmh",
16
- "awsRegion": "us-east-2",
16
+ "awsRegion": "us-east-2.amazonaws.com",
17
17
  "apiKey": "3_-xUbbrIY8QCbHDWQs1tLXE-CZBQ50SGElcOY5hF1euE11wCoIlNbjMGAFQ6UwhMY",
18
18
  },
19
19
  "eu": {
20
- "gigyaRegion": "eu1",
20
+ "gigyaRegion": "accounts.eu1.gigya.com",
21
21
  "restApiId": "hkgmr8v960",
22
- "awsRegion": "eu-west-1",
22
+ "awsRegion": "eu-west-1.amazonaws.com",
23
23
  "apiKey": "3_qRseYzrUJl1VyxvSJANalu_kNgQ83swB1B9uzgms58--5w1ClVNmrFdsDnWVQQCl",
24
24
  },
25
+ "cn": {
26
+ "gigyaRegion": "accounts.cn1.sapcdm.cn",
27
+ "restApiId": "ftbkyp79si",
28
+ "awsRegion": "cn-north-1.amazonaws.com.cn",
29
+ "apiKey": "3_h3UEfJnA-zDpFPR9L4412HO7Mz2VVeN4wprbWYafPN1gX0kSnLcZ9VSfFi7bEIIU",
30
+ },
31
+ "au": {
32
+ "gigyaRegion": "accounts.au1.gigya.com",
33
+ "restApiId": "hkgmr8v960",
34
+ "awsRegion": "eu-west-1.amazonaws.com",
35
+ "apiKey": "3_Z2N0mIFC6j2fx1z2sq76R3pwkCMaMX2y9btPb0_PgI_3wfjSJoofFnBbxbtuQksN",
36
+ },
25
37
  }
26
38
 
27
39
 
blueair_api/device_aws.py CHANGED
@@ -32,6 +32,7 @@ class DeviceAws(CallbacksMixin):
32
32
 
33
33
  api: HttpAwsBlueair = field(repr=False)
34
34
  raw_info : dict[str, Any] = field(repr=False, init=False)
35
+ raw_sensors : dict[str, Any] = field(repr=False, init=False)
35
36
 
36
37
  uuid : str | None = None
37
38
  name : str | None = None
@@ -81,16 +82,18 @@ class DeviceAws(CallbacksMixin):
81
82
  async def refresh(self):
82
83
  _LOGGER.debug(f"refreshing blueair device aws: {self}")
83
84
  self.raw_info = await self.api.device_info(self.name_api, self.uuid)
85
+ self.raw_sensors = await self.api.device_sensors(self.name_api, self.uuid)
84
86
  _LOGGER.debug(dumps(self.raw_info, indent=2))
87
+ if self.raw_sensors is not None:
88
+ _LOGGER.debug(dumps(self.raw_sensors, indent=2))
85
89
 
86
- # ir.parse_json(ir.Attribute, ir.query_json(info, "configuration.da"))
87
90
  ds = ir.parse_json(ir.Sensor, ir.query_json(self.raw_info, "configuration.ds"))
88
91
  dc = ir.parse_json(ir.Control, ir.query_json(self.raw_info, "configuration.dc"))
89
92
 
90
- sensor_data = ir.SensorPack(self.raw_info["sensordata"]).to_latest_value()
93
+ sensor_data = ir.SensorHistory(self.raw_sensors).to_latest()
91
94
 
92
95
  def sensor_data_safe_get(key):
93
- return sensor_data.get(key) if key in ds else NotImplemented
96
+ return sensor_data.values.get(key) if key in ds else NotImplemented
94
97
 
95
98
  self.pm1 = sensor_data_safe_get("pm1")
96
99
  self.pm2_5 = sensor_data_safe_get("pm2_5")
@@ -142,7 +145,10 @@ class DeviceAws(CallbacksMixin):
142
145
  self.cool_sub_mode = states_safe_get("coolsubmode")
143
146
  self.cool_fan_speed = states_safe_get("coolfs")
144
147
  self.ap_sub_mode = states_safe_get("apsubmode")
145
- self.fan_speed_0 = states_safe_get("fsp0")
148
+ if states_safe_get("fsp0") is NotImplemented:
149
+ self.fan_speed_0 = sensor_data_safe_get("fsp0")
150
+ else:
151
+ self.fan_speed_0 = states_safe_get("fsp0")
146
152
  self.temperature_unit = states_safe_get("tu")
147
153
 
148
154
  self.publish_updates()
@@ -3,6 +3,7 @@ import functools
3
3
  from logging import getLogger
4
4
  from typing import Any
5
5
  from aiohttp import ClientSession, ClientResponse, FormData
6
+ from datetime import datetime, timedelta
6
7
 
7
8
  from .const import AWS_APIKEYS
8
9
  from .util_http import request_with_logging
@@ -92,9 +93,9 @@ class HttpAwsBlueair:
92
93
  @request_with_errors
93
94
  @request_with_logging
94
95
  async def _get_request_with_logging_and_errors_raised(
95
- self, url: str, headers: dict | None = None
96
+ self, url: str, headers: dict | None = None, params: dict | None = None
96
97
  ) -> ClientResponse:
97
- return await self.api_session.get(url=url, headers=headers)
98
+ return await self.api_session.get(url=url, headers=headers, params=params)
98
99
 
99
100
  @request_with_errors
100
101
  @request_with_logging
@@ -111,7 +112,7 @@ class HttpAwsBlueair:
111
112
 
112
113
  async def refresh_session(self) -> None:
113
114
  _LOGGER.debug("refresh_session")
114
- url = f"https://accounts.{AWS_APIKEYS[self.region]['gigyaRegion']}.gigya.com/accounts.login"
115
+ url = f"https://{AWS_APIKEYS[self.region]['gigyaRegion']}/accounts.login"
115
116
  form_data = FormData()
116
117
  form_data.add_field("apikey", AWS_APIKEYS[self.region]["apiKey"])
117
118
  form_data.add_field("loginID", self.username)
@@ -130,7 +131,7 @@ class HttpAwsBlueair:
130
131
  _LOGGER.debug("refresh_jwt")
131
132
  if self.session_token is None or self.session_secret is None:
132
133
  await self.refresh_session()
133
- url = f"https://accounts.{AWS_APIKEYS[self.region]['gigyaRegion']}.gigya.com/accounts.getJWT"
134
+ url = f"https://{AWS_APIKEYS[self.region]['gigyaRegion']}/accounts.getJWT"
134
135
  form_data = FormData()
135
136
  form_data.add_field("oauth_token", self.session_token)
136
137
  form_data.add_field("secret", self.session_secret)
@@ -147,7 +148,7 @@ class HttpAwsBlueair:
147
148
  _LOGGER.debug("refresh_access_token")
148
149
  if self.jwt is None:
149
150
  await self.refresh_jwt()
150
- url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}.amazonaws.com/prod/c/login"
151
+ url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}/prod/c/login"
151
152
  headers = {"idtoken": self.jwt}
152
153
  response: ClientResponse = (
153
154
  await self._post_request_with_logging_and_errors_raised(
@@ -167,7 +168,7 @@ class HttpAwsBlueair:
167
168
  @request_with_active_session
168
169
  async def devices(self) -> dict[str, Any]:
169
170
  _LOGGER.debug("devices")
170
- url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}.amazonaws.com/prod/c/registered-devices"
171
+ url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}/prod/c/registered-devices"
171
172
  headers = {
172
173
  "Authorization": f"Bearer {await self.get_access_token()}",
173
174
  }
@@ -179,10 +180,30 @@ class HttpAwsBlueair:
179
180
  response_json = await response.json()
180
181
  return response_json["devices"]
181
182
 
183
+ @request_with_active_session
184
+ async def device_sensors(self, device_name, device_uuid, duration: timedelta = timedelta(hours=10)):
185
+ url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}/prod/c/{device_name}/r/telemetry/5m/historical"
186
+ headers = {
187
+ "Authorization": f"Bearer {await self.get_access_token()}",
188
+ }
189
+ params = {
190
+ "did": device_uuid,
191
+ "from": int((datetime.now()-duration).timestamp()),
192
+ "to": int(datetime.now().timestamp()),
193
+ "s": ["pm1", "pm2_5", "pm10", "tVOC", "hcho", "h", "t", "fsp0"]
194
+ }
195
+ response: ClientResponse = (
196
+ await self._get_request_with_logging_and_errors_raised(
197
+ url=url, headers=headers, params=params
198
+ )
199
+ )
200
+ response_json = await response.json()
201
+ return response_json
202
+
182
203
  @request_with_active_session
183
204
  async def device_info(self, device_name, device_uuid) -> dict[str, Any]:
184
205
  _LOGGER.debug("device_info")
185
- url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}.amazonaws.com/prod/c/{device_name}/r/initial"
206
+ url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}/prod/c/{device_name}/r/initial"
186
207
  headers = {
187
208
  "Authorization": f"Bearer {await self.get_access_token()}",
188
209
  }
@@ -212,7 +233,7 @@ class HttpAwsBlueair:
212
233
  self, device_uuid, service_name, action_verb, action_value
213
234
  ) -> bool:
214
235
  _LOGGER.debug("set_device_info")
215
- url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}.amazonaws.com/prod/c/{device_uuid}/a/{service_name}"
236
+ url = f"https://{AWS_APIKEYS[self.region]['restApiId']}.execute-api.{AWS_APIKEYS[self.region]['awsRegion']}/prod/c/{device_uuid}/a/{service_name}"
216
237
  headers = {
217
238
  "Authorization": f"Bearer {await self.get_access_token()}",
218
239
  }
@@ -1,5 +1,5 @@
1
1
  import typing
2
- from typing import Any, TypeVar
2
+ from typing import Any
3
3
  from collections.abc import Iterable
4
4
  import dataclasses
5
5
  import base64
@@ -85,7 +85,7 @@ class Attribute:
85
85
  class Sensor:
86
86
  """DeviceSensor(ds); seems to define a sensor.
87
87
 
88
- We never directly access these objects. Thos this defines
88
+ We never directly access these objects. Though this defines
89
89
  the schema for 'h', 't', 'pm10' etc that gets returned in
90
90
  the sensor_data senml SensorPack.
91
91
  """
@@ -115,6 +115,36 @@ class Control:
115
115
  d: str | None = None # device info json path
116
116
 
117
117
 
118
+ @dataclasses.dataclass
119
+ class SensorRecord:
120
+ values: dict[str, int]
121
+ timestamp: float | None
122
+
123
+
124
+ class SensorHistory(list[SensorRecord]):
125
+ def __init__(self, response):
126
+ sensors = response[0]["sensors"]
127
+ datapoints = response[0]["datapoints"]
128
+ sensor_records = []
129
+ for datapoint in datapoints:
130
+ values = {}
131
+ for idx, sensor in enumerate(sensors):
132
+ if datapoint[idx+1] is not None:
133
+ values[sensor] = int(datapoint[idx+1])
134
+ sensor_records.append(SensorRecord(timestamp=int(datapoint[0]), values=values))
135
+ super().__init__(sensor_records)
136
+
137
+ def to_latest(self) -> SensorRecord:
138
+ def key(e):
139
+ return e.timestamp
140
+ self.sort(key=key, reverse=True)
141
+ if len(self) > 0:
142
+ return self[0]
143
+ else:
144
+ return SensorRecord(values={}, timestamp=0)
145
+
146
+
147
+
118
148
  ########################
119
149
  # SenML RFC8428
120
150
 
blueair_api/stub.py CHANGED
@@ -8,7 +8,7 @@ import sys
8
8
 
9
9
  path_root = Path(__file__).parents[2]
10
10
  sys.path.append(str(path_root))
11
- from src.blueair_api import get_devices, get_aws_devices, DeviceAws
11
+ from src.blueair_api import get_devices, get_aws_devices
12
12
 
13
13
 
14
14
  logger = logging.getLogger("src.blueair_api")
blueair_api/util_http.py CHANGED
@@ -12,6 +12,9 @@ def request_with_logging(func):
12
12
  headers = kwargs.get("headers")
13
13
  if headers is not None:
14
14
  request_message = request_message + f"headers: {headers}"
15
+ params = kwargs.get("params")
16
+ if params is not None:
17
+ request_message = request_message + f"params: {params}"
15
18
  json_body = kwargs.get("json_body")
16
19
  if json_body is not None:
17
20
  request_message = (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blueair_api
3
- Version: 1.36.0
3
+ Version: 1.36.2
4
4
  Summary: Blueair Api Wrapper
5
5
  Author-email: Brendan Dahl <dahl.brendan@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/dahlb/blueair_api
@@ -0,0 +1,19 @@
1
+ blueair_api/__init__.py,sha256=gk2CZWEnEo4FlP7VcJsDs1kujZ_ryXF5H9n3Kd-4WWo,313
2
+ blueair_api/callbacks.py,sha256=fvrJsqH5eDRxWOGWiZkF2uLU4n2ve0zzU17ERqWbHP8,756
3
+ blueair_api/const.py,sha256=x-2ae2faZqsalYCuqA_ZpGSZSu0_x_rTrvikagnM9PU,1585
4
+ blueair_api/device.py,sha256=4mjoYGOiVGi_yw5ZRnpgwSRcV9q68nseC6VgdCDyWAQ,5554
5
+ blueair_api/device_aws.py,sha256=ClvpDKT9prv4d15j1fdF1S4gtdH810ZF-iKRN6WXbr0,10506
6
+ blueair_api/errors.py,sha256=lJ_iFU_W6zQfGRi_wsMhWDw-fAVPFeCkCbT1erIlYQQ,233
7
+ blueair_api/http_aws_blueair.py,sha256=4-qrtqYkXHejUhImyDisnLJ-6sDf_YZkzD_Nv-KcBUw,9450
8
+ blueair_api/http_blueair.py,sha256=IMnKXs9S0Z-t1GVshSItjNr9tTdtXjh7-T-tHpsbzhE,11752
9
+ blueair_api/intermediate_representation_aws.py,sha256=g4S7TsRxJd4uh-0k8ByvgG36AXDiPPbDtlHn5ZOTk_A,7035
10
+ blueair_api/model_enum.py,sha256=gH82gAnnxnK7JFIHbfjkFXFA3p_hSmujvvuQ0GQyzm4,494
11
+ blueair_api/stub.py,sha256=J1BXHUzRR4P32q9WkbucmMqzAjqxS722NSMK4TLZG8o,1176
12
+ blueair_api/util.py,sha256=2FerJEliR4AQc8nBeAMXb7nyl-CPhUszaHxFcVq4p0s,2105
13
+ blueair_api/util_bootstrap.py,sha256=oo6qqEYUxWb4wLLivk7ofB0YFrKsTZo5tW0U2wc3-D0,1757
14
+ blueair_api/util_http.py,sha256=LohO13eBk7_MCfBRg3tMKjLklDTr87RC6Q8vLffwX1M,1464
15
+ blueair_api-1.36.2.dist-info/licenses/LICENSE,sha256=W6UV41yCe1R_Avet8VtsxwdJar18n40b3MRnbEMHZmI,1066
16
+ blueair_api-1.36.2.dist-info/METADATA,sha256=7KSURW4i0JNyKUMtJEaawYDY-T0Ab4EMQHz8lisv_O0,2017
17
+ blueair_api-1.36.2.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
18
+ blueair_api-1.36.2.dist-info/top_level.txt,sha256=-gn0jNtmE83qEu70uMW5F4JrXnHGRfuFup1EPWF70oc,12
19
+ blueair_api-1.36.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.3.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,19 +0,0 @@
1
- blueair_api/__init__.py,sha256=gk2CZWEnEo4FlP7VcJsDs1kujZ_ryXF5H9n3Kd-4WWo,313
2
- blueair_api/callbacks.py,sha256=fvrJsqH5eDRxWOGWiZkF2uLU4n2ve0zzU17ERqWbHP8,756
3
- blueair_api/const.py,sha256=q1smSEhwyYvuQiR867lToFm-mGV-d3dNJvN0NJgscbU,1037
4
- blueair_api/device.py,sha256=4mjoYGOiVGi_yw5ZRnpgwSRcV9q68nseC6VgdCDyWAQ,5554
5
- blueair_api/device_aws.py,sha256=J3HhuCnqSQhArgjBQfg3FdIl2lI8EKQp24OFxeA7GNY,10210
6
- blueair_api/errors.py,sha256=lJ_iFU_W6zQfGRi_wsMhWDw-fAVPFeCkCbT1erIlYQQ,233
7
- blueair_api/http_aws_blueair.py,sha256=f5HhjaYiBS8RMPjYgVJLOkk4Yqas2euh6hAkHr6H6Uc,8543
8
- blueair_api/http_blueair.py,sha256=IMnKXs9S0Z-t1GVshSItjNr9tTdtXjh7-T-tHpsbzhE,11752
9
- blueair_api/intermediate_representation_aws.py,sha256=DJWxHFP9yVll0O6kxHWjKscIlu-7genc3f7ZvwV5YdA,6137
10
- blueair_api/model_enum.py,sha256=gH82gAnnxnK7JFIHbfjkFXFA3p_hSmujvvuQ0GQyzm4,494
11
- blueair_api/stub.py,sha256=tWJJLIhKf39P1gRAXfJk6L8cBJI9fVK3Uk9pj2_Teaw,1187
12
- blueair_api/util.py,sha256=2FerJEliR4AQc8nBeAMXb7nyl-CPhUszaHxFcVq4p0s,2105
13
- blueair_api/util_bootstrap.py,sha256=oo6qqEYUxWb4wLLivk7ofB0YFrKsTZo5tW0U2wc3-D0,1757
14
- blueair_api/util_http.py,sha256=45AJG3Vb6LMVzI0WV22AoSyt64f_Jj3KpOAwF5M6EFE,1327
15
- blueair_api-1.36.0.dist-info/licenses/LICENSE,sha256=W6UV41yCe1R_Avet8VtsxwdJar18n40b3MRnbEMHZmI,1066
16
- blueair_api-1.36.0.dist-info/METADATA,sha256=oj2k2pxbF40Bezigu942MVToRz2h5Zi5817HK2YXbDc,2017
17
- blueair_api-1.36.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
18
- blueair_api-1.36.0.dist-info/top_level.txt,sha256=-gn0jNtmE83qEu70uMW5F4JrXnHGRfuFup1EPWF70oc,12
19
- blueair_api-1.36.0.dist-info/RECORD,,