aioamazondevices 6.5.0__tar.gz → 6.5.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aioamazondevices
3
- Version: 6.5.0
3
+ Version: 6.5.2
4
4
  Summary: Python library to control Amazon devices
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "aioamazondevices"
3
- version = "6.5.0"
3
+ version = "6.5.2"
4
4
  requires-python = ">=3.12"
5
5
  description = "Python library to control Amazon devices"
6
6
  authors = [
@@ -1,6 +1,6 @@
1
1
  """aioamazondevices library."""
2
2
 
3
- __version__ = "6.5.0"
3
+ __version__ = "6.5.2"
4
4
 
5
5
 
6
6
  from .api import AmazonDevice, AmazonEchoApi
@@ -40,6 +40,7 @@ from .const import (
40
40
  AMAZON_CLIENT_OS,
41
41
  AMAZON_DEVICE_SOFTWARE_VERSION,
42
42
  AMAZON_DEVICE_TYPE,
43
+ ARRAY_WRAPPER,
43
44
  BIN_EXTENSION,
44
45
  COUNTRY_GROUPS,
45
46
  CSRF_COOKIE,
@@ -61,6 +62,7 @@ from .const import (
61
62
  REQUEST_AGENT,
62
63
  SAVE_PATH,
63
64
  SENSORS,
65
+ SPEAKER_GROUP_FAMILY,
64
66
  URI_DEVICES,
65
67
  URI_DND,
66
68
  URI_NEXUS_GRAPHQL,
@@ -606,20 +608,19 @@ class AmazonEchoApi:
606
608
  _LOGGER.info("Register device: %s", scrub_fields(login_data))
607
609
  return login_data
608
610
 
609
- async def _get_sensors_state(
610
- self, endpoint_id_list: list[str]
611
- ) -> dict[str, Any] | list[dict[str, Any]]:
612
- """Get sensor State."""
611
+ async def _get_sensors_states(self) -> dict[str, dict[str, AmazonDeviceSensor]]:
612
+ """Retrieve devices sensors states."""
613
+ devices_sensors: dict[str, dict[str, AmazonDeviceSensor]] = {}
614
+
615
+ endpoint_ids = list(self._endpoints.keys())
613
616
  payload = [
614
617
  {
615
618
  "operationName": "getEndpointState",
616
619
  "variables": {
617
- "endpointId": endpoint_id,
618
- "latencyTolerance": "LOW",
620
+ "endpointIds": endpoint_ids,
619
621
  },
620
622
  "query": QUERY_SENSOR_STATE,
621
623
  }
622
- for endpoint_id in endpoint_id_list
623
624
  ]
624
625
 
625
626
  _, raw_resp = await self._session_request(
@@ -629,47 +630,29 @@ class AmazonEchoApi:
629
630
  json_data=True,
630
631
  )
631
632
 
632
- return await self._response_to_json(raw_resp)
633
+ sensors_state = await self._response_to_json(raw_resp)
634
+ _LOGGER.debug("Sensor data - %s", sensors_state)
633
635
 
634
- async def _get_sensors_states(self) -> dict[str, dict[str, AmazonDeviceSensor]]:
635
- """Retrieve devices sensors states."""
636
- devices_sensors: dict[str, dict[str, AmazonDeviceSensor]] = {}
636
+ if await self._format_human_error(sensors_state):
637
+ # Explicit error in returned data
638
+ return {}
637
639
 
638
- # batch endpoints into groups of 3 to reduce number of requests
639
- endpoint_ids = list(self._endpoints.keys())
640
- batches = [endpoint_ids[i : i + 3] for i in range(0, len(endpoint_ids), 3)]
641
- for endpoint_id_batch in batches:
642
- sensors_state = await self._get_sensors_state(endpoint_id_batch)
643
- _LOGGER.debug("Sensor data - %s", sensors_state)
640
+ if (
641
+ not (arr := sensors_state.get(ARRAY_WRAPPER))
642
+ or not (data := arr[0].get("data"))
643
+ or not (endpoints_list := data.get("listEndpoints"))
644
+ or not (endpoints := endpoints_list.get("endpoints"))
645
+ ):
646
+ _LOGGER.error("Malformed sensor state data received: %s", sensors_state)
647
+ return {}
644
648
 
645
- if not isinstance(sensors_state, list) and (
646
- error := sensors_state.get("errors")
647
- ):
648
- if isinstance(error, list):
649
- error = error[0]
650
- msg = error.get("message", "Unknown error")
651
- path = error.get("path", "Unknown path")
652
- _LOGGER.error(
653
- "Error retrieving devices state: %s for path %s", msg, path
654
- )
655
- return {}
649
+ for endpoint in endpoints:
650
+ serial_number = self._endpoints[endpoint.get("endpointId")]
656
651
 
657
- for endpoint_data in sensors_state:
658
- if (
659
- not isinstance(endpoint_data, dict)
660
- or not (data := endpoint_data.get("data"))
661
- or not (endpoint := data.get("endpoint"))
662
- ):
663
- _LOGGER.error(
664
- "Malformed sensor state data received: %s", endpoint_data
665
- )
666
- return {}
667
- serial_number = self._endpoints[endpoint.get("endpointId")]
668
-
669
- if serial_number in self._final_devices:
670
- devices_sensors[serial_number] = self._get_device_sensor_state(
671
- endpoint, serial_number
672
- )
652
+ if serial_number in self._final_devices:
653
+ devices_sensors[serial_number] = self._get_device_sensor_state(
654
+ endpoint, serial_number
655
+ )
673
656
 
674
657
  return devices_sensors
675
658
 
@@ -730,14 +713,16 @@ class AmazonEchoApi:
730
713
  _LOGGER.debug(
731
714
  "error in sensor %s - %s - %s", name, error_type, error_msg
732
715
  )
733
- device_sensors[name] = AmazonDeviceSensor(
734
- name,
735
- value,
736
- error,
737
- error_type,
738
- error_msg,
739
- scale,
740
- )
716
+
717
+ if error_type != "NOT_FOUND":
718
+ device_sensors[name] = AmazonDeviceSensor(
719
+ name,
720
+ value,
721
+ error,
722
+ error_type,
723
+ error_msg,
724
+ scale,
725
+ )
741
726
 
742
727
  return device_sensors
743
728
 
@@ -758,7 +743,7 @@ class AmazonEchoApi:
758
743
  endpoint_data = await self._response_to_json(raw_resp)
759
744
 
760
745
  if not (data := endpoint_data.get("data")) or not data.get("listEndpoints"):
761
- _LOGGER.error("Malformed endpoint data received: %s", endpoint_data)
746
+ await self._format_human_error(endpoint_data)
762
747
  return {}
763
748
 
764
749
  endpoints = data["listEndpoints"]
@@ -782,6 +767,10 @@ class AmazonEchoApi:
782
767
  if not data:
783
768
  _LOGGER.warning("Empty JSON data received")
784
769
  data = {}
770
+ if isinstance(data, list):
771
+ # if anonymous array is returned wrap it inside
772
+ # generated key to convert list to dict
773
+ data = {ARRAY_WRAPPER: data}
785
774
  return cast("dict[str, Any]", data)
786
775
  except ContentTypeError as exc:
787
776
  raise ValueError("Response not in JSON format") from exc
@@ -1153,7 +1142,9 @@ class AmazonEchoApi:
1153
1142
  else:
1154
1143
  for device_sensor in device.sensors.values():
1155
1144
  device_sensor.error = True
1156
- if device_dnd := dnd_sensors.get(device.serial_number):
1145
+ if (
1146
+ device_dnd := dnd_sensors.get(device.serial_number)
1147
+ ) and device.device_family != SPEAKER_GROUP_FAMILY:
1157
1148
  device.sensors["dnd"] = device_dnd
1158
1149
 
1159
1150
  # Update notifications
@@ -1222,11 +1213,20 @@ class AmazonEchoApi:
1222
1213
  if not device or (device.get("deviceType") in DEVICE_TO_IGNORE):
1223
1214
  continue
1224
1215
 
1216
+ account_name: str = device["accountName"]
1217
+ capabilities: list[str] = device["capabilities"]
1218
+ # Skip devices that cannot be used with voice features
1219
+ if "MICROPHONE" not in capabilities:
1220
+ _LOGGER.debug(
1221
+ "Skipping device without microphone capabilities: %s", account_name
1222
+ )
1223
+ continue
1224
+
1225
1225
  serial_number: str = device["serialNumber"]
1226
1226
 
1227
1227
  final_devices_list[serial_number] = AmazonDevice(
1228
- account_name=device["accountName"],
1229
- capabilities=device["capabilities"],
1228
+ account_name=account_name,
1229
+ capabilities=capabilities,
1230
1230
  device_family=device["deviceFamily"],
1231
1231
  device_type=device["deviceType"],
1232
1232
  device_owner_customer_id=device["deviceOwnerCustomerId"],
@@ -1576,3 +1576,18 @@ class AmazonEchoApi:
1576
1576
  scale=None,
1577
1577
  )
1578
1578
  return dnd_status
1579
+
1580
+ async def _format_human_error(self, sensors_state: dict) -> bool:
1581
+ """Format human readable error from malformed data."""
1582
+ if sensors_state.get(ARRAY_WRAPPER):
1583
+ error = sensors_state[ARRAY_WRAPPER][0].get("errors", [])
1584
+ else:
1585
+ error = sensors_state.get("errors", [])
1586
+
1587
+ if not error:
1588
+ return False
1589
+
1590
+ msg = error[0].get("message", "Unknown error")
1591
+ path = error[0].get("path", "Unknown path")
1592
+ _LOGGER.error("Error retrieving devices state: %s for path %s", msg, path)
1593
+ return True
@@ -4,6 +4,8 @@ import logging
4
4
 
5
5
  _LOGGER = logging.getLogger(__package__)
6
6
 
7
+ ARRAY_WRAPPER = "generatedArrayWrapper"
8
+
7
9
  HTTP_ERROR_199 = 199
8
10
  HTTP_ERROR_299 = 299
9
11
 
@@ -41,44 +41,57 @@ query getDevicesBaseData {
41
41
  """
42
42
 
43
43
  QUERY_SENSOR_STATE = """
44
- query getEndpointState($endpointId: String!, $latencyTolerance: LatencyToleranceValue) {
45
- endpoint(id: $endpointId) {
46
- endpointId: id
47
- features(latencyToleranceValue: $latencyTolerance) {
44
+ fragment EndpointState on Endpoint {
45
+ endpointId: id
46
+ friendlyNameObject { value { text } }
47
+ features {
48
+ name
49
+ properties {
48
50
  name
49
- instance
50
- properties {
51
+ type
52
+ accuracy
53
+ error { type message }
54
+ __typename
55
+ ... on Illuminance {
56
+ illuminanceValue { value }
57
+ timeOfSample
58
+ timeOfLastChange
59
+ }
60
+ ... on Reachability {
61
+ reachabilityStatusValue
62
+ timeOfSample
63
+ timeOfLastChange
64
+ }
65
+ ... on DetectionState {
66
+ detectionStateValue
67
+ timeOfSample
68
+ timeOfLastChange
69
+ }
70
+ ... on TemperatureSensor {
51
71
  name
52
- type
53
- accuracy
54
- error { type message }
55
- __typename
56
- ... on Illuminance {
57
- illuminanceValue { value }
58
- timeOfSample
59
- timeOfLastChange
60
- }
61
- ... on Reachability {
62
- reachabilityStatusValue
63
- timeOfSample
64
- timeOfLastChange
65
- }
66
- ... on DetectionState {
67
- detectionStateValue
68
- timeOfSample
69
- timeOfLastChange
70
- }
71
- ... on TemperatureSensor {
72
- name
73
- value {
74
- value
75
- scale
76
- }
77
- timeOfSample
78
- timeOfLastChange
72
+ value {
73
+ value
74
+ scale
79
75
  }
76
+ timeOfSample
77
+ timeOfLastChange
80
78
  }
81
79
  }
82
80
  }
83
81
  }
82
+
83
+
84
+ query getEndpointState($endpointIds: [String]!) {
85
+ listEndpoints(
86
+ listEndpointsInput: {
87
+ latencyTolerance: LOW,
88
+ endpointIds: $endpointIds,
89
+ includeHouseholdDevices: true
90
+ }
91
+ ) {
92
+ endpoints {
93
+ ...EndpointState
94
+ }
95
+ }
96
+ }
84
97
  """