Skip to content

Instantly share code, notes, and snippets.

@pruppert
Last active June 23, 2023 08:57
Show Gist options
  • Save pruppert/af7d38cb7b7ca75584ef to your computer and use it in GitHub Desktop.
Save pruppert/af7d38cb7b7ca75584ef to your computer and use it in GitHub Desktop.
This is a python script that can control a local WeMo switch as long as you know the local IP of the WeMo. The script is a fork of pdumoulin's blinky: https://github.com/pdumoulin/blinky.
#!/usr/bin/python
import re
import urllib2
# Configuration:
# Enter the local IP address of your WeMo in the parentheses of the ip variable below.
# You may have to check your router to see what local IP is assigned to the WeMo.
# It is recommended that you assign a static local IP to the WeMo to ensure the WeMo is always at that address.
# Uncomment one of the triggers at the end of this script.
ip = ''
class wemo:
OFF_STATE = '0'
ON_STATES = ['1', '8']
ip = None
ports = [49153, 49152, 49154, 49151, 49155]
def __init__(self, switch_ip):
self.ip = switch_ip
def toggle(self):
status = self.status()
if status in self.ON_STATES:
result = self.off()
result = 'WeMo is now off.'
elif status == self.OFF_STATE:
result = self.on()
result = 'WeMo is now on.'
else:
raise Exception("UnexpectedStatusResponse")
return result
def on(self):
return self._send('Set', 'BinaryState', 1)
def off(self):
return self._send('Set', 'BinaryState', 0)
def status(self):
return self._send('Get', 'BinaryState')
def name(self):
return self._send('Get', 'FriendlyName')
def signal(self):
return self._send('Get', 'SignalStrength')
def _get_header_xml(self, method, obj):
method = method + obj
return '"urn:Belkin:service:basicevent:1#%s"' % method
def _get_body_xml(self, method, obj, value=0):
method = method + obj
return '<u:%s xmlns:u="urn:Belkin:service:basicevent:1"><%s>%s</%s></u:%s>' % (method, obj, value, obj, method)
def _send(self, method, obj, value=None):
body_xml = self._get_body_xml(method, obj, value)
header_xml = self._get_header_xml(method, obj)
for port in self.ports:
result = self._try_send(self.ip, port, body_xml, header_xml, obj)
if result is not None:
self.ports = [port]
return result
raise Exception("TimeoutOnAllPorts")
def _try_send(self, ip, port, body, header, data):
try:
request = urllib2.Request('http://%s:%s/upnp/control/basicevent1' % (ip, port))
request.add_header('Content-type', 'text/xml; charset="utf-8"')
request.add_header('SOAPACTION', header)
request_body = '<?xml version="1.0" encoding="utf-8"?>'
request_body += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
request_body += '<s:Body>%s</s:Body></s:Envelope>' % body
request.add_data(request_body)
result = urllib2.urlopen(request, timeout=3)
return self._extract(result.read(), data)
except Exception as e:
print str(e)
return None
def _extract(self, response, name):
exp = '<%s>(.*?)<\/%s>' % (name, name)
g = re.search(exp, response)
if g:
return g.group(1)
return response
def output(message):
print message
switch = wemo(ip)
# Configuration:
# Uncomment only one of the lines below to make the script work.
#output(switch.on())
#output(switch.off())
#output(switch.toggle())
#output(switch.status())
@cclauss
Copy link

cclauss commented Dec 25, 2014

Pythonista UI version... Warning: I don't have a Wemo so I was unable to debug this completely.

If you want to collaborate on improving this app, please make it a repo instead of a gist.

#!/usr/bin/python
# source code: https://gist.github.com/pruppert/af7d38cb7b7ca75584ef

import console, re, ui, urllib2

# Configuration:
# Create a wemo.pyui file by follow the steps located at the bottom of this file.
# Enter your WeMo's local IP address in the ui and then click the toggle button.
# You may have to check your router to see what local IP address is assigned to your WeMo.
# It is recommended that you assign a static local IP to your WeMo to ensure it is always at that address.

class wemo:
    OFF_STATE = '0'
    ON_STATES = ['1', '8']
    ip = None
    ports = [49153, 49152, 49154, 49151, 49155]

    def __init__(self, switch_ip = ''):
        self.ip = switch_ip

    def toggle(self):
        status = self.status()
        if status in self.ON_STATES:
            result = self.off()
        elif status == self.OFF_STATE:
            result = self.on()
        else:
            raise Exception("UnexpectedStatusResponse")
        result = 'WeMo is now {}.'.format('on' if result else 'off')
        view.set_name()
        return result

    def on(self):
        return self._send('Set', 'BinaryState', 1)

    def off(self):
        return self._send('Set', 'BinaryState', 0)

    def status(self):
        return self._send('Get', 'BinaryState')

    def name(self):
        return self._send('Get', 'FriendlyName')

    def signal(self):
        return self._send('Get', 'SignalStrength')

    def _get_header_xml(self, method, obj):
        method = method + obj
        return '"urn:Belkin:service:basicevent:1#%s"' % method

    def _get_body_xml(self, method, obj, value=0):
        method = method + obj
        return '<u:%s xmlns:u="urn:Belkin:service:basicevent:1"><%s>%s</%s></u:%s>' % (method, obj, value, obj, method)

    def _send(self, method, obj, value=None):
        body_xml = self._get_body_xml(method, obj, value)
        header_xml = self._get_header_xml(method, obj)
        view.set_xml(header_xml, body_xml)
        for port in self.ports:
            result = self._try_send(self.ip, port, body_xml, header_xml, obj)
            if result is not None:
                self.ports = [port]
                view['IPAddress'].text = '{}:{}'.format(self.ip, port)
            return result
        raise Exception("TimeoutOnAllPorts")

    def _try_send(self, ip, port, body, header, data):
        try:
            request = urllib2.Request('http://%s:%s/upnp/control/basicevent1' % (ip, port))
            request.add_header('Content-type', 'text/xml; charset="utf-8"')
            request.add_header('SOAPACTION', header)
            request_body = '<?xml version="1.0" encoding="utf-8"?>'
            request_body += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
            request_body += '<s:Body>%s</s:Body></s:Envelope>' % body
            request.add_data(request_body)
            result = urllib2.urlopen(request, timeout=3)
            return self._extract(result.read(), data)
        except Exception as e:
            print str(e)
            return None

    def _extract(self, response, name):
        exp = '<%s>(.*?)<\/%s>' % (name, name)
        g = re.search(exp, response)
        if g:
            return g.group(1)
        return response

def output(message):
    print message

switch = wemo()

def valid_ip_v4_addr(ip_v4_address):
    octets = ip_v4_address.strip().partition(':')[0].split('.')
    assert len(octets) == 4, 'Valid IP v4 addresses need 4 octets: ' + '.'.join(octets)
    for octet in octets:
        assert octet.isdigit(), 'Valid IP v4 octets must only contain digits: ' + octet
        assert 0 <= int(octet) <= 255, 'Valid IP v4 octets must be between 0 and 255: ' + octet
    return '.'.join(octets)

class WemoView(ui.View):
    def __init__(self):
        self.switch = switch
        self.present()

    def did_load(self):
        self['IPAddress'].delegate = self
        self['Toggle'].action = self.toggle_action

    def set_name(self):
        self['Name'].text = '{} ({} {})'.format(self.switch.name(),
                                                self.switch.status(),
                                                self.switch.signal())

    def set_xml(self, header, body):
        self['XML'].text = '{}\n{}\n{}'.format(header, '=' * 29, body)

    def toggle_action(self, sender):
        ip_address = None
        try:
            ip_address = valid_ip_v4_addr(self['IPAddress'].text)
        except AssertionError as e:
            console.hud_alert(str(e))
        if ip_address:
            self.switch.ip = ip_address
            try:
                self.switch.toggle()
            except Exception as e:
                console.hud_alert(str(e))

view = ui.load_view()

# Copy the text between the triple quotes below to iOS clipboard
# and then run pyui_from_clipboard.py to make a wemo.pyui file.
# pyui_from_clipboard.py is at: https://github.com/cclauss/Ten-lines-or-less

'''
[{"class":"View","attributes":{"custom_class":"WemoView","background_color":"RGBA(1.000000,1.000000,1.000000,1.000000)","tint_color":"RGBA(0.000000,0.478000,1.000000,1.000000)","enabled":true,"border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","flex":""},"frame":"{{0, 0}, {320, 416}}","nodes":[{"class":"TextField","attributes":{"alignment":"center","border_color":"RGBA(0.000000,0.333333,1.000000,1.000000)","font_size":17,"enabled":true,"flex":"","placeholder":"IP address of your Wemo","text":"","text_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","name":"IPAddress","border_style":3,"border_width":1,"uuid":"3202CA31-6E12-41F9-98B8-3D312BABC701"},"frame":"{{6, 46}, {232.5, 32}}","nodes":[]},{"class":"Label","attributes":{"font_size":17,"enabled":true,"text":"Wemo Controller for Pythonista","flex":"","name":"label1","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","text_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","alignment":"center","uuid":"84F0A783-F735-4938-AF57-9FB897AB9004"},"frame":"{{6, 6}, {308, 32}}","nodes":[]},{"class":"Button","attributes":{"font_size":15,"enabled":true,"flex":"","font_bold":false,"name":"Toggle","corner_radius":8,"border_color":"RGBA(0.000000,0.333333,1.000000,1.000000)","border_width":2,"tint_color":"RGBA(0.000000,0.333333,1.000000,1.000000)","uuid":"E04B2E4E-E61A-4B36-A6C9-67AF6F31CFAE","title":"Toggle"},"frame":"{{246.5, 46}, {67.5, 32}}","nodes":[]},{"class":"Label","attributes":{"font_size":17,"enabled":true,"text":"","flex":"","name":"Name","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","text_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","alignment":"center","uuid":"1D9A147B-8837-4A89-B2E0-3A9A407822C6"},"frame":"{{6, 86}, {308, 32}}","nodes":[]},{"class":"TextView","attributes":{"font_size":17,"enabled":true,"flex":"","name":"XML","border_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","text_color":"RGBA(0.000000,0.000000,0.000000,1.000000)","alignment":"left","editable":false,"uuid":"115571E4-C381-48E6-9100-B8259F3C6F4F"},"frame":"{{6, 126}, {308, 284}}","nodes":[]}]}]
'''

@Toug19
Copy link

Toug19 commented Nov 30, 2020

Hi,
Is there a way to retrieve the instant power?
Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment