aioamazondevices 6.4.6__py3-none-any.whl → 6.5.1__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.
- aioamazondevices/__init__.py +1 -1
- aioamazondevices/api.py +247 -45
- aioamazondevices/const.py +57 -0
- aioamazondevices/query.py +46 -33
- {aioamazondevices-6.4.6.dist-info → aioamazondevices-6.5.1.dist-info}/METADATA +2 -1
- aioamazondevices-6.5.1.dist-info/RECORD +12 -0
- aioamazondevices-6.4.6.dist-info/RECORD +0 -12
- {aioamazondevices-6.4.6.dist-info → aioamazondevices-6.5.1.dist-info}/WHEEL +0 -0
- {aioamazondevices-6.4.6.dist-info → aioamazondevices-6.5.1.dist-info}/licenses/LICENSE +0 -0
aioamazondevices/__init__.py
CHANGED
aioamazondevices/api.py
CHANGED
|
@@ -23,6 +23,8 @@ from aiohttp import (
|
|
|
23
23
|
ContentTypeError,
|
|
24
24
|
)
|
|
25
25
|
from bs4 import BeautifulSoup, Tag
|
|
26
|
+
from dateutil.parser import parse
|
|
27
|
+
from dateutil.rrule import rrulestr
|
|
26
28
|
from langcodes import Language, standardize_tag
|
|
27
29
|
from multidict import MultiDictProxy
|
|
28
30
|
from yarl import URL
|
|
@@ -38,7 +40,9 @@ from .const import (
|
|
|
38
40
|
AMAZON_CLIENT_OS,
|
|
39
41
|
AMAZON_DEVICE_SOFTWARE_VERSION,
|
|
40
42
|
AMAZON_DEVICE_TYPE,
|
|
43
|
+
ARRAY_WRAPPER,
|
|
41
44
|
BIN_EXTENSION,
|
|
45
|
+
COUNTRY_GROUPS,
|
|
42
46
|
CSRF_COOKIE,
|
|
43
47
|
DEFAULT_HEADERS,
|
|
44
48
|
DEFAULT_SITE,
|
|
@@ -48,6 +52,11 @@ from .const import (
|
|
|
48
52
|
HTTP_ERROR_199,
|
|
49
53
|
HTTP_ERROR_299,
|
|
50
54
|
JSON_EXTENSION,
|
|
55
|
+
NOTIFICATION_ALARM,
|
|
56
|
+
NOTIFICATION_MUSIC_ALARM,
|
|
57
|
+
NOTIFICATION_REMINDER,
|
|
58
|
+
NOTIFICATION_TIMER,
|
|
59
|
+
RECURRING_PATTERNS,
|
|
51
60
|
REFRESH_ACCESS_TOKEN,
|
|
52
61
|
REFRESH_AUTH_COOKIES,
|
|
53
62
|
REQUEST_AGENT,
|
|
@@ -56,7 +65,9 @@ from .const import (
|
|
|
56
65
|
URI_DEVICES,
|
|
57
66
|
URI_DND,
|
|
58
67
|
URI_NEXUS_GRAPHQL,
|
|
68
|
+
URI_NOTIFICATIONS,
|
|
59
69
|
URI_SIGNIN,
|
|
70
|
+
WEEKEND_EXCEPTIONS,
|
|
60
71
|
)
|
|
61
72
|
from .exceptions import (
|
|
62
73
|
CannotAuthenticate,
|
|
@@ -81,6 +92,16 @@ class AmazonDeviceSensor:
|
|
|
81
92
|
scale: str | None
|
|
82
93
|
|
|
83
94
|
|
|
95
|
+
@dataclass
|
|
96
|
+
class AmazonSchedule:
|
|
97
|
+
"""Amazon schedule class."""
|
|
98
|
+
|
|
99
|
+
type: str # alarm, reminder, timer
|
|
100
|
+
status: str
|
|
101
|
+
label: str
|
|
102
|
+
next_occurrence: datetime | None
|
|
103
|
+
|
|
104
|
+
|
|
84
105
|
@dataclass
|
|
85
106
|
class AmazonDevice:
|
|
86
107
|
"""Amazon device class."""
|
|
@@ -98,6 +119,7 @@ class AmazonDevice:
|
|
|
98
119
|
entity_id: str | None
|
|
99
120
|
endpoint_id: str | None
|
|
100
121
|
sensors: dict[str, AmazonDeviceSensor]
|
|
122
|
+
notifications: dict[str, AmazonSchedule]
|
|
101
123
|
|
|
102
124
|
|
|
103
125
|
class AmazonSequenceType(StrEnum):
|
|
@@ -173,6 +195,7 @@ class AmazonEchoApi:
|
|
|
173
195
|
lang_object = Language.make(territory=country_code.upper())
|
|
174
196
|
lang_maximized = lang_object.maximize()
|
|
175
197
|
|
|
198
|
+
self._country_code: str = country_code
|
|
176
199
|
self._domain: str = domain
|
|
177
200
|
language = f"{lang_maximized.language}-{lang_maximized.territory}"
|
|
178
201
|
self._language = standardize_tag(language)
|
|
@@ -584,20 +607,19 @@ class AmazonEchoApi:
|
|
|
584
607
|
_LOGGER.info("Register device: %s", scrub_fields(login_data))
|
|
585
608
|
return login_data
|
|
586
609
|
|
|
587
|
-
async def
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
610
|
+
async def _get_sensors_states(self) -> dict[str, dict[str, AmazonDeviceSensor]]:
|
|
611
|
+
"""Retrieve devices sensors states."""
|
|
612
|
+
devices_sensors: dict[str, dict[str, AmazonDeviceSensor]] = {}
|
|
613
|
+
|
|
614
|
+
endpoint_ids = list(self._endpoints.keys())
|
|
591
615
|
payload = [
|
|
592
616
|
{
|
|
593
617
|
"operationName": "getEndpointState",
|
|
594
618
|
"variables": {
|
|
595
|
-
"
|
|
596
|
-
"latencyTolerance": "LOW",
|
|
619
|
+
"endpointIds": endpoint_ids,
|
|
597
620
|
},
|
|
598
621
|
"query": QUERY_SENSOR_STATE,
|
|
599
622
|
}
|
|
600
|
-
for endpoint_id in endpoint_id_list
|
|
601
623
|
]
|
|
602
624
|
|
|
603
625
|
_, raw_resp = await self._session_request(
|
|
@@ -607,47 +629,29 @@ class AmazonEchoApi:
|
|
|
607
629
|
json_data=True,
|
|
608
630
|
)
|
|
609
631
|
|
|
610
|
-
|
|
632
|
+
sensors_state = await self._response_to_json(raw_resp)
|
|
633
|
+
_LOGGER.debug("Sensor data - %s", sensors_state)
|
|
611
634
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
# batch endpoints into groups of 3 to reduce number of requests
|
|
617
|
-
endpoint_ids = list(self._endpoints.keys())
|
|
618
|
-
batches = [endpoint_ids[i : i + 3] for i in range(0, len(endpoint_ids), 3)]
|
|
619
|
-
for endpoint_id_batch in batches:
|
|
620
|
-
sensors_state = await self._get_sensors_state(endpoint_id_batch)
|
|
621
|
-
_LOGGER.debug("Sensor data - %s", sensors_state)
|
|
635
|
+
if await self._format_human_error(sensors_state):
|
|
636
|
+
# Explicit error in returned data
|
|
637
|
+
return {}
|
|
622
638
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
"Error retrieving devices state: %s for path %s", msg, path
|
|
632
|
-
)
|
|
633
|
-
return {}
|
|
639
|
+
if (
|
|
640
|
+
not (arr := sensors_state.get(ARRAY_WRAPPER))
|
|
641
|
+
or not (data := arr[0].get("data"))
|
|
642
|
+
or not (endpoints_list := data.get("listEndpoints"))
|
|
643
|
+
or not (endpoints := endpoints_list.get("endpoints"))
|
|
644
|
+
):
|
|
645
|
+
_LOGGER.error("Malformed sensor state data received: %s", sensors_state)
|
|
646
|
+
return {}
|
|
634
647
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
not isinstance(endpoint_data, dict)
|
|
638
|
-
or not (data := endpoint_data.get("data"))
|
|
639
|
-
or not (endpoint := data.get("endpoint"))
|
|
640
|
-
):
|
|
641
|
-
_LOGGER.error(
|
|
642
|
-
"Malformed sensor state data received: %s", endpoint_data
|
|
643
|
-
)
|
|
644
|
-
return {}
|
|
645
|
-
serial_number = self._endpoints[endpoint.get("endpointId")]
|
|
648
|
+
for endpoint in endpoints:
|
|
649
|
+
serial_number = self._endpoints[endpoint.get("endpointId")]
|
|
646
650
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
+
if serial_number in self._final_devices:
|
|
652
|
+
devices_sensors[serial_number] = self._get_device_sensor_state(
|
|
653
|
+
endpoint, serial_number
|
|
654
|
+
)
|
|
651
655
|
|
|
652
656
|
return devices_sensors
|
|
653
657
|
|
|
@@ -736,7 +740,7 @@ class AmazonEchoApi:
|
|
|
736
740
|
endpoint_data = await self._response_to_json(raw_resp)
|
|
737
741
|
|
|
738
742
|
if not (data := endpoint_data.get("data")) or not data.get("listEndpoints"):
|
|
739
|
-
|
|
743
|
+
await self._format_human_error(endpoint_data)
|
|
740
744
|
return {}
|
|
741
745
|
|
|
742
746
|
endpoints = data["listEndpoints"]
|
|
@@ -760,12 +764,173 @@ class AmazonEchoApi:
|
|
|
760
764
|
if not data:
|
|
761
765
|
_LOGGER.warning("Empty JSON data received")
|
|
762
766
|
data = {}
|
|
767
|
+
if isinstance(data, list):
|
|
768
|
+
# if anonymous array is returned wrap it inside
|
|
769
|
+
# generated key to convert list to dict
|
|
770
|
+
data = {ARRAY_WRAPPER: data}
|
|
763
771
|
return cast("dict[str, Any]", data)
|
|
764
772
|
except ContentTypeError as exc:
|
|
765
773
|
raise ValueError("Response not in JSON format") from exc
|
|
766
774
|
except orjson.JSONDecodeError as exc:
|
|
767
775
|
raise ValueError("Response with corrupted JSON format") from exc
|
|
768
776
|
|
|
777
|
+
async def _get_notifications(self) -> dict[str, dict[str, AmazonSchedule]]:
|
|
778
|
+
final_notifications: dict[str, dict[str, AmazonSchedule]] = {}
|
|
779
|
+
|
|
780
|
+
_, raw_resp = await self._session_request(
|
|
781
|
+
HTTPMethod.GET,
|
|
782
|
+
url=f"https://alexa.amazon.{self._domain}{URI_NOTIFICATIONS}",
|
|
783
|
+
)
|
|
784
|
+
notifications = await self._response_to_json(raw_resp)
|
|
785
|
+
for schedule in notifications["notifications"]:
|
|
786
|
+
schedule_type: str = schedule["type"]
|
|
787
|
+
schedule_device_serial = schedule["deviceSerialNumber"]
|
|
788
|
+
if schedule_type == NOTIFICATION_MUSIC_ALARM:
|
|
789
|
+
# Structure is the same as standard Alarm
|
|
790
|
+
schedule_type = NOTIFICATION_ALARM
|
|
791
|
+
schedule["type"] = NOTIFICATION_ALARM
|
|
792
|
+
label_desc = schedule_type.lower() + "Label"
|
|
793
|
+
if (schedule_status := schedule["status"]) == "ON" and (
|
|
794
|
+
next_occurrence := await self._parse_next_occurence(schedule)
|
|
795
|
+
):
|
|
796
|
+
schedule_notification_list = final_notifications.get(
|
|
797
|
+
schedule_device_serial, {}
|
|
798
|
+
)
|
|
799
|
+
schedule_notification_by_type = schedule_notification_list.get(
|
|
800
|
+
schedule_type
|
|
801
|
+
)
|
|
802
|
+
# Replace if no existing notification
|
|
803
|
+
# or if existing.next_occurrence is None
|
|
804
|
+
# or if new next_occurrence is earlier
|
|
805
|
+
if (
|
|
806
|
+
not schedule_notification_by_type
|
|
807
|
+
or schedule_notification_by_type.next_occurrence is None
|
|
808
|
+
or next_occurrence < schedule_notification_by_type.next_occurrence
|
|
809
|
+
):
|
|
810
|
+
final_notifications.update(
|
|
811
|
+
{
|
|
812
|
+
schedule_device_serial: {
|
|
813
|
+
**schedule_notification_list
|
|
814
|
+
| {
|
|
815
|
+
schedule_type: AmazonSchedule(
|
|
816
|
+
type=schedule_type,
|
|
817
|
+
status=schedule_status,
|
|
818
|
+
label=schedule[label_desc],
|
|
819
|
+
next_occurrence=next_occurrence,
|
|
820
|
+
),
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
return final_notifications
|
|
827
|
+
|
|
828
|
+
async def _parse_next_occurence(
|
|
829
|
+
self,
|
|
830
|
+
schedule: dict[str, Any],
|
|
831
|
+
) -> datetime | None:
|
|
832
|
+
"""Parse RFC5545 rule set for next iteration."""
|
|
833
|
+
# Local timezone
|
|
834
|
+
tzinfo = datetime.now().astimezone().tzinfo
|
|
835
|
+
# Current time
|
|
836
|
+
actual_time = datetime.now(tz=tzinfo)
|
|
837
|
+
# Reference start date
|
|
838
|
+
today_midnight = actual_time.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
839
|
+
# Reference time (1 minute ago to avoid edge cases)
|
|
840
|
+
now_reference = actual_time - timedelta(minutes=1)
|
|
841
|
+
|
|
842
|
+
# Schedule data
|
|
843
|
+
original_date = schedule.get("originalDate")
|
|
844
|
+
original_time = schedule.get("originalTime")
|
|
845
|
+
|
|
846
|
+
recurring_rules: list[str] = []
|
|
847
|
+
if schedule.get("rRuleData"):
|
|
848
|
+
recurring_rules = schedule["rRuleData"]["recurrenceRules"]
|
|
849
|
+
if schedule.get("recurringPattern"):
|
|
850
|
+
recurring_rules.append(schedule["recurringPattern"])
|
|
851
|
+
|
|
852
|
+
# Recurring events
|
|
853
|
+
if recurring_rules:
|
|
854
|
+
next_candidates: list[datetime] = []
|
|
855
|
+
for recurring_rule in recurring_rules:
|
|
856
|
+
# Already in RFC5545 format
|
|
857
|
+
if "FREQ=" in recurring_rule:
|
|
858
|
+
rule = await self._add_hours_minutes(recurring_rule, original_time)
|
|
859
|
+
|
|
860
|
+
# Add date to candidates list
|
|
861
|
+
next_candidates.append(
|
|
862
|
+
rrulestr(rule, dtstart=today_midnight).after(
|
|
863
|
+
now_reference, True
|
|
864
|
+
),
|
|
865
|
+
)
|
|
866
|
+
continue
|
|
867
|
+
|
|
868
|
+
if recurring_rule not in RECURRING_PATTERNS:
|
|
869
|
+
_LOGGER.warning("Unknown recurring rule: %s", recurring_rule)
|
|
870
|
+
return None
|
|
871
|
+
|
|
872
|
+
# Adjust recurring rules for country specific weekend exceptions
|
|
873
|
+
recurring_pattern = RECURRING_PATTERNS.copy()
|
|
874
|
+
for group, countries in COUNTRY_GROUPS.items():
|
|
875
|
+
if self._country_code in countries:
|
|
876
|
+
recurring_pattern |= WEEKEND_EXCEPTIONS[group]
|
|
877
|
+
break
|
|
878
|
+
|
|
879
|
+
rule = await self._add_hours_minutes(
|
|
880
|
+
recurring_pattern[recurring_rule], original_time
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
# Add date to candidates list
|
|
884
|
+
next_candidates.append(
|
|
885
|
+
rrulestr(rule, dtstart=today_midnight).after(now_reference, True),
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
return min(next_candidates) if next_candidates else None
|
|
889
|
+
|
|
890
|
+
# Single events
|
|
891
|
+
if schedule["type"] == NOTIFICATION_ALARM:
|
|
892
|
+
timestamp = parse(f"{original_date} {original_time}").replace(tzinfo=tzinfo)
|
|
893
|
+
|
|
894
|
+
elif schedule["type"] == NOTIFICATION_TIMER:
|
|
895
|
+
# API returns triggerTime in milliseconds since epoch
|
|
896
|
+
timestamp = datetime.fromtimestamp(
|
|
897
|
+
schedule["triggerTime"] / 1000, tz=tzinfo
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
elif schedule["type"] == NOTIFICATION_REMINDER:
|
|
901
|
+
# API returns alarmTime in milliseconds since epoch
|
|
902
|
+
timestamp = datetime.fromtimestamp(schedule["alarmTime"] / 1000, tz=tzinfo)
|
|
903
|
+
|
|
904
|
+
else:
|
|
905
|
+
_LOGGER.warning(("Unknown schedule type: %s"), schedule["type"])
|
|
906
|
+
return None
|
|
907
|
+
|
|
908
|
+
if timestamp > now_reference:
|
|
909
|
+
return timestamp
|
|
910
|
+
|
|
911
|
+
return None
|
|
912
|
+
|
|
913
|
+
async def _add_hours_minutes(
|
|
914
|
+
self,
|
|
915
|
+
recurring_rule: str,
|
|
916
|
+
original_time: str | None,
|
|
917
|
+
) -> str:
|
|
918
|
+
"""Add hours and minutes to a RFC5545 string."""
|
|
919
|
+
rule = recurring_rule.removesuffix(";")
|
|
920
|
+
|
|
921
|
+
if not original_time:
|
|
922
|
+
return rule
|
|
923
|
+
|
|
924
|
+
# Add missing BYHOUR, BYMINUTE if needed (Alarms only)
|
|
925
|
+
if "BYHOUR=" not in recurring_rule:
|
|
926
|
+
hour = int(original_time.split(":")[0])
|
|
927
|
+
rule += f";BYHOUR={hour}"
|
|
928
|
+
if "BYMINUTE=" not in recurring_rule:
|
|
929
|
+
minute = int(original_time.split(":")[1])
|
|
930
|
+
rule += f";BYMINUTE={minute}"
|
|
931
|
+
|
|
932
|
+
return rule
|
|
933
|
+
|
|
769
934
|
async def login_mode_interactive(self, otp_code: str) -> dict[str, Any]:
|
|
770
935
|
"""Login to Amazon interactively via OTP."""
|
|
771
936
|
_LOGGER.debug(
|
|
@@ -965,6 +1130,7 @@ class AmazonEchoApi:
|
|
|
965
1130
|
async def _get_sensor_data(self) -> None:
|
|
966
1131
|
devices_sensors = await self._get_sensors_states()
|
|
967
1132
|
dnd_sensors = await self._get_dnd_status()
|
|
1133
|
+
notifications = await self._get_notifications()
|
|
968
1134
|
for device in self._final_devices.values():
|
|
969
1135
|
# Update sensors
|
|
970
1136
|
sensors = devices_sensors.get(device.serial_number, {})
|
|
@@ -976,6 +1142,26 @@ class AmazonEchoApi:
|
|
|
976
1142
|
if device_dnd := dnd_sensors.get(device.serial_number):
|
|
977
1143
|
device.sensors["dnd"] = device_dnd
|
|
978
1144
|
|
|
1145
|
+
# Update notifications
|
|
1146
|
+
device_notifications = notifications.get(device.serial_number, {})
|
|
1147
|
+
|
|
1148
|
+
# Add only supported notification types
|
|
1149
|
+
for capability, notification_type in [
|
|
1150
|
+
("REMINDERS", NOTIFICATION_REMINDER),
|
|
1151
|
+
("TIMERS_AND_ALARMS", NOTIFICATION_ALARM),
|
|
1152
|
+
("TIMERS_AND_ALARMS", NOTIFICATION_TIMER),
|
|
1153
|
+
]:
|
|
1154
|
+
if (
|
|
1155
|
+
capability in device.capabilities
|
|
1156
|
+
and notification_type in device_notifications
|
|
1157
|
+
and (
|
|
1158
|
+
notification_object := device_notifications.get(
|
|
1159
|
+
notification_type
|
|
1160
|
+
)
|
|
1161
|
+
)
|
|
1162
|
+
):
|
|
1163
|
+
device.notifications[notification_type] = notification_object
|
|
1164
|
+
|
|
979
1165
|
async def _set_device_endpoints_data(self) -> None:
|
|
980
1166
|
"""Set device endpoint data."""
|
|
981
1167
|
devices_endpoints = await self._get_devices_endpoint_data()
|
|
@@ -1039,6 +1225,7 @@ class AmazonEchoApi:
|
|
|
1039
1225
|
entity_id=None,
|
|
1040
1226
|
endpoint_id=None,
|
|
1041
1227
|
sensors={},
|
|
1228
|
+
notifications={},
|
|
1042
1229
|
)
|
|
1043
1230
|
|
|
1044
1231
|
self._list_for_clusters.update(
|
|
@@ -1375,3 +1562,18 @@ class AmazonEchoApi:
|
|
|
1375
1562
|
scale=None,
|
|
1376
1563
|
)
|
|
1377
1564
|
return dnd_status
|
|
1565
|
+
|
|
1566
|
+
async def _format_human_error(self, sensors_state: dict) -> bool:
|
|
1567
|
+
"""Format human readable error from malformed data."""
|
|
1568
|
+
if sensors_state.get(ARRAY_WRAPPER):
|
|
1569
|
+
error = sensors_state[ARRAY_WRAPPER][0].get("errors", [])
|
|
1570
|
+
else:
|
|
1571
|
+
error = sensors_state.get("errors", [])
|
|
1572
|
+
|
|
1573
|
+
if not error:
|
|
1574
|
+
return False
|
|
1575
|
+
|
|
1576
|
+
msg = error[0].get("message", "Unknown error")
|
|
1577
|
+
path = error[0].get("path", "Unknown path")
|
|
1578
|
+
_LOGGER.error("Error retrieving devices state: %s for path %s", msg, path)
|
|
1579
|
+
return True
|
aioamazondevices/const.py
CHANGED
|
@@ -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
|
|
|
@@ -54,6 +56,7 @@ REFRESH_AUTH_COOKIES = "auth_cookies"
|
|
|
54
56
|
|
|
55
57
|
URI_DEVICES = "/api/devices-v2/device"
|
|
56
58
|
URI_DND = "/api/dnd/device-status-list"
|
|
59
|
+
URI_NOTIFICATIONS = "/api/notifications"
|
|
57
60
|
URI_SIGNIN = "/ap/signin"
|
|
58
61
|
URI_NEXUS_GRAPHQL = "/nexus/v1/graphql"
|
|
59
62
|
|
|
@@ -477,3 +480,57 @@ DEVICE_TYPE_TO_MODEL: dict[str, dict[str, str | None]] = {
|
|
|
477
480
|
"hw_version": "Gen2",
|
|
478
481
|
},
|
|
479
482
|
}
|
|
483
|
+
|
|
484
|
+
RECURRING_PATTERNS: dict[str, str] = {
|
|
485
|
+
"XXXX-WD": "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
|
|
486
|
+
"XXXX-WE": "FREQ=WEEKLY;BYDAY=SA,SU",
|
|
487
|
+
"XXXX-WXX-1": "FREQ=WEEKLY;BYDAY=MO",
|
|
488
|
+
"XXXX-WXX-2": "FREQ=WEEKLY;BYDAY=TU",
|
|
489
|
+
"XXXX-WXX-3": "FREQ=WEEKLY;BYDAY=WE",
|
|
490
|
+
"XXXX-WXX-4": "FREQ=WEEKLY;BYDAY=TH",
|
|
491
|
+
"XXXX-WXX-5": "FREQ=WEEKLY;BYDAY=FR",
|
|
492
|
+
"XXXX-WXX-6": "FREQ=WEEKLY;BYDAY=SA",
|
|
493
|
+
"XXXX-WXX-7": "FREQ=WEEKLY;BYDAY=SU",
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
WEEKEND_EXCEPTIONS = {
|
|
497
|
+
"TH-FR": {
|
|
498
|
+
"XXXX-WD": "FREQ=WEEKLY;BYDAY=MO,TU,WE,SA,SU",
|
|
499
|
+
"XXXX-WE": "FREQ=WEEKLY;BYDAY=TH,FR",
|
|
500
|
+
},
|
|
501
|
+
"FR-SA": {
|
|
502
|
+
"XXXX-WD": "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,SU",
|
|
503
|
+
"XXXX-WE": "FREQ=WEEKLY;BYDAY=FR,SA",
|
|
504
|
+
},
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
# Countries grouped by their weekend type
|
|
508
|
+
COUNTRY_GROUPS = {
|
|
509
|
+
"TH-FR": ["IR"],
|
|
510
|
+
"FR-SA": [
|
|
511
|
+
"AF",
|
|
512
|
+
"BD",
|
|
513
|
+
"BH",
|
|
514
|
+
"DZ",
|
|
515
|
+
"EG",
|
|
516
|
+
"IL",
|
|
517
|
+
"IQ",
|
|
518
|
+
"JO",
|
|
519
|
+
"KW",
|
|
520
|
+
"LY",
|
|
521
|
+
"MV",
|
|
522
|
+
"MY",
|
|
523
|
+
"OM",
|
|
524
|
+
"PS",
|
|
525
|
+
"QA",
|
|
526
|
+
"SA",
|
|
527
|
+
"SD",
|
|
528
|
+
"SY",
|
|
529
|
+
"YE",
|
|
530
|
+
],
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
NOTIFICATION_ALARM = "Alarm"
|
|
534
|
+
NOTIFICATION_MUSIC_ALARM = "MusicAlarm"
|
|
535
|
+
NOTIFICATION_REMINDER = "Reminder"
|
|
536
|
+
NOTIFICATION_TIMER = "Timer"
|
aioamazondevices/query.py
CHANGED
|
@@ -41,44 +41,57 @@ query getDevicesBaseData {
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
QUERY_SENSOR_STATE = """
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
fragment EndpointState on Endpoint {
|
|
45
|
+
endpointId: id
|
|
46
|
+
friendlyNameObject { value { text } }
|
|
47
|
+
features {
|
|
48
|
+
name
|
|
49
|
+
properties {
|
|
48
50
|
name
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aioamazondevices
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.5.1
|
|
4
4
|
Summary: Python library to control Amazon devices
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -17,6 +17,7 @@ Requires-Dist: beautifulsoup4
|
|
|
17
17
|
Requires-Dist: colorlog
|
|
18
18
|
Requires-Dist: langcodes
|
|
19
19
|
Requires-Dist: orjson (>=3.10,<4)
|
|
20
|
+
Requires-Dist: python-dateutil
|
|
20
21
|
Project-URL: Bug Tracker, https://github.com/chemelli74/aioamazondevices/issues
|
|
21
22
|
Project-URL: Changelog, https://github.com/chemelli74/aioamazondevices/blob/main/CHANGELOG.md
|
|
22
23
|
Project-URL: Homepage, https://github.com/chemelli74/aioamazondevices
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
aioamazondevices/__init__.py,sha256=F9PVHDhnnAwbsRZGVQ6jRtVcKcvPsK2zm7IZVRX_p64,276
|
|
2
|
+
aioamazondevices/api.py,sha256=4ut_LNMO4nHD1Fw92XP7hXzTdcOnnYeQC5GynqvSbqk,58409
|
|
3
|
+
aioamazondevices/const.py,sha256=EUoGr9gqqwoSz01gk56Nhw_-1Tdmv0Yjyh9cJb3ofbs,13556
|
|
4
|
+
aioamazondevices/exceptions.py,sha256=gRYrxNAJnrV6uRuMx5e76VMvtNKyceXd09q84pDBBrI,638
|
|
5
|
+
aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
aioamazondevices/query.py,sha256=xVgXF1PIiH7uu33Q_iVfCtdH9hqLOAGdCNnAxTZ76UE,1883
|
|
7
|
+
aioamazondevices/sounds.py,sha256=CXMDk-KoKVFxBdVAw3MeOClqgpzcVDxvQhFOJp7qX-Y,1896
|
|
8
|
+
aioamazondevices/utils.py,sha256=RzuKRhnq_8ymCoJMoQJ2vBYyuew06RSWpqQWmqdNczE,2019
|
|
9
|
+
aioamazondevices-6.5.1.dist-info/METADATA,sha256=2XGsEjNlJpCKx4ffTMoEO_WjRnEuuU693ST-pRE0y1Q,7679
|
|
10
|
+
aioamazondevices-6.5.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
11
|
+
aioamazondevices-6.5.1.dist-info/licenses/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
|
|
12
|
+
aioamazondevices-6.5.1.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
aioamazondevices/__init__.py,sha256=G3JnYhlhatrCtpn_93BQbFjsfMMhklpDM4ACRD_F0Jw,276
|
|
2
|
-
aioamazondevices/api.py,sha256=VaOGSfUmpy8hRF7vISGoalZbyn2RqRpelDN9LepkPbw,50685
|
|
3
|
-
aioamazondevices/const.py,sha256=BZTyUku94uQa50R1ZeXo1h585xgUNT_Pb7KAifjawWc,12266
|
|
4
|
-
aioamazondevices/exceptions.py,sha256=gRYrxNAJnrV6uRuMx5e76VMvtNKyceXd09q84pDBBrI,638
|
|
5
|
-
aioamazondevices/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
aioamazondevices/query.py,sha256=SKn-fXFUnXnCvmKd6IvAGdkFL7sBzhYBEAZ0aZ2ez9E,1800
|
|
7
|
-
aioamazondevices/sounds.py,sha256=CXMDk-KoKVFxBdVAw3MeOClqgpzcVDxvQhFOJp7qX-Y,1896
|
|
8
|
-
aioamazondevices/utils.py,sha256=RzuKRhnq_8ymCoJMoQJ2vBYyuew06RSWpqQWmqdNczE,2019
|
|
9
|
-
aioamazondevices-6.4.6.dist-info/METADATA,sha256=e7_GTeYgN9fJp_P4o2Spft3iE2oY78DDaQtpQlakT4c,7648
|
|
10
|
-
aioamazondevices-6.4.6.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
11
|
-
aioamazondevices-6.4.6.dist-info/licenses/LICENSE,sha256=sS48k5sp9bFV-NSHDfAJuTZZ_-AP9ZDqUzQ9sffGlsg,11346
|
|
12
|
-
aioamazondevices-6.4.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|