Wednesday, January 15, 2014

Belkin's Wemo Light Switch Part 2

It's been over a month (well, almost two months) since I wrote about the Wemo switch.  During that time, Belkin has come out with the Insight switch and a couple of iterations of the Android software.  It still doesn't work well on my phone, their online server has problems when you add switches.  Seems they forget you already had other switches.  I'm not willing to reinitialize everything in my house each time I do something to one of them.  But:

I have three of the switches now and they're all hooked into my Raspberry Pi house controller.  Over the last couple of months I've found a couple of problems with the ouimeaux python library that the author is looking at, and of course, with my code to run them.  Unlike the Android software, the wall switches work really well.  Occasionally the switch that is at the edge of my Wifi network will fail to report and twice now I've had one of the switches respond to a command with an error.  Really good performance for a remote switch and far, far better than the X10 stuff they replaced.

The really cool thing is that the switches report when they change state.  So, if I change it over the phone, the switch tells my Pi that it happened.  If I push the button, same thing.  This is great because you can check the state of a switch at any time to see if you left the lights on.  Using my Pi to control them works great as well.  I don't have to rely on Belkin's server or that 'if this then that' website to control things.  I can set up as many schedules as I want and don't have to worry about somebody else's server having problems.

For you folk out there that would like to mess with these switches, here's the code I'm using to control them:

The Python Script
#! /usr/bin/python
import os
import signal
import sys
import logging
from apscheduler.scheduler import Scheduler
from threading import Thread
import datetime
from datetime import timedelta
import time
import sysv_ipc


from ouimeaux.upnp import UPnPLoopbackException
from ouimeaux.environment import Environment
from ouimeaux.config import get_cache, in_home, WemoConfiguration
from ouimeaux.utils import matcher

#-------------------------------------------------

def lprint(text):
 print time.strftime("%A, %B, %d at %H:%M:%S"),text
 sys.stdout.flush()
 
def signal_handler(signal,frame):
 lprint (" Exit Signal")
 sys.exit(0)

def on_switch(switch):
 lprint (" Switch found! " + switch.name)

def on_motion(motion):
 lprint (" Motion found!" + switch.motion)
 
def outsideLightsOn():
 lprint (" Outside lights on")
 garageLightSwitch.on()
 cactusLightSwitch.on()
 fPorchLightSwitch.on()
 
def outsideLightsOff():
 lprint (" Outside lights off")
 garageLightSwitch.off()
 cactusLightSwitch.off()
 fPorchLightSwitch.off()
 
def fPorchToggle():
 if (int(fPorchLightSwitch.get_state()) == 1):
  fPorchLightSwitch.off()
 else:
  fPorchLightSwitch.on()
  
def garageToggle():
 if (int(garageLightSwitch.get_state()) == 1):
  garageLightSwitch.off()
 else:
  garageLightSwitch.on()
  
def cactusToggle():
 if (int(cactusLightSwitch.get_state()) == 1):
  cactusLightSwitch.off()
 else:
  cactusLightSwitch.on()
 
def updateDatabase(whichone, status):
  pass

def cactusSwitchChange(value):
 if (int(value) == 1):
  lprint (" Cactus Spot On")
  updateDatabase('cactusspot', 'On')
 elif (int(value) == 0):
  lprint (" Cactus Spot Off")
  updateDatabase('cactusspot', 'Off')
 else:
  lprint (" Cactus Spot Switch Unknown value: " + value)
  
def garageSwitchChange(value):
 if (int(value) == 1):
  lprint (" Garage Outside Light On")
  updateDatabase('outsidegarage', 'On')
 elif (int(value) == 0):
  lprint (" Garage Outside Light Off")
  updateDatabase('outsidegarage', 'Off')
 else:
  lprint (" Garage Outside Light Switch Unknown value: " + value)
  
def fPorchSwitchChange(value):
 if (int(value) == 1):
  lprint (" Front Porch Light On")
  updateDatabase('frontporch', 'On')
 elif (int(value) == 0):
  lprint (" Front Porch Light Off")
  updateDatabase('frontporch', 'Off')
 else:
  lprint (" Front Porch Light Switch Unknown value: " + value)
  
def handleCommand(command):
 #lprint(" " + str(command))
 # the command comes in from php as something like
 # ('s:17:"AcidPump, pumpOff";', 2)
 # so command[0] is 's:17:"AcidPump, pumpOff'
 # then split it at the "  and take the second item
 try:
  c = str(command[0].split('\"')[1]).split(',')
 except IndexError:
  c = str(command[0]).split(' ')    #this is for something I sent from another process
 #lprint(c)
 if (c[0] == 'OutsideLightsOn'):
  outsideLightsOn()
 elif (c[0] == 'OutsideLightsOff'):
  outsideLightsOff()
 elif (c[0] == 'fPorchToggle'):
  fPorchToggle()
 elif(c[0] == 'garageToggle'):
  garageToggle()
 elif (c[0] == 'cactusToggle'):
  cactusToggle()
 else:
  lprint(" Weird command = " + str(c))

def showTime():
 lprint("timecheck");

if __name__ == '__main__':
 #Catch Control-C input so it isn't ugly
 signal.signal(signal.SIGINT, signal_handler)
 logging.basicConfig(format='%(levelname)s:%(message)s',level=logging.WARNING)
 #When looking at a log, this will tell me when it is restarted
 lprint (" started")

 env = Environment(on_switch, on_motion, with_cache=False)
 env.start()
 lprint (" Starting device discovery")
 env.discover(seconds=5)
 lprint (" done with discovery")
 garageLightSwitch = env.get_switch('outsidegarage')
 cactusLightSwitch = env.get_switch('cactusspot')
 fPorchLightSwitch = env.get_switch('frontporch')
 # lprint (" testing lights")
 # garageLightSwitch.basicevent.SetBinaryState(BinaryState=1)
 # cactusLightSwitch.basicevent.SetBinaryState(BinaryState=1)
 # fPorchLightSwitch.basicevent.SetBinaryState(BinaryState=1)
 # time.sleep(2)
 # lprint (' garage ' + str(garageLightSwitch.basicevent.GetBinaryState()))
 # lprint (' cactus ' + str(cactusLightSwitch.basicevent.GetBinaryState()))
 # lprint (' front porch ' + str(fPorchLightSwitch.basicevent.GetBinaryState()))
 # garageLightSwitch.basicevent.SetBinaryState(BinaryState=0)
 # cactusLightSwitch.basicevent.SetBinaryState(BinaryState=0)
 # fPorchLightSwitch.basicevent.SetBinaryState(BinaryState=0)
 # lprint (' garage ' + str(garageLightSwitch.basicevent.GetBinaryState()))
 # lprint (' cactus ' + str(cactusLightSwitch.basicevent.GetBinaryState()))
 # lprint (' front porch ' + str(fPorchLightSwitch.basicevent.GetBinaryState()))
 # If I want to see the various attributes of the device
 # lprint (' ' + garageLightSwitch.explain())
 # lprint (" done testing")
 # examine the status of the lights right now and update the database
 lprint (' checking current state of switches')
 garageSwitchChange(garageLightSwitch.get_state())
 cactusSwitchChange(cactusLightSwitch.get_state())
 fPorchSwitchChange(fPorchLightSwitch.get_state())
 
 lprint (" Setting up the callbacks for the devices")
 cactusLightSwitch.register_listener(cactusSwitchChange)
 garageLightSwitch.register_listener(garageSwitchChange)
 fPorchLightSwitch.register_listener(fPorchSwitchChange)
 
 lprint (" Setting up scheduled items")
 #------------------Stuff I schedule to happen -----
 scheditem = Scheduler()
 # every day at 1900, turn the outside lights on
 #scheditem.add_cron_job(outsideLightsOn, hour=19, minute=01)
 # every day at 2200, turn the outside lights off
 #scheditem.add_cron_job(outsideLightsOff, hour=22, minute=0)
 #just an interval timer for putting time in the log
 scheditem.add_interval_job(showTime, minutes=30)
 lprint (" starting scheduler")
 scheditem.start()
 # Create the message queue where commands can be read
 # I just chose an identifier of 13 because the house monitor
 # already took the number 12.
 Cqueue = sysv_ipc.MessageQueue(13, sysv_ipc.IPC_CREAT,mode=0666)
 firstTime = True
 
 lprint (" Going into the processing loop")
 while True:
  try:
   if (firstTime):
    while(True):
     try:
      # commands could have piled up while this was 
      # not running.  Clear them out.
      junk = Cqueue.receive(block=False, type=0)
      print "purging leftover commands", str(junk)
     except sysv_ipc.BusyError:
      break
    firstTime=False
   newCommand = Cqueue.receive(block=False, type=0)
   # type=0 above means suck every message off the
   # queue.  If I used a number above that, I'd
   # have to worry about the type in other ways.
   # note, I'm reserving type 1 messages for 
   # test messages I may send from time to 
   # time.  Type 2 are messages that are
   # sent by the php code in the web interface.
   # Type 3 are from the event handler. This is just like
   # the house monitor code in that respect.
   # I haven't decided on any others yet.
   handleCommand(newCommand)
  except sysv_ipc.BusyError:
   pass # Only means there wasn't anything there

  # Turn control over to the Wemo environment handler for a second so
  # changes happening can be recorded and handled
  env.wait(time=1)
 
 

I cut out the code to update my database since that is very specific to my implementation, and you'll notice down at the bottom, that control of the lights is handled by Sys V messages.  I don't control the lights with a console, everything comes from other processes sending messages.  For example, my web server software sends a message to this process to turn the lights on and off.  Another process sends messages to turn them on and off at certain times of the day.  That kind of thing.

Just cut that code out and insert whatever kind of control you want to use.  I also made a change to the ouimeaux python library to support asyncronous operation.  The library as it was constructed hung and waited for changes to happen.  I modified it a bit to return after a env.wait call; that's what the very last line in the code is doing; calling the wait for a second then getting back control so it can do other things.

There's a ton of things that can be done to make my control code better, but for now, it works.  I'll loop back to this project some time and add more versatility.  Maybe when the ouimeaux library is changed.

3 comments:

  1. Hi Dave,

    I've been following your posts.
    I'm very new to all this stuffs. I'm wondering that if i can use this python script on the Intel Galileo board to control the wemo?

    Do you know about that? I could use some of your help

    Thanks,
    Harshy

    ReplyDelete
  2. Harshy, I'm not sure. The python script was specifically written with linux in mind, so if you can get linux up on it, you should be able to adapt the script to work. You'll have to figure out a few things I'm sure, but since it's over ethernet, most of the changes will be relatively easy.

    ReplyDelete
  3. Hi Dave,

    Thanks for the quick response.
    The Intel Galileo has the sd card facility so i can boot the intel galileo board having a default linux image which supports python scripts.

    I'm very confused about how to run this script with the ouimeaux library.

    Can you please reply to me back on my email so that i can contact you via email?
    thanks for your help in advance.

    Thanks dave,

    Regards,
    Harshy

    ReplyDelete