aioamazondevices 6.0.0__tar.gz → 6.1.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.
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/PKG-INFO +1 -1
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/pyproject.toml +1 -1
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/src/aioamazondevices/__init__.py +1 -1
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/src/aioamazondevices/api.py +105 -188
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/src/aioamazondevices/const.py +28 -33
- aioamazondevices-6.1.1/src/aioamazondevices/query.py +91 -0
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/LICENSE +0 -0
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/README.md +0 -0
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/src/aioamazondevices/exceptions.py +0 -0
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/src/aioamazondevices/py.typed +0 -0
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/src/aioamazondevices/sounds.py +0 -0
- {aioamazondevices-6.0.0 → aioamazondevices-6.1.1}/src/aioamazondevices/utils.py +0 -0
@@ -22,7 +22,7 @@ from aiohttp import (
|
|
22
22
|
ClientSession,
|
23
23
|
)
|
24
24
|
from bs4 import BeautifulSoup, Tag
|
25
|
-
from langcodes import Language
|
25
|
+
from langcodes import Language, standardize_tag
|
26
26
|
from multidict import MultiDictProxy
|
27
27
|
from yarl import URL
|
28
28
|
|
@@ -48,18 +48,12 @@ from .const import (
|
|
48
48
|
HTTP_ERROR_199,
|
49
49
|
HTTP_ERROR_299,
|
50
50
|
JSON_EXTENSION,
|
51
|
-
NODE_BLUETOOTH,
|
52
|
-
NODE_DEVICES,
|
53
|
-
NODE_DO_NOT_DISTURB,
|
54
|
-
NODE_IDENTIFIER,
|
55
|
-
NODE_PREFERENCES,
|
56
51
|
REFRESH_ACCESS_TOKEN,
|
57
52
|
REFRESH_AUTH_COOKIES,
|
58
53
|
SAVE_PATH,
|
59
54
|
SENSORS,
|
60
|
-
|
61
|
-
|
62
|
-
URI_SENSORS,
|
55
|
+
URI_DEVICES,
|
56
|
+
URI_NEXUS_GRAPHQL,
|
63
57
|
URI_SIGNIN,
|
64
58
|
)
|
65
59
|
from .exceptions import (
|
@@ -69,6 +63,7 @@ from .exceptions import (
|
|
69
63
|
CannotRetrieveData,
|
70
64
|
WrongMethod,
|
71
65
|
)
|
66
|
+
from .query import QUERY_DEVICE_STATE
|
72
67
|
from .utils import obfuscate_email, scrub_fields
|
73
68
|
|
74
69
|
|
@@ -94,11 +89,8 @@ class AmazonDevice:
|
|
94
89
|
online: bool
|
95
90
|
serial_number: str
|
96
91
|
software_version: str
|
97
|
-
|
98
|
-
|
99
|
-
bluetooth_state: bool
|
100
|
-
entity_id: str
|
101
|
-
appliance_id: str
|
92
|
+
entity_id: str | None
|
93
|
+
endpoint_id: str | None
|
102
94
|
sensors: dict[str, AmazonDeviceSensor]
|
103
95
|
|
104
96
|
|
@@ -171,7 +163,8 @@ class AmazonEchoApi:
|
|
171
163
|
lang_maximized = lang_object.maximize()
|
172
164
|
|
173
165
|
self._domain: str = domain
|
174
|
-
|
166
|
+
language = f"{lang_maximized.language}-{lang_maximized.territory}"
|
167
|
+
self._language = standardize_tag(language)
|
175
168
|
|
176
169
|
# Reset CSRF cookie when changing country
|
177
170
|
self._csrf_cookie: str | None = None
|
@@ -326,14 +319,6 @@ class AmazonEchoApi:
|
|
326
319
|
)
|
327
320
|
return False
|
328
321
|
|
329
|
-
async def _ignore_phoenix_error(self, response: ClientResponse) -> bool:
|
330
|
-
"""Return true if error is due to phoenix endpoint."""
|
331
|
-
# Endpoint URI_IDS replies with error 199 or 299
|
332
|
-
# during maintenance
|
333
|
-
return response.status in [HTTP_ERROR_199, HTTP_ERROR_299] and (
|
334
|
-
URI_IDS in response.url.path
|
335
|
-
)
|
336
|
-
|
337
322
|
async def _http_phrase_error(self, error: int) -> str:
|
338
323
|
"""Convert numeric error in human phrase."""
|
339
324
|
if error == HTTP_ERROR_199:
|
@@ -444,9 +429,7 @@ class AmazonEchoApi:
|
|
444
429
|
HTTPStatus.UNAUTHORIZED,
|
445
430
|
]:
|
446
431
|
raise CannotAuthenticate(await self._http_phrase_error(resp.status))
|
447
|
-
if not await self._ignore_ap_signin_error(
|
448
|
-
resp
|
449
|
-
) and not await self._ignore_phoenix_error(resp):
|
432
|
+
if not await self._ignore_ap_signin_error(resp):
|
450
433
|
raise CannotRetrieveData(
|
451
434
|
f"Request failed: {await self._http_phrase_error(resp.status)}"
|
452
435
|
)
|
@@ -597,127 +580,80 @@ class AmazonEchoApi:
|
|
597
580
|
_LOGGER.info("Register device: %s", scrub_fields(login_data))
|
598
581
|
return login_data
|
599
582
|
|
600
|
-
async def
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
"Sensors data not available [%s error '%s'], skipping",
|
612
|
-
URI_IDS,
|
613
|
-
await self._http_phrase_error(raw_resp.status),
|
614
|
-
)
|
615
|
-
self._sensors_available = False
|
616
|
-
return []
|
617
|
-
|
618
|
-
json_data = await raw_resp.json()
|
619
|
-
|
620
|
-
network_detail = orjson.loads(json_data["networkDetail"])
|
621
|
-
# Navigate through the nested structure step by step
|
622
|
-
location_details = network_detail["locationDetails"]["locationDetails"]
|
623
|
-
default_location = location_details["Default_Location"]
|
624
|
-
amazon_bridge = default_location["amazonBridgeDetails"]["amazonBridgeDetails"]
|
625
|
-
|
626
|
-
# New devices are based on LambdaBridge_AAA structure
|
627
|
-
lambda_bridge_aaa = amazon_bridge.get("LambdaBridge_AAA/SonarCloudService")
|
628
|
-
appliance_details_aaa = (
|
629
|
-
lambda_bridge_aaa["applianceDetails"]["applianceDetails"]
|
630
|
-
if lambda_bridge_aaa
|
631
|
-
else {}
|
632
|
-
)
|
583
|
+
async def _get_devices_state(
|
584
|
+
self,
|
585
|
+
) -> dict[str, Any]:
|
586
|
+
"""Get Device State."""
|
587
|
+
payload = {
|
588
|
+
"operationName": "getDevicesState",
|
589
|
+
"variables": {
|
590
|
+
"latencyTolerance": "LOW",
|
591
|
+
},
|
592
|
+
"query": QUERY_DEVICE_STATE,
|
593
|
+
}
|
633
594
|
|
634
|
-
|
635
|
-
|
595
|
+
_, raw_resp = await self._session_request(
|
596
|
+
method=HTTPMethod.POST,
|
597
|
+
url=f"https://alexa.amazon.{self._domain}{URI_NEXUS_GRAPHQL}",
|
598
|
+
input_data=payload,
|
599
|
+
json_data=True,
|
636
600
|
)
|
637
601
|
|
638
|
-
|
639
|
-
for bridge_key, bridge_value in amazon_bridge.items():
|
640
|
-
if "LambdaBridge_AlexaBridge/" in bridge_key:
|
641
|
-
# Value key: "LambdaBridge_AlexaBridge/XXXXXXXXXXXXXX@XXXXXXXXXXXXXX"
|
642
|
-
# Value subkey: "AlexaBridge_XXXXXXXXXXXXXX@XXXXXXXXXXXXXX_XXXXXXXXXXXX"
|
643
|
-
subkey = bridge_key.split("_")[1].replace("/", "_")
|
644
|
-
|
645
|
-
appliance_details_alexa = bridge_value["applianceDetails"][
|
646
|
-
"applianceDetails"
|
647
|
-
]
|
648
|
-
entity_ids_list.extend(
|
649
|
-
await self._get_entities_ids(appliance_details_alexa, subkey)
|
650
|
-
)
|
651
|
-
|
652
|
-
return entity_ids_list
|
653
|
-
|
654
|
-
async def _get_entities_ids(
|
655
|
-
self, appliance_details: dict[str, Any], searchkey: str
|
656
|
-
) -> list[dict[str, str]]:
|
657
|
-
"""Extract entityId and applianceId."""
|
658
|
-
entity_ids_list: list[dict[str, str]] = []
|
659
|
-
# Process each appliance that starts with "searchkey"
|
660
|
-
for appliance_key, appliance_data in appliance_details.items():
|
661
|
-
if not appliance_key.startswith(searchkey):
|
662
|
-
continue
|
663
|
-
|
664
|
-
entity_id = appliance_data["entityId"]
|
665
|
-
appliance_id = appliance_data["applianceId"]
|
666
|
-
|
667
|
-
# Create identifier object for this appliance
|
668
|
-
identifier = {
|
669
|
-
"entityId": entity_id,
|
670
|
-
"applianceId": appliance_id,
|
671
|
-
}
|
672
|
-
|
673
|
-
# Update device information for each device in the identifier list
|
674
|
-
for device_identifier in appliance_data["alexaDeviceIdentifierList"]:
|
675
|
-
serial_number = device_identifier["dmsDeviceSerialNumber"]
|
676
|
-
|
677
|
-
# Add identifier information to the device
|
678
|
-
# but only if the device was previously found
|
679
|
-
if serial_number in self._devices:
|
680
|
-
self._devices[serial_number] |= {NODE_IDENTIFIER: identifier}
|
681
|
-
|
682
|
-
# Add to entity IDs list for sensor retrieval
|
683
|
-
entity_ids_list.append({"entityId": entity_id, "entityType": "ENTITY"})
|
684
|
-
|
685
|
-
return entity_ids_list
|
602
|
+
return cast("dict", await raw_resp.json())
|
686
603
|
|
687
604
|
async def _get_sensors_states(
|
688
|
-
self,
|
689
|
-
) -> dict[str, dict[str, AmazonDeviceSensor]]:
|
605
|
+
self,
|
606
|
+
) -> tuple[dict[str, dict[str, Any]], dict[str, dict[str, AmazonDeviceSensor]]]:
|
690
607
|
"""Retrieve devices sensors states."""
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
608
|
+
devices_state = await self._get_devices_state()
|
609
|
+
devices_sensors: dict[str, dict[str, AmazonDeviceSensor]] = {}
|
610
|
+
devices_endpoints: dict[str, dict[str, Any]] = {}
|
611
|
+
|
612
|
+
endpoints = devices_state["data"]["listEndpoints"]
|
613
|
+
for endpoint in endpoints.get("endpoints"):
|
614
|
+
serial_number = (
|
615
|
+
endpoint["serialNumber"]["value"]["text"]
|
616
|
+
if endpoint["serialNumber"]
|
617
|
+
else None
|
618
|
+
)
|
619
|
+
if serial_number in self._devices:
|
620
|
+
devices_sensors[serial_number] = self._get_device_sensor_state(endpoint)
|
621
|
+
devices_endpoints[serial_number] = endpoint
|
622
|
+
|
623
|
+
return devices_endpoints, devices_sensors
|
624
|
+
|
625
|
+
def _get_device_sensor_state(
|
626
|
+
self, endpoint: dict[str, Any]
|
627
|
+
) -> dict[str, AmazonDeviceSensor]:
|
628
|
+
device_sensors: dict[str, AmazonDeviceSensor] = {}
|
629
|
+
if (
|
630
|
+
endpoint_dnd := endpoint.get("settings", {}).get("doNotDisturb")
|
631
|
+
) and not endpoint_dnd["error"]:
|
632
|
+
device_sensors["dnd"] = AmazonDeviceSensor(
|
633
|
+
"dnd", endpoint_dnd.get("toggleValue"), None
|
634
|
+
)
|
635
|
+
for feature in endpoint.get("features", {}):
|
636
|
+
first_property = (feature.get("properties") or [None])[0] or {}
|
637
|
+
if (
|
638
|
+
first_property.get("type") != "RETRIEVABLE"
|
639
|
+
or (sensor := SENSORS.get(feature["name"])) is None
|
640
|
+
):
|
641
|
+
continue
|
699
642
|
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
714
|
-
value=(_value["value"] if _value_dict else _value),
|
715
|
-
scale=_value.get("scale") if _value_dict else None,
|
716
|
-
)
|
717
|
-
}
|
718
|
-
)
|
719
|
-
final_sensors.update({_id: dict_sensors})
|
720
|
-
return final_sensors
|
643
|
+
if not (name := sensor["name"]):
|
644
|
+
raise TypeError("Unable to read sensor template")
|
645
|
+
|
646
|
+
value = first_property[sensor["key"]]
|
647
|
+
scale = value["scale"] if sensor["scale"] else None
|
648
|
+
if subkey := sensor["subkey"]:
|
649
|
+
value = value[subkey]
|
650
|
+
device_sensors[name] = AmazonDeviceSensor(
|
651
|
+
name,
|
652
|
+
value,
|
653
|
+
scale,
|
654
|
+
)
|
655
|
+
|
656
|
+
return device_sensors
|
721
657
|
|
722
658
|
async def login_mode_interactive(self, otp_code: str) -> dict[str, Any]:
|
723
659
|
"""Login to Amazon interactively via OTP."""
|
@@ -857,68 +793,49 @@ class AmazonEchoApi:
|
|
857
793
|
) -> dict[str, AmazonDevice]:
|
858
794
|
"""Get Amazon devices data."""
|
859
795
|
self._devices = {}
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
)
|
865
|
-
|
866
|
-
response_data = await raw_resp.text()
|
867
|
-
json_data = {} if len(response_data) == 0 else await raw_resp.json()
|
796
|
+
_, raw_resp = await self._session_request(
|
797
|
+
method=HTTPMethod.GET,
|
798
|
+
url=f"https://alexa.amazon.{self._domain}{URI_DEVICES}",
|
799
|
+
)
|
868
800
|
|
869
|
-
|
801
|
+
response_data = await raw_resp.text()
|
802
|
+
json_data = {} if len(response_data) == 0 else await raw_resp.json()
|
870
803
|
|
871
|
-
|
872
|
-
dev_serial = data.get("serialNumber") or data.get("deviceSerialNumber")
|
873
|
-
if previous_data := self._devices.get(dev_serial):
|
874
|
-
self._devices[dev_serial] = previous_data | {key: data}
|
875
|
-
else:
|
876
|
-
self._devices[dev_serial] = {key: data}
|
804
|
+
_LOGGER.debug("JSON devices data: %s", scrub_fields(json_data))
|
877
805
|
|
878
|
-
|
806
|
+
for data in json_data["devices"]:
|
807
|
+
dev_serial = data.get("serialNumber")
|
808
|
+
self._devices[dev_serial] = data
|
879
809
|
|
880
|
-
|
881
|
-
entity_ids_list := await self._get_devices_ids()
|
882
|
-
):
|
883
|
-
devices_sensors = await self._get_sensors_states(entity_ids_list)
|
810
|
+
devices_endpoints, devices_sensors = await self._get_sensors_states()
|
884
811
|
|
885
812
|
final_devices_list: dict[str, AmazonDevice] = {}
|
886
813
|
for device in self._devices.values():
|
887
814
|
# Remove stale, orphaned and virtual devices
|
888
|
-
|
889
|
-
if not devices_node or (devices_node.get("deviceType") in DEVICE_TO_IGNORE):
|
815
|
+
if not device or (device.get("deviceType") in DEVICE_TO_IGNORE):
|
890
816
|
continue
|
891
817
|
|
892
|
-
|
893
|
-
do_not_disturb_node = device[NODE_DO_NOT_DISTURB]
|
894
|
-
bluetooth_node = device[NODE_BLUETOOTH]
|
895
|
-
identifier_node = device.get(NODE_IDENTIFIER, {})
|
896
|
-
|
818
|
+
serial_number: str = device["serialNumber"]
|
897
819
|
# Add sensors
|
898
|
-
sensors = {}
|
899
|
-
|
900
|
-
for _device_id, _device_sensors in devices_sensors.items():
|
901
|
-
if _device_id == identifier_node["entityId"]:
|
902
|
-
sensors = _device_sensors
|
820
|
+
sensors = devices_sensors.get(serial_number, {})
|
821
|
+
device_endpoint = devices_endpoints.get(serial_number, {})
|
903
822
|
|
904
|
-
serial_number: str = devices_node["serialNumber"]
|
905
823
|
final_devices_list[serial_number] = AmazonDevice(
|
906
|
-
account_name=
|
907
|
-
capabilities=
|
908
|
-
device_family=
|
909
|
-
device_type=
|
910
|
-
device_owner_customer_id=
|
911
|
-
device_cluster_members=(
|
912
|
-
|
913
|
-
),
|
914
|
-
online=devices_node["online"],
|
824
|
+
account_name=device["accountName"],
|
825
|
+
capabilities=device["capabilities"],
|
826
|
+
device_family=device["deviceFamily"],
|
827
|
+
device_type=device["deviceType"],
|
828
|
+
device_owner_customer_id=device["deviceOwnerCustomerId"],
|
829
|
+
device_cluster_members=(device["clusterMembers"] or [serial_number]),
|
830
|
+
online=device["online"],
|
915
831
|
serial_number=serial_number,
|
916
|
-
software_version=
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
832
|
+
software_version=device["softwareVersion"],
|
833
|
+
entity_id=device_endpoint["legacyIdentifiers"]["chrsIdentifier"][
|
834
|
+
"entityId"
|
835
|
+
]
|
836
|
+
if device_endpoint
|
837
|
+
else None,
|
838
|
+
endpoint_id=device_endpoint["endpointId"] if device_endpoint else None,
|
922
839
|
sensors=sensors,
|
923
840
|
)
|
924
841
|
|
@@ -54,41 +54,10 @@ CSRF_COOKIE = "csrf"
|
|
54
54
|
REFRESH_ACCESS_TOKEN = "access_token" # noqa: S105
|
55
55
|
REFRESH_AUTH_COOKIES = "auth_cookies"
|
56
56
|
|
57
|
-
|
58
|
-
NODE_DO_NOT_DISTURB = "doNotDisturbDeviceStatusList"
|
59
|
-
NODE_PREFERENCES = "devicePreferences"
|
60
|
-
NODE_BLUETOOTH = "bluetoothStates"
|
61
|
-
NODE_IDENTIFIER = "identifier"
|
62
|
-
NODE_SENSORS = "sensors"
|
63
|
-
|
64
|
-
URI_QUERIES = {
|
65
|
-
NODE_DEVICES: "/api/devices-v2/device",
|
66
|
-
NODE_DO_NOT_DISTURB: "/api/dnd/device-status-list",
|
67
|
-
NODE_PREFERENCES: "/api/device-preferences",
|
68
|
-
NODE_BLUETOOTH: "/api/bluetooth",
|
69
|
-
# "/api/ping"
|
70
|
-
# "/api/np/command"
|
71
|
-
# "/api/np/player"
|
72
|
-
# "/api/device-wifi-details"
|
73
|
-
# "/api/activities"
|
74
|
-
# "/api/behaviors/v2/automations"
|
75
|
-
# "/api/notifications"
|
76
|
-
}
|
77
|
-
|
57
|
+
URI_DEVICES = "/api/devices-v2/device"
|
78
58
|
URI_SIGNIN = "/ap/signin"
|
79
|
-
|
80
|
-
URI_SENSORS = "/api/phoenix/state"
|
59
|
+
URI_NEXUS_GRAPHQL = "/nexus/v1/graphql"
|
81
60
|
|
82
|
-
SENSORS = [
|
83
|
-
"babyCryDetectionState",
|
84
|
-
"beepingApplianceDetectionState",
|
85
|
-
"coughDetectionState",
|
86
|
-
"dogBarkDetectionState",
|
87
|
-
"humanPresenceDetectionState",
|
88
|
-
"illuminance",
|
89
|
-
"temperature",
|
90
|
-
"waterSoundsDetectionState",
|
91
|
-
]
|
92
61
|
SENSOR_STATE_OFF = "NOT_DETECTED"
|
93
62
|
|
94
63
|
# File extensions
|
@@ -100,6 +69,32 @@ BIN_EXTENSION = ".bin"
|
|
100
69
|
SPEAKER_GROUP_FAMILY = "WHA"
|
101
70
|
SPEAKER_GROUP_MODEL = "Speaker Group"
|
102
71
|
|
72
|
+
SENSORS: dict[str, dict[str, str | None]] = {
|
73
|
+
"temperatureSensor": {
|
74
|
+
"name": "temperature",
|
75
|
+
"key": "value",
|
76
|
+
"subkey": "value",
|
77
|
+
"scale": "scale",
|
78
|
+
},
|
79
|
+
"motionSensor": {
|
80
|
+
"name": "motion",
|
81
|
+
"key": "detectionStateValue",
|
82
|
+
"subkey": None,
|
83
|
+
"scale": None,
|
84
|
+
},
|
85
|
+
"lightSensor": {
|
86
|
+
"name": "illuminance",
|
87
|
+
"key": "illuminanceValue",
|
88
|
+
"subkey": "value",
|
89
|
+
"scale": None,
|
90
|
+
},
|
91
|
+
"speaker": {
|
92
|
+
"name": "volume",
|
93
|
+
"key": "value",
|
94
|
+
"subkey": "volValue",
|
95
|
+
"scale": None,
|
96
|
+
},
|
97
|
+
}
|
103
98
|
DEVICE_TO_IGNORE: list[str] = [
|
104
99
|
AMAZON_DEVICE_TYPE, # Alexa App for iOS
|
105
100
|
"A2TF17PFR55MTB", # Alexa App for Android
|
@@ -0,0 +1,91 @@
|
|
1
|
+
"""GraphQL Queries."""
|
2
|
+
|
3
|
+
QUERY_DEVICE_STATE = """
|
4
|
+
query getDevicesState ($latencyTolerance: LatencyToleranceValue) {
|
5
|
+
listEndpoints(listEndpointsInput: {}) {
|
6
|
+
endpoints {
|
7
|
+
endpointId: id
|
8
|
+
friendlyNameObject { value { text } }
|
9
|
+
manufacturer { value { text } }
|
10
|
+
model { value { text} }
|
11
|
+
serialNumber { value { text } }
|
12
|
+
softwareVersion { value { text } }
|
13
|
+
creationTime
|
14
|
+
enablement
|
15
|
+
settings {
|
16
|
+
doNotDisturb {
|
17
|
+
id
|
18
|
+
endpointId
|
19
|
+
name
|
20
|
+
toggleValue
|
21
|
+
error {
|
22
|
+
type
|
23
|
+
message
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
displayCategories {
|
28
|
+
all { value }
|
29
|
+
primary { value }
|
30
|
+
}
|
31
|
+
alexaEnabledMetadata {
|
32
|
+
iconId
|
33
|
+
isVisible
|
34
|
+
category
|
35
|
+
capabilities
|
36
|
+
}
|
37
|
+
legacyIdentifiers {
|
38
|
+
dmsIdentifier {
|
39
|
+
deviceType { value { text } }
|
40
|
+
}
|
41
|
+
chrsIdentifier { entityId }
|
42
|
+
}
|
43
|
+
legacyAppliance { applianceId }
|
44
|
+
associatedUnits { id }
|
45
|
+
connections {
|
46
|
+
type
|
47
|
+
macAddress
|
48
|
+
bleMeshDeviceUuid
|
49
|
+
}
|
50
|
+
features(latencyToleranceValue: $latencyTolerance) {
|
51
|
+
name
|
52
|
+
instance
|
53
|
+
properties {
|
54
|
+
name
|
55
|
+
type
|
56
|
+
accuracy
|
57
|
+
error { message }
|
58
|
+
__typename
|
59
|
+
... on Illuminance {
|
60
|
+
illuminanceValue { value }
|
61
|
+
timeOfSample
|
62
|
+
timeOfLastChange
|
63
|
+
}
|
64
|
+
... on Reachability {
|
65
|
+
reachabilityStatusValue
|
66
|
+
timeOfSample
|
67
|
+
timeOfLastChange
|
68
|
+
}
|
69
|
+
... on DetectionState {
|
70
|
+
detectionStateValue
|
71
|
+
timeOfSample
|
72
|
+
timeOfLastChange
|
73
|
+
}
|
74
|
+
... on Volume {
|
75
|
+
value { volValue: value }
|
76
|
+
}
|
77
|
+
... on TemperatureSensor {
|
78
|
+
name
|
79
|
+
value {
|
80
|
+
value
|
81
|
+
scale
|
82
|
+
}
|
83
|
+
timeOfSample
|
84
|
+
timeOfLastChange
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
91
|
+
"""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|