diff --git a/pyproject.toml b/pyproject.toml index 55242cc..e65d2d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "python-awair" -version = "0.2.4" +version = "0.2.5" description = "asyncio client for the Awair GraphQL and Local APIs" authors = ["Andrew Hayworth "] license = "MIT" diff --git a/python_awair/client.py b/python_awair/client.py index ddbc53a..004ed63 100644 --- a/python_awair/client.py +++ b/python_awair/client.py @@ -1,5 +1,6 @@ """Wrapper class to query the Awair API.""" +import json as Json from typing import Any, Dict, NoReturn from aiohttp import ClientResponse, ClientSession @@ -33,7 +34,14 @@ async def query(self, url: str) -> Any: if resp.status != 200: self.__handle_non_200_error(resp) - json = await resp.json() + # Response could be from Awair Omni Ethernet backpack + # https://github.com/home-assistant/core/issues/147682 + if resp.content_type == "text/html": + text = await resp.text() + json = Json.loads(text) + else: + json = await resp.json() + self.__check_errors_array(json) return json diff --git a/tests/const.py b/tests/const.py index f028108..e871dab 100644 --- a/tests/const.py +++ b/tests/const.py @@ -42,3 +42,9 @@ "deviceUUID": "awair-element_5366", "fw_version": "1.2.8", } +MOCK_ELEMENT_DEVICE_C_ATTRS = { + "deviceId": 18959, + "deviceType": "awair-omni", + "deviceUUID": "awair-omni_18959", + "fw_version": "1.8.1", +} diff --git a/tests/fixtures/cassettes/latest_local_backplate.yaml b/tests/fixtures/cassettes/latest_local_backplate.yaml new file mode 100644 index 0000000..a657817 --- /dev/null +++ b/tests/fixtures/cassettes/latest_local_backplate.yaml @@ -0,0 +1,42 @@ +interactions: +- request: + body: null + headers: + Content-Type: + - application/json + authorization: + - fake_token + method: GET + uri: http://awair-omni-18959-backplate.local/settings/config/data + response: + body: + string: '{"device_uuid":"awair-omni_18959","wifi_mac":"E8:C0:01:78:16:ED","ip":"192.168.0.101","netmask":"255.255.255.0","gateway":"192.168.0.1","fw_version":"1.8.1","timezone":"America/Los_Angeles","display":"score","led":{"mode":"auto","brightness":70},"power-status":{"battery":99,"plugged":true}}' + headers: + Access-Control-Allow-Origin: '*' + Content-Type: text/html + Transfer-Encoding: chunked + status: + code: 200 + message: OK + url: http://awair-omni-18959-backplate.local/settings/config/data +- request: + body: null + headers: + Content-Type: + - application/json + authorization: + - fake_token + method: GET + uri: http://awair-omni-18959-backplate.local/air-data/latest + response: + body: + string: '{"timestamp":"2025-09-20T13:24:38.359Z","score":99,"temp":22.12,"humid":51.90,"co2":424,"voc":33,"pm25":1,"lux":0.0,"spl_a":51.9}' + headers: + Access-Control-Allow-Origin: '*' + Content-Type: text/html + Transfer-Encoding: chunked + status: + code: 200 + message: OK + url: http://awair-omni-18959-backplate.local/air-data/latest +version: 1 diff --git a/tests/fixtures/cassettes/local_backplate.yaml b/tests/fixtures/cassettes/local_backplate.yaml new file mode 100644 index 0000000..8cf9e17 --- /dev/null +++ b/tests/fixtures/cassettes/local_backplate.yaml @@ -0,0 +1,22 @@ +interactions: +- request: + body: null + headers: + Content-Type: + - application/json + authorization: + - fake_token + method: GET + uri: http://awair-omni-18959-backplate.local/settings/config/data + response: + body: + string: '{"device_uuid":"awair-omni_18959","wifi_mac":"E8:C0:01:78:16:ED","ip":"192.168.0.101","netmask":"255.255.255.0","gateway":"192.168.0.1","fw_version":"1.8.1","timezone":"America/Los_Angeles","display":"score","led":{"mode":"auto","brightness":70},"power-status":{"battery":99,"plugged":true}}' + headers: + Access-Control-Allow-Origin: '*' + Content-Type: text/html + Transfer-Encoding: chunked + status: + code: 200 + message: OK + url: http://awair-omni-18959-backplate.local/settings/config/data +version: 1 \ No newline at end of file diff --git a/tests/test_python_awair.py b/tests/test_python_awair.py index 3030a1c..3fcccfe 100644 --- a/tests/test_python_awair.py +++ b/tests/test_python_awair.py @@ -16,6 +16,7 @@ AWAIR_GEN1_ID, MOCK_ELEMENT_DEVICE_A_ATTRS, MOCK_ELEMENT_DEVICE_B_ATTRS, + MOCK_ELEMENT_DEVICE_C_ATTRS, MOCK_GEN2_DEVICE_ATTRS, MOCK_GLOW_DEVICE_ATTRS, MOCK_MINT_DEVICE_ATTRS, @@ -92,6 +93,27 @@ async def test_get_local_devices() -> Any: assert " Any: + """Test that we can get a list of devices.""" + async with aiohttp.ClientSession() as session: + with VCR.use_cassette("local_backplate.yaml"): + awair = AwairLocal( + session=session, + device_addrs=[ + "AWAIR-OMNI-18959-BACKPLATE.local", + ], + ) + devices = await awair.devices() + + assert len(devices) == 1 + + assert devices[0].device_id == MOCK_ELEMENT_DEVICE_C_ATTRS["deviceId"] + assert devices[0].device_type == MOCK_ELEMENT_DEVICE_C_ATTRS["deviceType"] + assert devices[0].uuid == MOCK_ELEMENT_DEVICE_C_ATTRS["deviceUUID"] + assert devices[0].fw_version == MOCK_ELEMENT_DEVICE_C_ATTRS["fw_version"] + assert " Any: """Test that we can get the latest air data.""" target = datetime(2020, 4, 10, 10, 38, 30) @@ -130,6 +152,27 @@ async def test_get_latest_local() -> Any: assert " Any: + """Test that we can get the latest air data.""" + target = datetime(2025, 9, 20, 13, 24, 38) + async with aiohttp.ClientSession() as session: + with VCR.use_cassette("latest_local_backplate.yaml"), time_travel(target): + awair = AwairLocal( + session=session, device_addrs=["AWAIR-OMNI-18959-BACKPLATE.local"] + ) + devices = await awair.devices() + assert len(devices) == 1 + device = devices[0] + resp = await device.air_data_latest() + + assert resp is not None + assert resp.timestamp == datetime(2025, 9, 20, 13, 24, 38, 359000) + assert resp.score == 99 + assert resp.sensors["temperature"] == 22.12 + assert len(resp.indices) == 0 + assert " Any: """Test that we can get the five-minute avg air data.""" target = datetime(2020, 4, 10, 10, 38, 31, 2883)