pywemo 1.4.0__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.
- pywemo/README.md +69 -0
- pywemo/__init__.py +33 -0
- pywemo/color.py +79 -0
- pywemo/discovery.py +194 -0
- pywemo/exceptions.py +94 -0
- pywemo/ouimeaux_device/LICENSE +12 -0
- pywemo/ouimeaux_device/__init__.py +679 -0
- pywemo/ouimeaux_device/api/__init__.py +1 -0
- pywemo/ouimeaux_device/api/attributes.py +131 -0
- pywemo/ouimeaux_device/api/db_orm.py +197 -0
- pywemo/ouimeaux_device/api/long_press.py +168 -0
- pywemo/ouimeaux_device/api/rules_db.py +467 -0
- pywemo/ouimeaux_device/api/service.py +363 -0
- pywemo/ouimeaux_device/api/wemo_services.py +25 -0
- pywemo/ouimeaux_device/api/wemo_services.pyi +241 -0
- pywemo/ouimeaux_device/api/xsd/__init__.py +1 -0
- pywemo/ouimeaux_device/api/xsd/device.py +3888 -0
- pywemo/ouimeaux_device/api/xsd/device.xsd +95 -0
- pywemo/ouimeaux_device/api/xsd/service.py +3872 -0
- pywemo/ouimeaux_device/api/xsd/service.xsd +93 -0
- pywemo/ouimeaux_device/api/xsd_types.py +222 -0
- pywemo/ouimeaux_device/bridge.py +506 -0
- pywemo/ouimeaux_device/coffeemaker.py +92 -0
- pywemo/ouimeaux_device/crockpot.py +157 -0
- pywemo/ouimeaux_device/dimmer.py +70 -0
- pywemo/ouimeaux_device/humidifier.py +223 -0
- pywemo/ouimeaux_device/insight.py +191 -0
- pywemo/ouimeaux_device/lightswitch.py +11 -0
- pywemo/ouimeaux_device/maker.py +54 -0
- pywemo/ouimeaux_device/motion.py +6 -0
- pywemo/ouimeaux_device/outdoor_plug.py +6 -0
- pywemo/ouimeaux_device/switch.py +32 -0
- pywemo/py.typed +0 -0
- pywemo/ssdp.py +372 -0
- pywemo/subscribe.py +782 -0
- pywemo/util.py +139 -0
- pywemo-1.4.0.dist-info/LICENSE +54 -0
- pywemo-1.4.0.dist-info/METADATA +192 -0
- pywemo-1.4.0.dist-info/RECORD +40 -0
- pywemo-1.4.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
|
2
|
+
xmlns="urn:schemas-upnp-org:service-1-0"
|
|
3
|
+
targetNamespace="urn:schemas-upnp-org:service-1-0"
|
|
4
|
+
elementFormDefault="qualified">
|
|
5
|
+
|
|
6
|
+
<xs:annotation>
|
|
7
|
+
<xs:documentation>
|
|
8
|
+
XML Schema for UPnP service descriptions in real XSD format
|
|
9
|
+
(not like the XDR one from Microsoft)
|
|
10
|
+
Created by Michael Weinrich 2007
|
|
11
|
+
</xs:documentation>
|
|
12
|
+
</xs:annotation>
|
|
13
|
+
|
|
14
|
+
<xs:element name="scpd">
|
|
15
|
+
<xs:complexType>
|
|
16
|
+
<xs:all>
|
|
17
|
+
<xs:element name="specVersion" type="SpecVersionType" minOccurs="1" maxOccurs="1" />
|
|
18
|
+
<xs:element name="actionList" type="ActionListType" minOccurs="0" maxOccurs="1" />
|
|
19
|
+
<xs:element name="serviceStateTable" type="ServiceStateTableType" minOccurs="1" maxOccurs="1" />
|
|
20
|
+
</xs:all>
|
|
21
|
+
</xs:complexType>
|
|
22
|
+
</xs:element>
|
|
23
|
+
|
|
24
|
+
<xs:complexType name="SpecVersionType">
|
|
25
|
+
<xs:all>
|
|
26
|
+
<xs:element name="major" type="xs:int" minOccurs="1" />
|
|
27
|
+
<xs:element name="minor" type="xs:int" minOccurs="1" />
|
|
28
|
+
</xs:all>
|
|
29
|
+
</xs:complexType>
|
|
30
|
+
|
|
31
|
+
<xs:complexType name="ActionListType">
|
|
32
|
+
<xs:sequence>
|
|
33
|
+
<xs:element name="action" type="ActionType" minOccurs="0" maxOccurs="unbounded" />
|
|
34
|
+
</xs:sequence>
|
|
35
|
+
</xs:complexType>
|
|
36
|
+
|
|
37
|
+
<xs:complexType name="ActionType">
|
|
38
|
+
<xs:all>
|
|
39
|
+
<xs:element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
40
|
+
<xs:element name="argumentList" type="ArgumentListType" minOccurs="0" maxOccurs="1" />
|
|
41
|
+
</xs:all>
|
|
42
|
+
</xs:complexType>
|
|
43
|
+
|
|
44
|
+
<xs:complexType name="ArgumentListType">
|
|
45
|
+
<xs:sequence>
|
|
46
|
+
<xs:element name="argument" type="ArgumentType" minOccurs="1" maxOccurs="unbounded" />
|
|
47
|
+
</xs:sequence>
|
|
48
|
+
</xs:complexType>
|
|
49
|
+
|
|
50
|
+
<xs:complexType name="ArgumentType">
|
|
51
|
+
<xs:all>
|
|
52
|
+
<xs:element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
53
|
+
<xs:element name="direction" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
54
|
+
<xs:element name="relatedStateVariable" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
55
|
+
<xs:element name="retval" minOccurs="0" maxOccurs="1">
|
|
56
|
+
<xs:complexType>
|
|
57
|
+
</xs:complexType>
|
|
58
|
+
</xs:element>
|
|
59
|
+
</xs:all>
|
|
60
|
+
</xs:complexType>
|
|
61
|
+
|
|
62
|
+
<xs:complexType name="ServiceStateTableType">
|
|
63
|
+
<xs:sequence>
|
|
64
|
+
<xs:element name="stateVariable" type="StateVariableType" minOccurs="1" maxOccurs="unbounded" />
|
|
65
|
+
</xs:sequence>
|
|
66
|
+
</xs:complexType>
|
|
67
|
+
|
|
68
|
+
<xs:complexType name="StateVariableType">
|
|
69
|
+
<xs:all>
|
|
70
|
+
<xs:element name="name" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
71
|
+
<xs:element name="dataType" type="xs:string" minOccurs="1" maxOccurs="1" />
|
|
72
|
+
<xs:element name="defaultValue" type="xs:string" minOccurs="0" maxOccurs="1" />
|
|
73
|
+
<xs:element name="allowedValueList" type="AllowedValueListType" minOccurs="0" maxOccurs="1" />
|
|
74
|
+
<xs:element name="allowedValueRange" type="AllowedValueRangeType" minOccurs="0" maxOccurs="1" />
|
|
75
|
+
</xs:all>
|
|
76
|
+
<xs:attribute name="sendEvents" type="xs:string" default="yes" />
|
|
77
|
+
</xs:complexType>
|
|
78
|
+
|
|
79
|
+
<xs:complexType name="AllowedValueListType">
|
|
80
|
+
<xs:sequence>
|
|
81
|
+
<xs:element name="allowedValue" type="xs:string" minOccurs="1" maxOccurs="unbounded" />
|
|
82
|
+
</xs:sequence>
|
|
83
|
+
</xs:complexType>
|
|
84
|
+
|
|
85
|
+
<xs:complexType name="AllowedValueRangeType">
|
|
86
|
+
<xs:all>
|
|
87
|
+
<xs:element name="minimum" type="xs:decimal" minOccurs="1" maxOccurs="1" />
|
|
88
|
+
<xs:element name="maximum" type="xs:decimal" minOccurs="1" maxOccurs="1" />
|
|
89
|
+
<xs:element name="step" type="xs:decimal" minOccurs="0" maxOccurs="1" />
|
|
90
|
+
</xs:all>
|
|
91
|
+
</xs:complexType>
|
|
92
|
+
|
|
93
|
+
</xs:schema>
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Parsing and validation for Device/Service XML.
|
|
2
|
+
|
|
3
|
+
Provides a light wrapper around the generated device & service parsers. The
|
|
4
|
+
wrappers check for required fields and values. Default values are also
|
|
5
|
+
provided for optional fields. Clients of this module can expect that all
|
|
6
|
+
fields of the dataclass instances are fully populated and valid. Any parsing
|
|
7
|
+
or validation issues will result in InvalidSchemaError being raised.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any, Dict, List
|
|
14
|
+
|
|
15
|
+
from lxml import etree as et
|
|
16
|
+
|
|
17
|
+
from pywemo.exceptions import InvalidSchemaError
|
|
18
|
+
|
|
19
|
+
from .xsd import device as device_parser
|
|
20
|
+
from .xsd import service as service_parser
|
|
21
|
+
|
|
22
|
+
LOG = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def quote_xml(xml: str) -> str:
|
|
26
|
+
"""Escape markup chars, but do not modify CDATA sections."""
|
|
27
|
+
return device_parser.quote_xml(xml) # type: ignore
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class ArgumentType:
|
|
32
|
+
"""Parsed service_parser.ArgumentType."""
|
|
33
|
+
|
|
34
|
+
name: str
|
|
35
|
+
direction: str
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_argument(
|
|
39
|
+
cls, argument: service_parser.ArgumentType
|
|
40
|
+
) -> ArgumentType:
|
|
41
|
+
"""Parse and validate the service_parser.ArgumentType."""
|
|
42
|
+
return cls(
|
|
43
|
+
name=_get_element_text(argument, "name", ""),
|
|
44
|
+
direction=_get_element_text(argument, "direction", ""),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class ActionProperties:
|
|
50
|
+
"""Parsed service_parser.ActionType."""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
arguments: list[ArgumentType]
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def from_action(
|
|
57
|
+
cls, action: service_parser.ActionType
|
|
58
|
+
) -> ActionProperties:
|
|
59
|
+
"""Parse and validate the service_parser.ActionType."""
|
|
60
|
+
arguments: list[service_parser.ArgumentType] = []
|
|
61
|
+
if action.argumentList and action.argumentList.argument:
|
|
62
|
+
arguments = action.argumentList.argument
|
|
63
|
+
|
|
64
|
+
return cls(
|
|
65
|
+
name=_get_element_text(action, "name"),
|
|
66
|
+
arguments=[
|
|
67
|
+
ArgumentType.from_argument(argument) for argument in arguments
|
|
68
|
+
],
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True)
|
|
73
|
+
class ServiceDescription:
|
|
74
|
+
"""Parsed service_parser.scpd."""
|
|
75
|
+
|
|
76
|
+
actions: list[ActionProperties]
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_xml(cls, service_xml_content: bytes) -> ServiceDescription:
|
|
80
|
+
"""Parse and validate the service_parser.scpd."""
|
|
81
|
+
try:
|
|
82
|
+
scpd = service_parser.parseString( # type: ignore
|
|
83
|
+
service_xml_content, silence=True, print_warnings=False
|
|
84
|
+
)
|
|
85
|
+
except Exception as err:
|
|
86
|
+
raise InvalidSchemaError("Could not parse schema") from err
|
|
87
|
+
|
|
88
|
+
if scpd.actionList and scpd.actionList.action:
|
|
89
|
+
actions = scpd.actionList.action
|
|
90
|
+
else:
|
|
91
|
+
actions = []
|
|
92
|
+
|
|
93
|
+
return cls(
|
|
94
|
+
actions=[
|
|
95
|
+
ActionProperties.from_action(action) for action in actions
|
|
96
|
+
]
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True)
|
|
101
|
+
class ServiceProperties:
|
|
102
|
+
"""Parsed device_parser.serviceType."""
|
|
103
|
+
|
|
104
|
+
service_type: str
|
|
105
|
+
service_id: str
|
|
106
|
+
description_url: str
|
|
107
|
+
control_url: str
|
|
108
|
+
event_subscription_url: str
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def from_service(
|
|
112
|
+
cls, service: device_parser.serviceType
|
|
113
|
+
) -> ServiceProperties:
|
|
114
|
+
"""Parse and validate the device_parser.serviceType."""
|
|
115
|
+
return cls(
|
|
116
|
+
service_type=_get_element_text(service, "serviceType"),
|
|
117
|
+
service_id=_get_element_text(service, "serviceId"),
|
|
118
|
+
description_url=_get_element_text(service, "SCPDURL"),
|
|
119
|
+
control_url=_get_element_text(service, "controlURL"),
|
|
120
|
+
event_subscription_url=_get_element_text(service, "eventSubURL"),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass(frozen=True)
|
|
125
|
+
class DeviceDescription: # pylint: disable=too-many-instance-attributes
|
|
126
|
+
"""Device properties from the DeviceType xsd type."""
|
|
127
|
+
|
|
128
|
+
firmware_version: str
|
|
129
|
+
name: str
|
|
130
|
+
mac: str
|
|
131
|
+
manufacturer: str
|
|
132
|
+
model: str
|
|
133
|
+
model_name: str
|
|
134
|
+
serial_number: str
|
|
135
|
+
udn: str
|
|
136
|
+
_config_any: Dict[str, str]
|
|
137
|
+
_device_type: str
|
|
138
|
+
_services: List[ServiceProperties]
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def dict_from_xml(setup_xml_content: bytes) -> dict[str, Any]:
|
|
142
|
+
"""Parse and validate the DeviceType xsd type."""
|
|
143
|
+
try:
|
|
144
|
+
root = device_parser.parseString( # type: ignore
|
|
145
|
+
setup_xml_content, silence=True, print_warnings=False
|
|
146
|
+
)
|
|
147
|
+
except Exception as err:
|
|
148
|
+
raise InvalidSchemaError("Could not parse schema") from err
|
|
149
|
+
|
|
150
|
+
device = root.get_device()
|
|
151
|
+
if device is None:
|
|
152
|
+
raise InvalidSchemaError("Missing root.device element")
|
|
153
|
+
manufacturer = _get_element_text(device, "manufacturer")
|
|
154
|
+
if manufacturer != "Belkin International Inc.":
|
|
155
|
+
raise InvalidSchemaError(
|
|
156
|
+
f"Unexpected manufacturer: {manufacturer}"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if device.anytypeobjs_:
|
|
160
|
+
xs_any = (
|
|
161
|
+
et.fromstring(
|
|
162
|
+
extra, parser=et.XMLParser(resolve_entities=False)
|
|
163
|
+
)
|
|
164
|
+
for extra in device.anytypeobjs_
|
|
165
|
+
)
|
|
166
|
+
config_any = {
|
|
167
|
+
et.QName(tag).localname: tag.text.strip()
|
|
168
|
+
for tag in xs_any
|
|
169
|
+
if tag.text and tag.text.strip()
|
|
170
|
+
}
|
|
171
|
+
else:
|
|
172
|
+
config_any = {}
|
|
173
|
+
|
|
174
|
+
if device.serviceList and device.serviceList.service:
|
|
175
|
+
service_list = device.serviceList.service
|
|
176
|
+
else:
|
|
177
|
+
service_list = []
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
"firmware_version": config_any.get("firmwareVersion", ""),
|
|
181
|
+
"name": _get_element_text(device, "friendlyName"),
|
|
182
|
+
"mac": _get_element_text(device, "macAddress", ""),
|
|
183
|
+
"manufacturer": manufacturer,
|
|
184
|
+
"model": _get_element_text(device, "modelDescription", ""),
|
|
185
|
+
"model_name": _get_element_text(device, "modelName"),
|
|
186
|
+
"serial_number": _get_element_text(device, "serialNumber", ""),
|
|
187
|
+
"udn": _get_element_text(device, "UDN"),
|
|
188
|
+
"_config_any": config_any,
|
|
189
|
+
"_device_type": _get_element_text(device, "deviceType"),
|
|
190
|
+
"_services": [
|
|
191
|
+
ServiceProperties.from_service(service)
|
|
192
|
+
for service in service_list
|
|
193
|
+
],
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def from_xml(cls, setup_xml_content: bytes) -> DeviceDescription:
|
|
198
|
+
"""Parse and validate the DeviceType xsd type."""
|
|
199
|
+
return cls(**cls.dict_from_xml(setup_xml_content))
|
|
200
|
+
|
|
201
|
+
def __hash__(self) -> int:
|
|
202
|
+
"""Hash only the required elements from the xsd."""
|
|
203
|
+
return hash((self.name, self.manufacturer, self.model_name, self.udn))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _get_element_text(
|
|
207
|
+
parent_element: Any, element_name: str, default_value: str | None = None
|
|
208
|
+
) -> str:
|
|
209
|
+
"""Extract text from a sub-element.
|
|
210
|
+
|
|
211
|
+
If the sub-element is not found:
|
|
212
|
+
1. If a `default_value` is provided, that will be returned.
|
|
213
|
+
2. If no `default_value` is provided, raises InvalidSchemaError.
|
|
214
|
+
|
|
215
|
+
Use #1 for optional elements and use #2 for required elements.
|
|
216
|
+
"""
|
|
217
|
+
text: str | None = getattr(parent_element, f"get_{element_name}")()
|
|
218
|
+
if text is None or not text:
|
|
219
|
+
if default_value is not None:
|
|
220
|
+
return default_value
|
|
221
|
+
raise InvalidSchemaError(f"Missing element: {element_name}")
|
|
222
|
+
return text.strip()
|