pycloudedge 0.1.4.dev1__tar.gz → 0.1.4.dev2__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.
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/PKG-INFO +1 -1
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/_version.py +3 -3
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/client.py +100 -37
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/pycloudedge.egg-info/PKG-INFO +1 -1
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/.env.example +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/.gitignore +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/LICENSE +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/MANIFEST.in +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/README.md +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/__init__.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/cli.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/constants.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/exceptions.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/iot_parameters.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/logging_config.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/utils.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/cloudedge/validators.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/examples/README.md +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/examples/basic_example.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/examples/device_control.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/examples/network_ping_status.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/pycloudedge.egg-info/SOURCES.txt +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/pycloudedge.egg-info/dependency_links.txt +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/pycloudedge.egg-info/entry_points.txt +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/pycloudedge.egg-info/requires.txt +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/pycloudedge.egg-info/top_level.txt +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/pyproject.toml +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/requirements-dev.txt +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/requirements.txt +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/setup.cfg +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/setup.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/tests/test_basic.py +0 -0
- {pycloudedge-0.1.4.dev1 → pycloudedge-0.1.4.dev2}/tests/test_improvements.py +0 -0
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.4.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1, 4, '
|
|
31
|
+
__version__ = version = '0.1.4.dev2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 4, 'dev2')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gf40c10343'
|
|
@@ -44,6 +44,54 @@ from .validators import validate_email, validate_country_code, validate_phone_co
|
|
|
44
44
|
from .logging_config import get_logger
|
|
45
45
|
from .utils import retry_on_failure
|
|
46
46
|
|
|
47
|
+
# API list keys → readable product type when deviceTypeName is a CDN image URL
|
|
48
|
+
_DEVICE_LIST_CATEGORY_LABELS = {
|
|
49
|
+
"snap": "Camera",
|
|
50
|
+
"ipc": "Camera",
|
|
51
|
+
"nvr": "NVR",
|
|
52
|
+
"doorbell": "Doorbell",
|
|
53
|
+
"chime": "Chime",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _device_icon_url_from_type_name(device_type_name: Any) -> Optional[str]:
|
|
58
|
+
"""Return URL if deviceTypeName is an http(s) icon URL (Meari/OSS), else None."""
|
|
59
|
+
if not isinstance(device_type_name, str):
|
|
60
|
+
return None
|
|
61
|
+
s = device_type_name.strip()
|
|
62
|
+
if s.startswith(("http://", "https://")):
|
|
63
|
+
return s
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _human_type_and_icon_url(
|
|
68
|
+
device: Dict[str, Any],
|
|
69
|
+
list_category: Optional[str] = None,
|
|
70
|
+
) -> tuple[str, Optional[str]]:
|
|
71
|
+
"""
|
|
72
|
+
CloudEdge often puts a product image URL in deviceTypeName instead of a label.
|
|
73
|
+
Return a human-readable type for UIs (e.g. Home Assistant model) and optional icon URL.
|
|
74
|
+
"""
|
|
75
|
+
raw = device.get("deviceTypeName")
|
|
76
|
+
icon_url = _device_icon_url_from_type_name(raw)
|
|
77
|
+
if icon_url is not None:
|
|
78
|
+
label = _DEVICE_LIST_CATEGORY_LABELS.get(list_category or "", "SmartEye Camera")
|
|
79
|
+
lower = icon_url.lower()
|
|
80
|
+
if "doorbell" in lower:
|
|
81
|
+
label = "Doorbell"
|
|
82
|
+
elif "chime" in lower:
|
|
83
|
+
label = "Chime"
|
|
84
|
+
elif "nvr" in lower:
|
|
85
|
+
label = "NVR"
|
|
86
|
+
elif "snap" in lower or "ipc" in lower:
|
|
87
|
+
label = "Camera"
|
|
88
|
+
return label, icon_url
|
|
89
|
+
if raw is not None and str(raw).strip():
|
|
90
|
+
return str(raw).strip(), None
|
|
91
|
+
if list_category:
|
|
92
|
+
return _DEVICE_LIST_CATEGORY_LABELS.get(list_category, "Unknown"), None
|
|
93
|
+
return "Unknown", None
|
|
94
|
+
|
|
47
95
|
|
|
48
96
|
class CloudEdgeClient:
|
|
49
97
|
"""
|
|
@@ -787,15 +835,19 @@ class CloudEdgeClient:
|
|
|
787
835
|
device_list = response_data[device_type]
|
|
788
836
|
if isinstance(device_list, list):
|
|
789
837
|
for device in device_list:
|
|
838
|
+
type_str, icon_url = _human_type_and_icon_url(
|
|
839
|
+
device, list_category=device_type
|
|
840
|
+
)
|
|
790
841
|
device_dict = {
|
|
791
842
|
'device_id': device.get('deviceID'),
|
|
792
843
|
'serial_number': device.get('snNum'),
|
|
793
844
|
'name': device.get('deviceName', 'Unnamed'),
|
|
794
|
-
'type':
|
|
845
|
+
'type': type_str,
|
|
795
846
|
'type_id': device.get('devTypeID'),
|
|
796
847
|
'host_key': device.get('hostKey'),
|
|
797
848
|
'online': device.get('devStatus') == 1, # Store original API status
|
|
798
|
-
'home_id': home_id
|
|
849
|
+
'home_id': home_id,
|
|
850
|
+
'device_icon_url': icon_url,
|
|
799
851
|
}
|
|
800
852
|
|
|
801
853
|
# Get enhanced online status
|
|
@@ -920,43 +972,54 @@ class CloudEdgeClient:
|
|
|
920
972
|
import json
|
|
921
973
|
self._log(f"API Response structure: {json.dumps(response_data, indent=2)}")
|
|
922
974
|
|
|
923
|
-
devices = []
|
|
924
|
-
|
|
925
|
-
# Check for devices in different device type keys (working format)
|
|
926
975
|
device_types = ['nvr', 'ipc', 'chime', 'doorbell', 'snap']
|
|
927
|
-
|
|
928
|
-
for device_type in device_types:
|
|
929
|
-
if device_type in response_data and response_data[device_type]:
|
|
930
|
-
device_list = response_data[device_type]
|
|
931
|
-
if isinstance(device_list, list):
|
|
932
|
-
self._log(f"Found {len(device_list)} devices under '{device_type}' key")
|
|
933
|
-
devices.extend(device_list)
|
|
934
|
-
|
|
935
|
-
# Fallback: check for devices in result.deviceList (older format)
|
|
936
|
-
if not devices:
|
|
937
|
-
device_list = response_data.get("result", {}).get("deviceList", [])
|
|
938
|
-
if isinstance(device_list, list) and device_list:
|
|
939
|
-
self._log(f"Found {len(device_list)} devices under 'result.deviceList' key")
|
|
940
|
-
devices.extend(device_list)
|
|
941
|
-
|
|
942
|
-
# Convert to standardized format
|
|
943
976
|
standardized_devices = []
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
977
|
+
|
|
978
|
+
# Modern API: devices grouped by category key
|
|
979
|
+
for device_type in device_types:
|
|
980
|
+
device_list = response_data.get(device_type)
|
|
981
|
+
if not isinstance(device_list, list) or not device_list:
|
|
982
|
+
continue
|
|
983
|
+
self._log(f"Found {len(device_list)} devices under '{device_type}' key")
|
|
984
|
+
for device in device_list:
|
|
985
|
+
type_str, icon_url = _human_type_and_icon_url(
|
|
986
|
+
device, list_category=device_type
|
|
987
|
+
)
|
|
988
|
+
device_dict = {
|
|
989
|
+
'device_id': device.get('deviceID'),
|
|
990
|
+
'serial_number': device.get('snNum'),
|
|
991
|
+
'name': device.get('deviceName', 'Unnamed'),
|
|
992
|
+
'type': type_str,
|
|
993
|
+
'type_id': device.get('devTypeID'),
|
|
994
|
+
'host_key': device.get('hostKey'),
|
|
995
|
+
'online': device.get('onLine') == 1,
|
|
996
|
+
'device_icon_url': icon_url,
|
|
997
|
+
}
|
|
998
|
+
device_dict['online'] = self._get_enhanced_device_status(device_dict)
|
|
999
|
+
standardized_devices.append(device_dict)
|
|
1000
|
+
|
|
1001
|
+
# Older API fallback: devices under result.deviceList
|
|
1002
|
+
if not standardized_devices:
|
|
1003
|
+
fallback_list = response_data.get("result", {}).get("deviceList", [])
|
|
1004
|
+
if isinstance(fallback_list, list) and fallback_list:
|
|
1005
|
+
self._log(f"Found {len(fallback_list)} devices under 'result.deviceList' key")
|
|
1006
|
+
for device in fallback_list:
|
|
1007
|
+
type_str, icon_url = _human_type_and_icon_url(
|
|
1008
|
+
device, list_category=None
|
|
1009
|
+
)
|
|
1010
|
+
device_dict = {
|
|
1011
|
+
'device_id': device.get('deviceID'),
|
|
1012
|
+
'serial_number': device.get('snNum'),
|
|
1013
|
+
'name': device.get('deviceName', 'Unnamed'),
|
|
1014
|
+
'type': type_str,
|
|
1015
|
+
'type_id': device.get('devTypeID'),
|
|
1016
|
+
'host_key': device.get('hostKey'),
|
|
1017
|
+
'online': device.get('onLine') == 1,
|
|
1018
|
+
'device_icon_url': icon_url,
|
|
1019
|
+
}
|
|
1020
|
+
device_dict['online'] = self._get_enhanced_device_status(device_dict)
|
|
1021
|
+
standardized_devices.append(device_dict)
|
|
1022
|
+
|
|
960
1023
|
return standardized_devices
|
|
961
1024
|
else:
|
|
962
1025
|
error_msg = response_data.get('resultMsg', 'Unknown error')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|