Ouimeaux: Open Source WeMo Control

Contents:

# ouimeaux

Open source control for Belkin WeMo devices

## Features

  • Supports WeMo Switch, Light Switch, Insight Switch and Motion
  • Command-line tool to discover and control devices in your environment
  • REST API to obtain information and perform actions on devices
  • Simple responsive Web app provides device control on mobile
  • Python API to interact with device at a low level

## About this fork

The original repository can be found here: https://github.com/iancmcc/ouimeaux

It doesn’t appear to be maintained and it doesn’t work with modern Python packages.

It has been forked here so that I can include my modifications to requirements.txt as well as document how to use it.

## Installation

` $ sudo pip install virtualenv $ mkdir ouimeaux-env $ virtualenv ouimeaux-env $ source ouimeaux-env/bin/activate $ cd ouimeaux-env $ pip install git+https://github.com/syphoxy/ouimeaux.git `

At this point you should be able to use wemo and wemo server so long as you’ve activated your environment with source ouimeaux-env/bin/activate.

Note: Ensure that the pip and virtualenv command you use belongs to a Python 2 installation. On some systems, there are multiple versions of Python installed. See below for an example from my Fedora system.

``` $ /bin/ls -1 “$(dirname $(which python))/virtualenv”{,-2} “$(dirname $(which python))/p”{ython,ip}[23] /usr/bin/pip2 /usr/bin/pip3 /usr/bin/python2 /usr/bin/python3 /usr/bin/virtualenv /usr/bin/virtualenv-2

$ pip –version pip 9.0.1 from /usr/lib/python3.5/site-packages (python 3.5)

$ pip2 –version pip 9.0.1 from /usr/lib/python2.7/site-packages (python 2.7) ```

## Troubleshooting

#### Using a VPN The wemo command won’t be able to communicate with your devices if you’re connected to a VPN. It may be redirecting UDP traffic somewhere else. Disconnect from the VPN and the tool should work.

Open an issue and I’ll try to help.

Installation

Basic

At the command line:

$ easy_install ouimeaux

Or, if you have virtualenvwrapper installed:

$ mkvirtualenv ouimeaux
$ pip install ouimeaux

If you want to enable wemo server functionality, specify the server feature to install the necessary dependencies:

$ pip install ouimeaux[server]

Linux

ouimeaux requires Python header files to build some dependencies, and is installed normally using pip or easy_install.

Debian/Ubuntu:

sudo apt-get install python-setuptools python-dev

RHEL/CentOS/Fedora:

sudo yum -y install python-setuptools python-devel

If you wish to build from a local copy of the source, you can of course always execute:

python setup.py install

Windows

ouimeaux requires gevent version 1.0rc2 or higher. If you don’t have the ability to compile gevent and greenlet (a sub-dependency) locally, you can find and download the binary installers for these packages here:

wemo Command

The wemo script will discover devices in your environment and turn switches on and off. To list devices:

$ wemo list

Default is to search for 5 seconds; you can pass --timeout to change that.

You can also print the status of every device found in your environment (the -v option is available to print on/off instead of 0/1):

$ wemo status

To turn a switch on and off, you first have to know the name. Then:

$ wemo switch "TV Room" on
$ wemo switch "TV Room" off

You can also toggle the device:

$ wemo switch "TV Room" toggle

Or check its current status (the -v option will print the word on/off instead of 0/1):

$ wemo -v switch "TV Room" status
on

WeMo LED Bulbs are supported on the command line as well. Control them like switches with wemo light:

$ wemo light lamp on

Or set them to a dimness level from 1 to 255:

$ wemo light lamp on 45

The wemo script will do fuzzy matching of the name you pass in (this can be disabled with the -e option):

$ wemo switch tvrm on

Aliases configured in the file will be accessible on the command line as well:

aliases:
    tv: TV Room Lights

$ wemo switch tv on

Note: If an alias is used on the command line, fuzzy matching will not be attempted.

The wemo script will obey configured settings; they can also be overridden on the command line:

-b, --bind IP:PORT
Bind to this host and port when listening for responses
-d, --debug
Enable debug logging to stdout
-e, --exact-match
Disable fuzzy matching
-v, --human-readable
Print statuses as human-readable words
-t, --timeout
Time in seconds to allow for discovery

Server

wemo server starts a process serving up both a Web app providing basic device control and a REST API allowing integration with any number of services.

The necessary dependencies to run the server are not installed unless the server feature is specified at installation:

$ pip install ouimeaux[server]

Configuration

The IP and port to which the server will bind are specified by the listen parameter in the configuration file. Default: 0.0.0.0:5000. Optionally, basic authentication can be enabled for the web server by setting the auth parameter in the configuration file.

Web App

The Web app very simply presents buttons allowing control of devices and indicating current state. Motions appear as disabled buttons but will turn green when activated.

_images/webapp.png

REST API

A vaguely RESTful API is provided to control devices. Arguments, where specified, may be passed either as query arguments or as JSON:

curl -X POST http://localhost:5000/api/environment -d '{"seconds":10}'

is as valid as:

curl -X POST http://localhost:5000/api/environment?seconds=10
Resource Description
GET /api/environment Returns a JSON description of all devices in the environment
POST /api/environment Initiates a discovery of the environment. Optional seconds argument (default: 5) determines length of discovery.
GET /api/device/NAME Returns a JSON description of the device named NAME. NAME will be fuzzy-matched against device names, as on the command line (e.g., “closet” will match “Hall Closet”).
POST /api/device/NAME Toggle switch state, specified by optional state argument (default: “toggle”). Valid values are “on”, “off” or “toggle”.

Configuration

A configuration file in YAML format will be created at ~/.wemo/config.yml:

# ip:port to bind to when receiving responses from discovery.
# The default is first DNS resolution of local host, port 54321
#
# bind: 10.1.2.3:9090

# Whether to use a device cache (stored at ~/.wemo/cache)
#
# cache: false

aliases:
# Shortcuts to longer device names. Uncommenting the following
# line will allow you to execute 'wemo switch lr on' instead of
# 'wemo switch "Living Room Lights" on'
#
#    lr: Living Room Lights

# Web app bind address
#
# listen: 0.0.0.0:5000

# Require basic authentication (username:password) for the web app
#
# auth: admin:password

Python API

Environment

The main interface is presented by an Environment, which optionally accepts functions called when a Switch or Motion device is identified:

>>> from ouimeaux.environment import Environment
>>>
>>> def on_switch(switch):
...     print "Switch found!", switch.name
...
>>> def on_motion(motion):
...     print "Motion found!", motion.name
...
>>> env = Environment(on_switch, on_motion)

Start up the server to listen for responses to the discovery broadcast:

>>> env.start()

Discovery of all WeMo devices in an environment is then straightforward; simply pass the length of time (in seconds) you want discovery to run:

>>> env.discover(seconds=3)
Switch found! Living Room

During that time, the Environment will continually broadcast search requests and parse responses. At any point, you can see the names of discovered devices:

>>> env.list_switches()
['Living Room', 'TV Room', 'Front Closet']
>>> env.list_motions()
['Front Hallway']

Devices can be retrieved by using get_switch and get_motion methods:

>>> switch = env.get_switch('TV Room')
>>> switch
<WeMo Switch "TV Room">

Devices

All devices have an explain() method, which will print out a list of all available services, as well as the actions and arguments to those actions on each service:

>>> switch.explain()

basicevent
----------
  SetSmartDevInfo(SmartDevURL)
  SetServerEnvironment(ServerEnvironmentType, TurnServerEnvironment, ServerEnvironment)
  GetDeviceId()
  GetRuleOverrideStatus(RuleOverrideStatus)
  GetIconURL(URL)
  SetBinaryState(BinaryState)
...

Services and actions are available via simple attribute access. Calling actions returns a dictionary of return values:

>>> switch.basicevent.SetBinaryState(BinaryState=0)
{'BinaryState': 0}

Events

Warning

This events framework is deprecated and will be removed prior to the 1.0 release. Please use the signals framework.

By default, ouimeaux subscribes to property change events on discovered devices (this can be disabled by passing with_subscribers=False to the Environment constructor). You can register callbacks that will be called when switches and motions change state (on/off, or motion detected):

>>> def on_motion(value):
...     print "Motion detected!"
...
>>> env.get_motion('Front Hallway').register_listener(on_motion)
>>> env.wait(timeout=60)

Note the use of Environment.wait() to give control to the event loop for events to be detected. A timeout in seconds may optionally be specified; default is no timeout.

Signals

A simple signals framework (using pysignals) is included to replace the rudimentary events in earlier releases. These are found in the ouimeaux.signals module. Signal handlers may be registered using the receiver decorator and must have the signature sender, **kwargs:

@receiver(devicefound)
def handler(sender, **kwargs):
    print "Found device", sender

Where sender is the relevant object (in most cases, the device). Signals may also have handlers registered using signal.connect(handler):

def handler(sender, **kwargs):
    pass

statechange.connect(sender)

Available signals:

discovered
Fires when a device responds to the broadcast request. Includes:
  • sender: The UPnP broadcast component
  • address: The address of the responding device
  • headers: The response headers
devicefound
Sent when a device is found and registered into the environment. Includes:
  • sender: The device found
subscription
Sent when a device sends an event as the result of a subscription. Includes:
  • sender: The device that sent the event
  • type: The type of the event send (e.g., BinaryState)
  • value: The value associated with the event
statechange
Sent when a device indicates it has detected a state change. Includes:
  • sender: The device that changed state
  • state: The resulting state (0 or 1)

See the pysignals documentation for further information.

Example: Registering a handler for when a Light Switch switches on or off:

from ouimeaux.signals import statechange, receiver

env = Environment(); env.start()
env.discover(5)

switch = env.get_switch('Porch Light')

@receiver(statechange, sender=switch)
def switch_toggle(device, **kwargs):
    print device, kwargs['state']

env.wait()  # Pass control to the event loop

See the examples for a more detailed implementation.

Switches

Switches have three shortcut methods defined: get_state, on and off. Switches also have a blink method, which accepts a number of seconds. This will toggle the device, wait the number of seconds, then toggle it again. Remember to call env.wait() to give control to the event loop.

Motions

Motions have one shortcut method defined: get_state.

Insight

In addition to the normal Switch methods, Insight switches have several metrics exposed:

insight.today_kwh
insight.current_power
insight.today_on_time
insight.on_for
insight.today_standby_time

Device Cache

By default, device results are cached on the filesystem for quicker initialization. This can be disabled by passing with_cache=False to the Environment constructor. On a related note, if you want to use the cache exclusively, you can pass with_discovery=False to the Environment constructor to disable M-SEARCH requests.

You can clear the device cache either by deleting the file ~/.wemo/cache or by using the wemo clear command.

Examples

Detailed examples are included in the source demonstrating common use cases. Suggestions (or implementations) for more are always welcome.

Contributing

Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.

You can contribute in many ways:

Types of Contributions

Report Bugs

Report bugs at https://github.com/iancmcc/ouimeaux/issues.

If you are reporting a bug, please include:

  • Your operating system name and version.
  • Any details about your local setup that might be helpful in troubleshooting.
  • Detailed steps to reproduce the bug.

Fix Bugs

Look through the GitHub issues for bugs. Anything tagged with “bug” is open to whoever wants to implement it.

Implement Features

Look through the GitHub issues for features. Anything tagged with “feature” is open to whoever wants to implement it.

Write Documentation

ouimeaux could always use more documentation, whether as part of the official ouimeaux docs, in docstrings, or even on the web in blog posts, articles, and such.

Submit Feedback

The best way to send feedback is to file an issue at https://github.com/iancmcc/ouimeaux/issues.

If you are proposing a feature:

  • Explain in detail how it would work.
  • Keep the scope as narrow as possible, to make it easier to implement.
  • Remember that this is a volunteer-driven project, and that contributions are welcome :)

Get Started!

Ready to contribute? Here’s how to set up ouimeaux for local development.

  1. Fork the ouimeaux repo on GitHub.

  2. Clone your fork locally:

    $ git clone git@github.com:your_name_here/ouimeaux.git
    
  3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:

    $ mkvirtualenv ouimeaux
    $ cd ouimeaux/
    $ python setup.py develop
    
  4. Create a branch for local development:

    $ git checkout -b name-of-your-bugfix-or-feature
    

    Now you can make your changes locally.

  5. When you’re done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:

    $ flake8 ouimeaux tests
    $ python setup.py test
    $ tox
    

    To get flake8 and tox, just pip install them into your virtualenv.

  6. Commit your changes and push your branch to GitHub:

    $ git add .
    $ git commit -m "Your detailed description of your changes."
    $ git push origin name-of-your-bugfix-or-feature
    
  7. Submit a pull request through the GitHub website.

Pull Request Guidelines

Before you submit a pull request, check that it meets these guidelines:

  1. The pull request should include tests.
  2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.md.
  3. The pull request should work for Python 2.6, 2.7, and 3.3, and for PyPy. Check https://travis-ci.org/iancmcc/ouimeaux/pull_requests and make sure that the tests pass for all supported Python versions.

Tips

To run a subset of tests:

$ python -m unittest tests.test_ouimeaux

History

Release 0.8.0 (July 30, 2016)

  • Randomize subscription ports to enable simultaneous ouimeaux scripts (thanks @bennytheshap)
  • Fix for WeMo LED Light support (thanks @sstangle73)
  • #32: Removed address cache, broke server out into optional feature
  • Fix for Maker state reporting (thanks @pavoni)
  • Filter by SSDP location, fixing case where multiple devices respond from the same IP (thanks @szakharchenko)
  • Fix Maker event handlers, which were being passed as bridges (thanks @maxlazarov)
  • Work around gevent-socketio bug by explicitly casting header value as string
  • Fix for inconsistent Light state (thanks @canduuk)
  • StateChange signals are now a separate class and do not fire if value is unchanged (thanks @esecules)
  • Python 3 support (thanks to @drock371)

Release 0.7.9 (March 17, 2015)

  • Command line support for WeMo LED Light (thanks @fritz-fritz)
  • Command line support for WeMo Maker (thanks @logjames)
  • Support for 2.0.0 firmware (thanks @fritz-fritz)
  • Bug fixes

Release 0.7.3 (August 10, 2014)

  • Fixed #18: Error when run as root
  • Fixed #26: Evict devices from cache when unreachable
  • Fixed #29: GetPower stopped working for Insight devices
  • Fixed #31: Add blink method on switches, include in REST API
  • Fixed #33, #37: Handle invalid devices without dying
  • Fixed #35: Require requests >= 2.3.0
  • Fixed #40: Retry requests in the event of failure
  • Fixed #47: Don’t choke on invalid newlines in XML returned by switches
    (thanks to @fingon)

Release 0.7.2 (January 28, 2014)

  • Fix a bug with using query parameters on /api/device

Release 0.7 (January 27, 2014)

  • Added REST API
  • Added Web app

Release 0.6 (January 25, 2014)

  • Added signals framework
  • Fixed #16, #19, #22: Defensively resubscribe to events when device responds with an error
  • Fixed #15: Signals framework includes relevant device when sending signal
  • Refactored structure, added Sphinx docs

Release 0.5.3 (January 25, 2014)

  • Fixed #20: Allow timeout in environment.wait()
  • Fixed #21: Add Insight support

Release 0.5.2 (November 23, 2013)

  • Fixed #14: Indicate Connection:close header to avoid logging when WeMo sends invalid HTTP response.

Release 0.5.1 (November 9, 2013)

  • Fixed #10: Updated subscriber listener to use more reliable method of retrieving non-loopback IP address; updated docs to fix typo in listener registration example (thanks to @benhoyle, @francxk)
  • Fixed #11: Remove instancemethod objects before attempting to pickle devices in the cache (thanks @piperde, @JonPenner, @tomtomau, @masilu77)

Release 0.5 (October 14, 2013)

  • Added fuzzy matching of device name when searching/toggling from command line
  • Added status mode to print status for all devices
  • Added switch status mode to print status for specific device
  • Added flags for all command-line options
  • Fixed #9: Removed unused fcntl import that precluded Windows usage (thanks to @deepseven)

Release 0.4.3 (August 31, 2013)

  • Used new method of obtaining local IP for discovery that is less likely to return loopback
  • Exit with failure and instructions for solution if loopback IP is used
  • Updated installation docs to include python-dev and pip instructions (patch by @fnaard)
  • Fixed README inclusion bug that occasionally broke installation via pip.
  • Added --debug option to enable debug logging to stdout

Release 0.4 (August 17, 2013)

  • Fixed #7: Added support for light switch devices (patch by nschrenk).
  • Fixed #6: Added “wemo clear” command to clear the device cache.

Release 0.3 (May 25, 2013)

  • Fixed #4: Added ability to specify ip:port for discovery server binding. Removed documentation describing need to disable SSDP service on Windows.
  • Fixed #5: Added device cache for faster results.
  • Added configuration file.
  • Added ability to configure aliases for devices to avoid quoting strings on the command line.
  • Added ‘toggle’ command to command line switch control.

Release 0.2 (April 21, 2013)

  • Fixed #1: Added ability to subscribe to motion and switch state change events.
  • Added Windows installation details to README (patch by @brianpeiris)
  • Cleaned up UDP server lifecycle so rediscovery doesn’t try to start it back up.

Release 0.1 (February 2, 2013)

  • Initial release.
  • First release on PyPI.

Indices and tables