blueair-api 1.36.0__tar.gz → 1.36.1__tar.gz
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-1.36.0 → blueair_api-1.36.1}/PKG-INFO +1 -1
- {blueair_api-1.36.0 → blueair_api-1.36.1}/pyproject.toml +1 -1
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/const.py +16 -4
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/device_aws.py +10 -4
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/http_aws_blueair.py +29 -8
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/intermediate_representation_aws.py +32 -2
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/stub.py +1 -1
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/util_http.py +3 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api.egg-info/PKG-INFO +1 -1
- {blueair_api-1.36.0 → blueair_api-1.36.1}/tests/test_device_aws.py +26 -19
- {blueair_api-1.36.0 → blueair_api-1.36.1}/tests/test_intermediate_representation_aws.py +72 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/LICENSE +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/README.md +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/setup.cfg +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/__init__.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/callbacks.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/device.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/errors.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/http_blueair.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/model_enum.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/util.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/util_bootstrap.py +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api.egg-info/SOURCES.txt +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api.egg-info/dependency_links.txt +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api.egg-info/requires.txt +0 -0
- {blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api.egg-info/top_level.txt +0 -0
@@ -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
|
|
@@ -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.
|
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
|
-
|
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://
|
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://
|
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']}
|
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']}
|
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']}
|
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']}
|
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
|
}
|
{blueair_api-1.36.0 → blueair_api-1.36.1}/src/blueair_api/intermediate_representation_aws.py
RENAMED
@@ -1,5 +1,5 @@
|
|
1
1
|
import typing
|
2
|
-
from typing import Any
|
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.
|
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: typing.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)
|
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
|
|
@@ -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
|
11
|
+
from src.blueair_api import get_devices, get_aws_devices
|
12
12
|
|
13
13
|
|
14
14
|
logger = logging.getLogger("src.blueair_api")
|
@@ -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 = (
|
@@ -97,6 +97,13 @@ class DeviceAwsTestBase(IsolatedAsyncioTestCase):
|
|
97
97
|
"states": [],
|
98
98
|
})
|
99
99
|
|
100
|
+
async def fake_sensors(device_name, device_uuid):
|
101
|
+
return [{
|
102
|
+
"datapoints": [],
|
103
|
+
"sensors": []
|
104
|
+
}]
|
105
|
+
|
106
|
+
self.api.device_sensors.side_effect = fake_sensors
|
100
107
|
self.api.device_info.side_effect = self.device_info_helper.device_info
|
101
108
|
self.api.set_device_info.side_effect = self.device_info_helper.set_device_info
|
102
109
|
|
@@ -409,8 +416,8 @@ class H35iTest(DeviceAwsTestBase):
|
|
409
416
|
assert device.pm2_5 is NotImplemented
|
410
417
|
assert device.pm10 is NotImplemented
|
411
418
|
assert device.tVOC is NotImplemented
|
412
|
-
assert device.temperature
|
413
|
-
assert device.humidity
|
419
|
+
assert device.temperature is None
|
420
|
+
assert device.humidity is None
|
414
421
|
assert device.name == "Bedroom"
|
415
422
|
assert device.firmware == "1.0.1"
|
416
423
|
assert device.mcu_firmware == "1.0.1"
|
@@ -437,7 +444,7 @@ class H35iTest(DeviceAwsTestBase):
|
|
437
444
|
assert device.heat_fan_speed is NotImplemented
|
438
445
|
assert device.cool_sub_mode is NotImplemented
|
439
446
|
assert device.cool_fan_speed is NotImplemented
|
440
|
-
assert device.fan_speed_0 is
|
447
|
+
assert device.fan_speed_0 is None
|
441
448
|
assert device.temperature_unit is NotImplemented
|
442
449
|
|
443
450
|
|
@@ -459,7 +466,7 @@ class Max311iTest(DeviceAwsTestBase):
|
|
459
466
|
assert device.model == ModelEnum.MAX_311I
|
460
467
|
|
461
468
|
assert device.pm1 is NotImplemented
|
462
|
-
assert device.pm2_5
|
469
|
+
assert device.pm2_5 is None
|
463
470
|
assert device.pm10 is NotImplemented
|
464
471
|
assert device.tVOC is NotImplemented
|
465
472
|
assert device.temperature is NotImplemented
|
@@ -490,7 +497,7 @@ class Max311iTest(DeviceAwsTestBase):
|
|
490
497
|
assert device.heat_fan_speed is NotImplemented
|
491
498
|
assert device.cool_sub_mode is NotImplemented
|
492
499
|
assert device.cool_fan_speed is NotImplemented
|
493
|
-
assert device.fan_speed_0 is
|
500
|
+
assert device.fan_speed_0 is None
|
494
501
|
assert device.temperature_unit is NotImplemented
|
495
502
|
|
496
503
|
|
@@ -513,11 +520,11 @@ class T10iTest(DeviceAwsTestBase):
|
|
513
520
|
assert device.model == ModelEnum.T10I
|
514
521
|
|
515
522
|
assert device.pm1 is NotImplemented
|
516
|
-
assert device.pm2_5
|
523
|
+
assert device.pm2_5 is None
|
517
524
|
assert device.pm10 is NotImplemented
|
518
525
|
assert device.tVOC is NotImplemented
|
519
|
-
assert device.temperature
|
520
|
-
assert device.humidity
|
526
|
+
assert device.temperature is None
|
527
|
+
assert device.humidity is None
|
521
528
|
assert device.name == "Allen's Office"
|
522
529
|
assert device.firmware == "1.0.4"
|
523
530
|
assert device.mcu_firmware == "1.0.4"
|
@@ -566,12 +573,12 @@ class Protect7470iTest(DeviceAwsTestBase):
|
|
566
573
|
|
567
574
|
assert device.model == ModelEnum.PROTECT_7470I
|
568
575
|
|
569
|
-
assert device.pm1
|
570
|
-
assert device.pm2_5
|
571
|
-
assert device.pm10
|
572
|
-
assert device.tVOC
|
573
|
-
assert device.temperature
|
574
|
-
assert device.humidity
|
576
|
+
assert device.pm1 is None
|
577
|
+
assert device.pm2_5 is None
|
578
|
+
assert device.pm10 is None
|
579
|
+
assert device.tVOC is None
|
580
|
+
assert device.temperature is None
|
581
|
+
assert device.humidity is None
|
575
582
|
assert device.name == "air filter in room"
|
576
583
|
assert device.firmware == "2.1.1"
|
577
584
|
assert device.mcu_firmware == "1.0.12"
|
@@ -598,7 +605,7 @@ class Protect7470iTest(DeviceAwsTestBase):
|
|
598
605
|
assert device.cool_sub_mode is NotImplemented
|
599
606
|
assert device.cool_fan_speed is NotImplemented
|
600
607
|
assert device.ap_sub_mode is NotImplemented
|
601
|
-
assert device.fan_speed_0 is
|
608
|
+
assert device.fan_speed_0 is None
|
602
609
|
assert device.temperature_unit is NotImplemented
|
603
610
|
|
604
611
|
|
@@ -620,9 +627,9 @@ class Max211iTest(DeviceAwsTestBase):
|
|
620
627
|
|
621
628
|
assert device.model == ModelEnum.MAX_211I
|
622
629
|
|
623
|
-
assert device.pm1
|
624
|
-
assert device.pm2_5
|
625
|
-
assert device.pm10
|
630
|
+
assert device.pm1 is None
|
631
|
+
assert device.pm2_5 is None
|
632
|
+
assert device.pm10 is None
|
626
633
|
assert device.tVOC is NotImplemented
|
627
634
|
assert device.temperature is NotImplemented
|
628
635
|
assert device.humidity is NotImplemented
|
@@ -652,5 +659,5 @@ class Max211iTest(DeviceAwsTestBase):
|
|
652
659
|
assert device.cool_sub_mode is NotImplemented
|
653
660
|
assert device.cool_fan_speed is NotImplemented
|
654
661
|
assert device.ap_sub_mode is NotImplemented
|
655
|
-
assert device.fan_speed_0 is
|
662
|
+
assert device.fan_speed_0 is None
|
656
663
|
assert device.temperature_unit is NotImplemented
|
@@ -5,6 +5,78 @@ import pytest
|
|
5
5
|
|
6
6
|
from blueair_api import intermediate_representation_aws as ir
|
7
7
|
|
8
|
+
|
9
|
+
class SensorHistoryTest(TestCase):
|
10
|
+
def test_history_simple(self):
|
11
|
+
sh = ir.SensorHistory([
|
12
|
+
{
|
13
|
+
"datapoints": [
|
14
|
+
[
|
15
|
+
"1",
|
16
|
+
"4",
|
17
|
+
"5",
|
18
|
+
"6",
|
19
|
+
"42",
|
20
|
+
None,
|
21
|
+
"51",
|
22
|
+
"27",
|
23
|
+
"87"
|
24
|
+
],
|
25
|
+
[
|
26
|
+
"3",
|
27
|
+
"1",
|
28
|
+
"2",
|
29
|
+
"3",
|
30
|
+
"43",
|
31
|
+
None,
|
32
|
+
"50",
|
33
|
+
"24",
|
34
|
+
"91"
|
35
|
+
],
|
36
|
+
],
|
37
|
+
"sensors": [
|
38
|
+
"pm1",
|
39
|
+
"pm2_5",
|
40
|
+
"pm10",
|
41
|
+
"tVOC",
|
42
|
+
"hcho",
|
43
|
+
"h",
|
44
|
+
"t",
|
45
|
+
"fsp0"
|
46
|
+
],
|
47
|
+
"start": "1746401990",
|
48
|
+
"end": "1746409190",
|
49
|
+
"did": "1d528642-56a9"
|
50
|
+
}
|
51
|
+
])
|
52
|
+
assert sh[0].timestamp == 1
|
53
|
+
assert sh[0].values == {
|
54
|
+
"pm1": 4,
|
55
|
+
"pm2_5": 5,
|
56
|
+
"pm10": 6,
|
57
|
+
"tVOC": 42,
|
58
|
+
"h": 51,
|
59
|
+
"t": 27,
|
60
|
+
"fsp0": 87
|
61
|
+
}
|
62
|
+
assert sh[1].timestamp == 3
|
63
|
+
assert sh[1].values == {
|
64
|
+
"pm1": 1,
|
65
|
+
"pm2_5": 2,
|
66
|
+
"pm10": 3,
|
67
|
+
"tVOC": 43,
|
68
|
+
"h": 50,
|
69
|
+
"t": 24,
|
70
|
+
"fsp0": 91
|
71
|
+
}
|
72
|
+
|
73
|
+
assert sh.to_latest().timestamp == 1
|
74
|
+
|
75
|
+
# assert latest['v'].timestamp == 1
|
76
|
+
# assert latest['v'].value == 1
|
77
|
+
# assert isinstance(latest['v'].value, float)
|
78
|
+
|
79
|
+
|
8
80
|
class SensorPackTest(TestCase):
|
9
81
|
|
10
82
|
def testSimple(self):
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|