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.
Files changed (40) hide show
  1. pywemo/README.md +69 -0
  2. pywemo/__init__.py +33 -0
  3. pywemo/color.py +79 -0
  4. pywemo/discovery.py +194 -0
  5. pywemo/exceptions.py +94 -0
  6. pywemo/ouimeaux_device/LICENSE +12 -0
  7. pywemo/ouimeaux_device/__init__.py +679 -0
  8. pywemo/ouimeaux_device/api/__init__.py +1 -0
  9. pywemo/ouimeaux_device/api/attributes.py +131 -0
  10. pywemo/ouimeaux_device/api/db_orm.py +197 -0
  11. pywemo/ouimeaux_device/api/long_press.py +168 -0
  12. pywemo/ouimeaux_device/api/rules_db.py +467 -0
  13. pywemo/ouimeaux_device/api/service.py +363 -0
  14. pywemo/ouimeaux_device/api/wemo_services.py +25 -0
  15. pywemo/ouimeaux_device/api/wemo_services.pyi +241 -0
  16. pywemo/ouimeaux_device/api/xsd/__init__.py +1 -0
  17. pywemo/ouimeaux_device/api/xsd/device.py +3888 -0
  18. pywemo/ouimeaux_device/api/xsd/device.xsd +95 -0
  19. pywemo/ouimeaux_device/api/xsd/service.py +3872 -0
  20. pywemo/ouimeaux_device/api/xsd/service.xsd +93 -0
  21. pywemo/ouimeaux_device/api/xsd_types.py +222 -0
  22. pywemo/ouimeaux_device/bridge.py +506 -0
  23. pywemo/ouimeaux_device/coffeemaker.py +92 -0
  24. pywemo/ouimeaux_device/crockpot.py +157 -0
  25. pywemo/ouimeaux_device/dimmer.py +70 -0
  26. pywemo/ouimeaux_device/humidifier.py +223 -0
  27. pywemo/ouimeaux_device/insight.py +191 -0
  28. pywemo/ouimeaux_device/lightswitch.py +11 -0
  29. pywemo/ouimeaux_device/maker.py +54 -0
  30. pywemo/ouimeaux_device/motion.py +6 -0
  31. pywemo/ouimeaux_device/outdoor_plug.py +6 -0
  32. pywemo/ouimeaux_device/switch.py +32 -0
  33. pywemo/py.typed +0 -0
  34. pywemo/ssdp.py +372 -0
  35. pywemo/subscribe.py +782 -0
  36. pywemo/util.py +139 -0
  37. pywemo-1.4.0.dist-info/LICENSE +54 -0
  38. pywemo-1.4.0.dist-info/METADATA +192 -0
  39. pywemo-1.4.0.dist-info/RECORD +40 -0
  40. 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()