aioamazondevices 3.0.9__tar.gz → 3.1.0__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.
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/PKG-INFO +1 -1
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/pyproject.toml +1 -1
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/src/aioamazondevices/__init__.py +1 -1
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/src/aioamazondevices/api.py +116 -6
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/src/aioamazondevices/const.py +30 -0
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/LICENSE +0 -0
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/README.md +0 -0
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/src/aioamazondevices/exceptions.py +0 -0
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/src/aioamazondevices/py.typed +0 -0
- {aioamazondevices-3.0.9 → aioamazondevices-3.1.0}/src/aioamazondevices/sounds.py +0 -0
@@ -34,6 +34,7 @@ from .const import (
|
|
34
34
|
CSRF_COOKIE,
|
35
35
|
DEFAULT_ASSOC_HANDLE,
|
36
36
|
DEFAULT_HEADERS,
|
37
|
+
DEVICE_TO_IGNORE,
|
37
38
|
DEVICE_TYPE_TO_MODEL,
|
38
39
|
DOMAIN_BY_ISO3166_COUNTRY,
|
39
40
|
HTML_EXTENSION,
|
@@ -41,9 +42,13 @@ from .const import (
|
|
41
42
|
NODE_BLUETOOTH,
|
42
43
|
NODE_DEVICES,
|
43
44
|
NODE_DO_NOT_DISTURB,
|
45
|
+
NODE_IDENTIFIER,
|
44
46
|
NODE_PREFERENCES,
|
45
47
|
SAVE_PATH,
|
48
|
+
SENSORS,
|
49
|
+
URI_IDS,
|
46
50
|
URI_QUERIES,
|
51
|
+
URI_SENSORS,
|
47
52
|
)
|
48
53
|
from .exceptions import (
|
49
54
|
CannotAuthenticate,
|
@@ -53,6 +58,15 @@ from .exceptions import (
|
|
53
58
|
)
|
54
59
|
|
55
60
|
|
61
|
+
@dataclass
|
62
|
+
class AmazonDeviceSensor:
|
63
|
+
"""Amazon device sensor class."""
|
64
|
+
|
65
|
+
name: str
|
66
|
+
value: Any
|
67
|
+
scale: str | None
|
68
|
+
|
69
|
+
|
56
70
|
@dataclass
|
57
71
|
class AmazonDevice:
|
58
72
|
"""Amazon device class."""
|
@@ -69,6 +83,9 @@ class AmazonDevice:
|
|
69
83
|
do_not_disturb: bool
|
70
84
|
response_style: str | None
|
71
85
|
bluetooth_state: bool
|
86
|
+
entity_id: str
|
87
|
+
appliance_id: str
|
88
|
+
sensors: dict[str, AmazonDeviceSensor]
|
72
89
|
|
73
90
|
|
74
91
|
class AmazonSequenceType(StrEnum):
|
@@ -124,6 +141,7 @@ class AmazonEchoApi:
|
|
124
141
|
self._list_for_clusters: dict[str, str] = {}
|
125
142
|
|
126
143
|
self.session: ClientSession
|
144
|
+
self._devices: dict[str, Any] = {}
|
127
145
|
|
128
146
|
def _load_website_cookies(self) -> dict[str, str]:
|
129
147
|
"""Get website cookies, if avaliables."""
|
@@ -484,6 +502,84 @@ class AmazonEchoApi:
|
|
484
502
|
await self._save_to_file(login_data, "login_data", JSON_EXTENSION)
|
485
503
|
return login_data
|
486
504
|
|
505
|
+
async def _get_devices_ids(self) -> list[dict[str, str]]:
|
506
|
+
"""Retrieve devices entityId and applianceId."""
|
507
|
+
_, raw_resp = await self._session_request(
|
508
|
+
"GET",
|
509
|
+
url=f"https://alexa.amazon.{self._domain}{URI_IDS}",
|
510
|
+
)
|
511
|
+
json_data = await raw_resp.json()
|
512
|
+
|
513
|
+
network_detail = orjson.loads(json_data["networkDetail"])
|
514
|
+
# Navigate through the nested structure step by step
|
515
|
+
location_details = network_detail["locationDetails"]["locationDetails"]
|
516
|
+
default_location = location_details["Default_Location"]
|
517
|
+
amazon_bridge = default_location["amazonBridgeDetails"]["amazonBridgeDetails"]
|
518
|
+
lambda_bridge = amazon_bridge["LambdaBridge_AAA/SonarCloudService"]
|
519
|
+
appliance_details = lambda_bridge["applianceDetails"]["applianceDetails"]
|
520
|
+
|
521
|
+
entity_ids_list: list[dict[str, str]] = []
|
522
|
+
# Process each appliance that starts with AAA_SonarCloudService
|
523
|
+
for appliance_key, appliance_data in appliance_details.items():
|
524
|
+
if not appliance_key.startswith("AAA_SonarCloudService"):
|
525
|
+
continue
|
526
|
+
|
527
|
+
entity_id = appliance_data["entityId"]
|
528
|
+
appliance_id = appliance_data["applianceId"]
|
529
|
+
|
530
|
+
# Create identifier object for this appliance
|
531
|
+
identifier = {
|
532
|
+
"entityId": entity_id,
|
533
|
+
"applianceId": appliance_id,
|
534
|
+
}
|
535
|
+
|
536
|
+
# Update device information for each device in the identifier list
|
537
|
+
for device_identifier in appliance_data["alexaDeviceIdentifierList"]:
|
538
|
+
serial_number = device_identifier["dmsDeviceSerialNumber"]
|
539
|
+
|
540
|
+
# Add identifier information to the device
|
541
|
+
self._devices[serial_number] |= {NODE_IDENTIFIER: identifier}
|
542
|
+
|
543
|
+
# Add to entity IDs list for sensor retrieval
|
544
|
+
entity_ids_list.append({"entityId": entity_id, "entityType": "ENTITY"})
|
545
|
+
|
546
|
+
return entity_ids_list
|
547
|
+
|
548
|
+
async def _get_sensors_states(
|
549
|
+
self, entity_ids_list: list[dict[str, str]]
|
550
|
+
) -> dict[str, dict[str, AmazonDeviceSensor]]:
|
551
|
+
"""Retrieve devices sensors states."""
|
552
|
+
_data = {"stateRequests": entity_ids_list}
|
553
|
+
_, raw_resp = await self._session_request(
|
554
|
+
"POST",
|
555
|
+
url=f"https://alexa.amazon.{self._domain}{URI_SENSORS}",
|
556
|
+
input_data=_data,
|
557
|
+
json_data=True,
|
558
|
+
)
|
559
|
+
json_data = await raw_resp.json()
|
560
|
+
|
561
|
+
final_sensors: dict[str, dict[str, AmazonDeviceSensor]] = {}
|
562
|
+
for sensors in json_data["deviceStates"]:
|
563
|
+
_id = sensors["entity"]["entityId"]
|
564
|
+
dict_sensors: dict[str, AmazonDeviceSensor] = {}
|
565
|
+
for sensor in sensors["capabilityStates"]:
|
566
|
+
sensor_json = orjson.loads(sensor)
|
567
|
+
if sensor_json["name"] in SENSORS:
|
568
|
+
_value = sensor_json["value"]
|
569
|
+
_value_dict = isinstance(_value, dict)
|
570
|
+
_name = sensor_json["name"]
|
571
|
+
dict_sensors.update(
|
572
|
+
{
|
573
|
+
_name: AmazonDeviceSensor(
|
574
|
+
name=_name,
|
575
|
+
value=(_value["value"] if _value_dict else _value),
|
576
|
+
scale=_value.get("scale") if _value_dict else None,
|
577
|
+
)
|
578
|
+
}
|
579
|
+
)
|
580
|
+
final_sensors.update({_id: dict_sensors})
|
581
|
+
return final_sensors
|
582
|
+
|
487
583
|
async def login_mode_interactive(self, otp_code: str) -> dict[str, Any]:
|
488
584
|
"""Login to Amazon interactively via OTP."""
|
489
585
|
_LOGGER.debug("Logging-in for %s [otp code %s]", self._login_email, otp_code)
|
@@ -573,7 +669,7 @@ class AmazonEchoApi:
|
|
573
669
|
self,
|
574
670
|
) -> dict[str, AmazonDevice]:
|
575
671
|
"""Get Amazon devices data."""
|
576
|
-
|
672
|
+
self._devices = {}
|
577
673
|
for key in URI_QUERIES:
|
578
674
|
_, raw_resp = await self._session_request(
|
579
675
|
method=HTTPMethod.GET,
|
@@ -596,21 +692,32 @@ class AmazonEchoApi:
|
|
596
692
|
|
597
693
|
for data in json_data[key]:
|
598
694
|
dev_serial = data.get("serialNumber") or data.get("deviceSerialNumber")
|
599
|
-
if previous_data :=
|
600
|
-
|
695
|
+
if previous_data := self._devices.get(dev_serial):
|
696
|
+
self._devices[dev_serial] = previous_data | {key: data}
|
601
697
|
else:
|
602
|
-
|
698
|
+
self._devices[dev_serial] = {key: data}
|
699
|
+
|
700
|
+
entity_ids_list = await self._get_devices_ids()
|
701
|
+
devices_sensors = await self._get_sensors_states(entity_ids_list)
|
603
702
|
|
604
703
|
final_devices_list: dict[str, AmazonDevice] = {}
|
605
|
-
for device in
|
704
|
+
for device in self._devices.values():
|
606
705
|
devices_node = device[NODE_DEVICES]
|
607
706
|
preferences_node = device.get(NODE_PREFERENCES)
|
608
707
|
do_not_disturb_node = device[NODE_DO_NOT_DISTURB]
|
609
708
|
bluetooth_node = device[NODE_BLUETOOTH]
|
709
|
+
identifier_node = device.get(NODE_IDENTIFIER, {})
|
710
|
+
|
711
|
+
# Add sensors
|
712
|
+
if identifier_node:
|
713
|
+
for _device_id, _device_sensors in devices_sensors.items():
|
714
|
+
if _device_id == identifier_node["entityId"]:
|
715
|
+
sensors = _device_sensors
|
716
|
+
|
610
717
|
# Remove stale, orphaned and virtual devices
|
611
718
|
if (
|
612
719
|
NODE_DEVICES not in device
|
613
|
-
or devices_node.get("deviceType")
|
720
|
+
or devices_node.get("deviceType") in DEVICE_TO_IGNORE
|
614
721
|
):
|
615
722
|
continue
|
616
723
|
|
@@ -632,6 +739,9 @@ class AmazonEchoApi:
|
|
632
739
|
preferences_node["responseStyle"] if preferences_node else None
|
633
740
|
),
|
634
741
|
bluetooth_state=bluetooth_node["online"],
|
742
|
+
entity_id=identifier_node.get("entityId"),
|
743
|
+
appliance_id=identifier_node.get("applianceId"),
|
744
|
+
sensors=sensors,
|
635
745
|
)
|
636
746
|
|
637
747
|
self._list_for_clusters.update(
|
@@ -51,22 +51,52 @@ NODE_DEVICES = "devices"
|
|
51
51
|
NODE_DO_NOT_DISTURB = "doNotDisturbDeviceStatusList"
|
52
52
|
NODE_PREFERENCES = "devicePreferences"
|
53
53
|
NODE_BLUETOOTH = "bluetoothStates"
|
54
|
+
NODE_IDENTIFIER = "identifier"
|
55
|
+
NODE_SENSORS = "sensors"
|
54
56
|
|
55
57
|
URI_QUERIES = {
|
56
58
|
NODE_DEVICES: "/api/devices-v2/device",
|
57
59
|
NODE_DO_NOT_DISTURB: "/api/dnd/device-status-list",
|
58
60
|
NODE_PREFERENCES: "/api/device-preferences",
|
59
61
|
NODE_BLUETOOTH: "/api/bluetooth",
|
62
|
+
# "/api/ping"
|
63
|
+
# "/api/np/command"
|
64
|
+
# "/api/np/player"
|
65
|
+
# "/api/device-wifi-details"
|
66
|
+
# "/api/activities"
|
67
|
+
# "/api/behaviors/v2/automations"
|
68
|
+
# "/api/notifications"
|
60
69
|
}
|
61
70
|
|
71
|
+
URI_IDS = "/api/phoenix"
|
72
|
+
URI_SENSORS = "/api/phoenix/state"
|
73
|
+
|
74
|
+
SENSORS = [
|
75
|
+
"babyCryDetectionState",
|
76
|
+
"beepingApplianceDetectionState",
|
77
|
+
"coughDetectionState",
|
78
|
+
"dogBarkDetectionState",
|
79
|
+
"humanPresenceDetectionState",
|
80
|
+
"illuminance",
|
81
|
+
"temperature",
|
82
|
+
"waterSoundsDetectionState",
|
83
|
+
]
|
84
|
+
SENSOR_STATE_OFF = "NOT_DETECTED"
|
85
|
+
|
62
86
|
# File extensions
|
63
87
|
SAVE_PATH = "out"
|
64
88
|
HTML_EXTENSION = ".html"
|
65
89
|
JSON_EXTENSION = ".json"
|
66
90
|
BIN_EXTENSION = ".bin"
|
67
91
|
|
92
|
+
SPEAKER_GROUP_FAMILY = "WHA"
|
68
93
|
SPEAKER_GROUP_MODEL = "Speaker Group"
|
69
94
|
|
95
|
+
DEVICE_TO_IGNORE: list[str] = [
|
96
|
+
AMAZON_DEVICE_TYPE, # Alexa App for Mobile
|
97
|
+
"A1RTAM01W29CUP", # Alexa App for PC
|
98
|
+
]
|
99
|
+
|
70
100
|
DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
|
71
101
|
"A10A33FOX2NUBK": {
|
72
102
|
"model": "Echo Spot",
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|