Migrated WeMo Switch plugin to raw HTTP requests. ouimeaux was heavy and broken

This commit is contained in:
Fabio Manganiello 2019-12-04 02:09:49 +01:00
parent 55dd7b0d53
commit 2e4e847857
3 changed files with 101 additions and 60 deletions

View file

@ -1,7 +1,19 @@
import enum
import textwrap
from xml.dom.minidom import parseString
import requests
from platypush.plugins import action from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin from platypush.plugins.switch import SwitchPlugin
class SwitchAction(enum.Enum):
GET_STATE = 'GetBinaryState'
SET_STATE = 'SetBinaryState'
GET_NAME = 'GetFriendlyName'
class SwitchWemoPlugin(SwitchPlugin): class SwitchWemoPlugin(SwitchPlugin):
""" """
Plugin to control a Belkin WeMo smart switches Plugin to control a Belkin WeMo smart switches
@ -9,32 +21,28 @@ class SwitchWemoPlugin(SwitchPlugin):
Requires: Requires:
* **ouimeaux** (``pip install ouimeaux``) * **requests** (``pip install requests``)
""" """
def __init__(self, discovery_seconds=3, **kwargs): _default_port = 49153
def __init__(self, devices=None, **kwargs):
""" """
:param discovery_seconds: Discovery time when scanning for devices (default: 3) :param devices: List of IP addresses or name->address map containing the WeMo Switch devices to control.
:type discovery_seconds: int This plugin previously used ouimeaux for auto-discovery but it's been dropped because
1. too slow 2. too heavy 3. auto-discovery failed too often.
:type devices: list or dict
""" """
super().__init__(**kwargs) super().__init__(**kwargs)
self.discovery_seconds = discovery_seconds self._port = self._default_port
self.env = None if devices:
self._devices = devices if isinstance(devices, dict) else \
{addr: addr for addr in devices}
else:
self._devices = {}
def _refresh_devices(self): self._addresses = set(self._devices.values())
""" Update the list of available devices """
self.logger.info('Starting WeMo discovery')
self._get_environment()
self.env.discover(seconds=self.discovery_seconds)
self._devices = self.env.devices
def _get_environment(self):
if not self.env:
from ouimeaux.environment import Environment
self.env = Environment()
self.env.start()
self._refresh_devices()
@property @property
def devices(self): def devices(self):
@ -48,9 +56,7 @@ class SwitchWemoPlugin(SwitchPlugin):
{ {
"ip": "192.168.1.123", "ip": "192.168.1.123",
"name": "Switch 1", "name": "Switch 1",
"state": 1, "on": true,
"model": "Belkin Plugin Socket 1.0",
"serialnumber": "123456ABCDEF"
}, },
{ {
@ -58,65 +64,105 @@ class SwitchWemoPlugin(SwitchPlugin):
} }
] ]
""" """
self._refresh_devices()
return [ return [
{ {
'id': dev.name, 'ip': addr,
'ip': dev.host, 'name': name if name != addr else self.get_name(addr),
'name': dev.name, 'on': self.get_state(addr),
'model': dev.model,
'on': True if dev.get_state() else False,
'serialnumber': dev.serialnumber,
} }
for (name, dev) in self._devices.items() for (name, addr) in self._devices.items()
] ]
def _exec(self, method, device, *args, **kwargs): def _exec(self, device: str, action: SwitchAction, port: int = _default_port, value=None):
self._get_environment() if device not in self._addresses:
try:
device = self._devices[device]
except KeyError:
pass
if device not in self._devices: state_name = action.value[3:]
self._refresh_devices()
if device not in self._devices: response = requests.post(
raise RuntimeError('Device {} not found'.format(device)) 'http://{}:{}/upnp/control/basicevent1'.format(device, port),
headers={
'User-Agent': '',
'Accept': '',
'Content-Type': 'text/xml; charset="utf-8"',
'SOAPACTION': '\"urn:Belkin:service:basicevent:1#{}\"'.format(action.value),
},
data=textwrap.dedent(
'''
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:{action} xmlns:u="urn:Belkin:service:basicevent:1">
<{state}>{value}</{state}>
</u:{action}
></s:Body>
</s:Envelope>
'''.format(action=action.value, state=state_name,
value=value if value is not None else ''))
)
self.logger.info('Executing {} on WeMo device {}'. dom = parseString(response.text)
format(method, device)) return dom.getElementsByTagName(state_name).item(0).firstChild.data
dev = self._devices[device]
getattr(dev, method)(*args, **kwargs)
return self.status(device)
@action @action
def on(self, device, **kwargs): def on(self, device: str, **kwargs):
""" """
Turn a switch on Turn a switch on
:param device: Device name :param device: Device name or address
:type device: str
""" """
return self._exec('on', device) state = self._exec(device=device, action=SwitchAction.SET_STATE, value=1)
return {
'device': device,
'on': bool(int(state)),
}
@action @action
def off(self, device, **kwargs): def off(self, device: str, **kwargs):
""" """
Turn a switch off Turn a switch off
:param device: Device name :param device: Device name or address
:type device: str
""" """
return self._exec('off', device) state = self._exec(device=device, action=SwitchAction.SET_STATE, value=0)
return {
'device': device,
'on': bool(int(state)),
}
@action @action
def toggle(self, device, **kwargs): def toggle(self, device: str, *args, **kwargs):
""" """
Toggle the state of a switch (on/off) Toggle a device on/off state
:param device: Device name :param device: Device name or address
:type device: str
""" """
return self._exec('toggle', device) state = self.get_state(device).output
return self.on(device) if not state else self.off(device)
@action
def get_state(self, device: str):
"""
Get the on state of a device (True/False)
:param device: Device name or address
"""
state = self._exec(device=device, action=SwitchAction.GET_STATE)
return bool(int(state))
@action
def get_name(self, device: str):
"""
Get the friendly name of a device
:param device: Device name or address
"""
return self._exec(device=device, action=SwitchAction.GET_NAME)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -47,9 +47,6 @@ bcrypt
# MPD/Mopidy music server support # MPD/Mopidy music server support
# python-mpd2 # python-mpd2
# Belkin WeMo Switch plugin support
# git+https://github.com/iancmcc/ouimeaux
# Google Assistant support # Google Assistant support
# google-assistant-sdk[samples] # google-assistant-sdk[samples]
# google-assistant-library # google-assistant-library

View file

@ -165,8 +165,6 @@ setup(
'hue': ['phue'], 'hue': ['phue'],
# Support for MPD/Mopidy music server plugin and backend # Support for MPD/Mopidy music server plugin and backend
'mpd': ['python-mpd2', 'websocket-client'], 'mpd': ['python-mpd2', 'websocket-client'],
# Support for Belkin WeMo Switch plugin
'wemo' : ['ouimeaux @ https://github.com/iancmcc/ouimeaux/tarball/master'],
# Support for text2speech plugin # Support for text2speech plugin
'tts': ['mplayer'], 'tts': ['mplayer'],
# Support for Google text2speech plugin # Support for Google text2speech plugin