Wednesday, 20 March 2013

Raspberry Pi - Fridge Monitor

My fridge-freezer has this control unit on the front of it:


As a geek I'm not prepared to believe this display so I thought I'd have a closer look at the environment in the fridge using the AirPi environment monitor I previously built.

Breaking out the battery pack I previously use for our Pi powered Lego car I was able to get the whole unit to run in my fridge.  As you can see from the image below, the Pi looks at home with it's distant cousins!



The WiFi worked OK through my fridge door so I was able to upload the measurements to COSM.  Here's how the temperature changed over the 4 hours period I monitored:
The chart isn't great, each vertical column represents 30 minutes and and the Y-axis scale is a tad screwy.  You can see that I put the monitor in the fridge at just before 1800 and took at out at 2200.  It actually took about 50 minutes to get down to the temperature of the fridge; I assume that this is because of the residual heat in the temperature monitor and it's surroundings.  You can then see the temperature go through about 3 cycles with the minimum point being ~3.8C and the maximum point being ~5.6C.  So I would say it spends more time above 4C than below it...

This chart shows the light level:

The light level needs calibration but you can see relative light levels quite easily.  I only take samples every minute so I may have missed instances when I opened the fridge that evening.  However there is a definite peak at about 1830 so caught an instance then.

This chart shows humidity:

The fridge boasts some form of humidity control, (some form of fan kicks in when you close the door).  To me there's not too much of a pattern, perhaps some form of cycle going on in the mid part of the evening.

This chart shows barometric pressure:


The pressure just grows throughout the evening.  I didn't have a pressure sensor outside so I don't know if this is what was happening in the general environment.

All in all,very interesting; might have to do it again some time.  I was considering doing the freezer as well but was worried what would happen if the kit got frosty..  









Tuesday, 12 March 2013

Sky IP Speech Control with Android

Just after Christmas I asked my eldest daughter what geekery she wanted me to do next.  She suggested being able to control the TV by speaking to it.  Sounded like a challenge to me....

Overall, a pretty easy problem to solve.  If you've got about £1,500 to spare you can buy a Samsung Smart TV that comes with voice (and gesture control).  That's not pocket money prices and simply buying something that does the whole job is not the Geek Dad way.

A colleague told me about how he can control his Sky box with his iPad over his home WiFi network.  Sounds like an opportunity.   My old Sky box gave up the ghost later last year and was replaced with a shiny new black Sky+ HD box.  This has an ethernet port on the back of it and, by purchasing a Sky Wireless Controller I could use my wife's iPad as a remote control and also use the Sky On Demand service.

A quick Google search for "sky ipad protocol" led me to this blog:
http://www.gladdy.co.uk/blog/2012/08/18/sky-dnla-upnp-the-ipad-app-and-the-ip-control-protocol/

This chap has pretty much done all the hard work to decode the DLNA/ UPnP protocol used by the Sky box and has published a full code listing.  One thing he describes is how the Sky box constantly broadcasts SSDP messages to your LAN so I set up Wireshark to have a look at this. Here's an example:


So this seems to show the Sky box saying, "I am here, I have a file called description37.xml that describes what I can do".  A quick Python script to download description37.xml gets you an XML file that points to several other resources like "player_avt.xml", "player_cm.xml", "player_rc.xml" that seem to describe some SOAP services that the Sky box supports.

Rather than re-inventing the wheel I decided to use the brilliant code that Liam Gladdy published.  The Python script didn't work on my PC Python installation (missing packages) but did work on my Raspberry Pi.   Here's a screenshot of it in action:


So the script listens for the SSDP broadcasts, identifies the IP address for the Sky box and allows you to send SOAP commands to the Sky box.  It works a treat and it's a great way to annoy my wife, (keeper of the Sky remote).

The one thing the script didn't seem to let me do was to change channel on the Sky box, (essential to meet my daughter's challenge).  Another quick Google led me to this forum topic:
http://www.gossamer-threads.com/lists/mythtv/users/525915

These chaps have worked out that the "SetAVTransportURI" operation is used to change the channel on the Sky box and they've published some Python code showing how to do it. So using this, and reverse engineering the code for pause, play and stop from the Liam Gladdy code I was able to write a Python script with functions for pause, play, stop and change channel.

Here's a snippet, a function that sends a pause SOAP command:

# Do a command to pause the playback
#
def PauseSkyBox():
    XML="""<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:Pause xmlns:u="urn:schemas-nds-com:service:SkyPlay:2">
<InstanceID>0</InstanceID>
</u:Pause>
</s:Body>
</s:Envelope>"""

    headers=  { "Content-Length": "%d" % len(XML), "SOAPACTION": "\"urn:schemas-nds-com:service:SkyPlay:2#Pause\"", "Content-Type": "text/xml; char-set=utf-8"  }

    try:
conn = httplib.HTTPConnection("%s:49153" % skyAddress)
conn.request("POST",  "/SkyPlay2", "",  headers)
conn.send(XML)
response = conn.getresponse().read()
        print response
        conn.close()

dom = parseString(response)

    except Exception as inst:
sys.exit("Error doing a Pause command from %s: %s" % (skyAddress, inst))


The function simply creates the XML command as a string, creates a HTTP header, sends the command and awaits a response.

Using SL4A and Android speech recognition (see here for a previous example) it was easy to write a Python script to recognise speech commands and call functions to send a SOAP command to the Sky box. The code implements pause, play, stop, fast-forward (play with a different speed), rewind (play with negative speed) and changing channels.  Observations:
  • The Android speech recognition algorithm doesn't work for me with the word "pause".  It translates to "polls" or "horse".  Must be my geeky accent.   I use the word "still" instead.
  • I'm not 100% happy with the long "elif"  statement for each of the fast forward and rewind translations.  With a bit of thought I could do this better I'm sure.
  • The code relies upon a text file that contains a mapping from a decimal channel number to a hex equivalent and a channel name.  Download your own from one of the postings on the http://www.gossamer-threads.com/lists/mythtv/users/525915 forum.  I found my channel mappings to be different to those in the file so you'll have to play with this a bit.  I put this in the root directory of my SD card on my handset and then used the chdir ('/mnt/sdcard') command.
Here's a full code listing:


# Sky remote controller
# References http://www.gladdy.co.uk/blog/2012/08/18/sky-dnla-upnp-the-ipad-app-and-the-ip-control-protocol/
# http://www.gossamer-threads.com/lists/mythtv/users/525915

#!/usr/bin/python

#Import statements 
import  httplib,  sys, os
from  xml.dom.minidom import parseString
import android
from os import chdir

#Constants
skyAddress = "192.168.0.6"        # IP Address of Sky+HD Box
skyChannelsFile = "sky_channel_guide.txt" # Location of Channel Data

# Parse through tab delmited channel listing and return HEX Channel No

def findSkyChannel(channelFind):
    
    try:
channelsFile = open (skyChannelsFile, "r")
for line in channelsFile:
   rawdata = line.split('\t')
   if rawdata[0] == channelFind:
channelsFile.close()
return (rawdata[3], rawdata[1])
    
channelsFile.close()
return ("0", "Not Found")

    except Exception as inst:
sys.exit("Error in findSkyChannel: %s" % inst)

# Change channel on SkyHD Box via IP given HEX Channel ID
#
def changeSkyChannel(channelId):
    
    XML="""<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:SetAVTransportURI xmlns:u="urn:schemas-nds-com:service:SkyPlay:2">
<InstanceID>0</InstanceID>
<CurrentURI>xsi://%s</CurrentURI>
<CurrentURIMetaData>NOT_IMPLEMENTED</CurrentURIMetaData>
</u:SetAVTransportURI>
</s:Body>
</s:Envelope>""" % channelId

    headers = { "Content-Length": "%d" % len(XML), "SOAPACTION": "\"urn:schemas-nds-com:service:SkyPlay:2#SetAVTransportURI\"", "Content-Type": "text/xml; char-set=utf-8"  }

    try:
conn = httplib.HTTPConnection("%s:49153" % skyAddress)
conn.request("POST",  "/SkyPlay2", "",  headers)
conn.send(XML)
response=conn.getresponse()
data = response.read()
conn.close()

#### Debug Purposes
#os.path.exists(SkyTempFile) and os.remove(SkyTempFile)
#print data
        #os.chdir 'c:\temp'
        #f = open("skytempfile.txt",'w')
#f.write(data)
#f.close()
####
    except Exception as inst:
sys.exit("Error changing channel: %s" % inst)
pass

# Get Current Channel from skyHD Box
#
def getSkyChannel():
    XML="""<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:GetMediaInfo xmlns:u="urn:schemas-nds-com:service:SkyPlay:2">
<InstanceID>0</InstanceID>
</u:GetMediaInfo>
</s:Body>
</s:Envelope>"""

    headers=  { "Content-Length": "%d" % len(XML), "SOAPACTION": "\"urn:schemas-nds-com:service:SkyPlay:2#GetMediaInfo\"", "Content-Type": "text/xml; char-set=utf-8"  }

    try:
conn = httplib.HTTPConnection("%s:49153" % skyAddress)
conn.request("POST",  "/SkyPlay2", "",  headers)
conn.send(XML)
response = conn.getresponse().read()

dom = parseString(response)
return (dom.getElementsByTagName('CurrentURI')[0].firstChild.data.split('//')[1])

    except Exception as inst:
sys.exit("Error getting current channel from %s: %s" % (skyAddress, inst))

# Do a command to pause the playback
#
def PauseSkyBox():
    XML="""<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:Pause xmlns:u="urn:schemas-nds-com:service:SkyPlay:2">
<InstanceID>0</InstanceID>
</u:Pause>
</s:Body>
</s:Envelope>"""

    headers=  { "Content-Length": "%d" % len(XML), "SOAPACTION": "\"urn:schemas-nds-com:service:SkyPlay:2#Pause\"", "Content-Type": "text/xml; char-set=utf-8"  }

    try:
conn = httplib.HTTPConnection("%s:49153" % skyAddress)
conn.request("POST",  "/SkyPlay2", "",  headers)
conn.send(XML)
response = conn.getresponse().read()
        print response
        conn.close()

dom = parseString(response)
#Can't get this dom stuff to work fpr pause.  Don't think it's required though for doing a pause!!!
        #print dom
        #return (dom.getElementsByTagName('CurrentURI')[0].firstChild.data.split('//')[1])

    except Exception as inst:
sys.exit("Error doing a Pause command from %s: %s" % (skyAddress, inst))

# Do a command to stop the playback
#
def StopSkyBox():
    XML="""<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:Stop xmlns:u="urn:schemas-nds-com:service:SkyPlay:2">
<InstanceID>0</InstanceID>
</u:Stop>
</s:Body>
</s:Envelope>"""

    headers=  { "Content-Length": "%d" % len(XML), "SOAPACTION": "\"urn:schemas-nds-com:service:SkyPlay:2#Stop\"", "Content-Type": "text/xml; char-set=utf-8"  }

    try:
conn = httplib.HTTPConnection("%s:49153" % skyAddress)
conn.request("POST",  "/SkyPlay2", "",  headers)
conn.send(XML)
response = conn.getresponse().read()
        print response
        conn.close()

dom = parseString(response)
#Can't get this dom stuff to work fpr pause.  Don't think it's required though for doing a pause!!!
        #print dom
        #return (dom.getElementsByTagName('CurrentURI')[0].firstChild.data.split('//')[1])

    except Exception as inst:
sys.exit("Error doing a Stop command from %s: %s" % (skyAddress, inst))


# Do a command to play, fast forward or rewind
#
def PlaySkyBox(PlaySpeed):
    XML="""<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<u:Play xmlns:u="urn:schemas-nds-com:service:SkyPlay:2">
<InstanceID>0</InstanceID>
<Speed>%s</Speed>
</u:Play>
</s:Body>
</s:Envelope>""" % PlaySpeed

    headers=  { "Content-Length": "%d" % len(XML), "SOAPACTION": "\"urn:schemas-nds-com:service:SkyPlay:2#Play\"", "Content-Type": "text/xml; char-set=utf-8"  }

    try:
conn = httplib.HTTPConnection("%s:49153" % skyAddress)
conn.request("POST",  "/SkyPlay2", "",  headers)
conn.send(XML)
response = conn.getresponse().read()
        print response
#dom = parseString(response)
#return (dom.getElementsByTagName('CurrentURI')[0].firstChild.data.split('//')[1])

    except Exception as inst:
sys.exit("Error doing a Play command from %s: %s" % (skyAddress, inst))

###The main body of code

#Set up out droid object
droid = android.Android()

#Print some funky stuff
print "###################################################"
print "# SL4A Sky remote                                 #"
print "###################################################"

#Change the directory to where the description file is stored.
chdir ('/mnt/sdcard')



#Boolean to control the loop
KeepLooping = True

while KeepLooping == True:
  
  #Get the speech
  speech = droid.recognizeSpeech("Talk Now",None,None)
  print "You said: " + speech[1]
  
  #Big If statement
  if speech[1] == "still":
    PauseSkyBox()
  elif speech[1] == "play":
    PlaySkyBox("1")
  elif speech[1] == "stop":
    StopSkyBox()
  elif speech[1] == "2":     #Next commands do fast forward
    PlaySkyBox("2")        
  elif speech[1] == "6":
    PlaySkyBox("6")
  elif speech[1] == "12":
    PlaySkyBox("12")
  elif speech[1] == "30":
    PlaySkyBox("30")
  elif speech[1] == "minus 2":   #Next commands do re-wind
    PlaySkyBox("-2")
  elif speech[1] == "minus 6":
    PlaySkyBox("-6")
  elif speech[1] == "minus 12":
    PlaySkyBox("-12")
  elif speech[1] == "minus 30":
    PlaySkyBox("-30")
  #Now test to see if it's a channel number.  So three characters and a number
  elif len(speech[1]) == 3 and speech[1].isdigit:  
    #Get the channel details (in hex) from the description file
    channelId, channelName = findSkyChannel(str(speech[1]))
    #See if we had nothing returned
    if channelId == "0":
print "Unable to find HEX channel No"
    elif channelId == getSkyChannel():
print "Sky+HD is already tuned to %s (%s - %s).  No Need to Change" % (channelName, speech[1], channelId)
    else:   #Actually change the channel
      changeSkyChannel(channelId)
      print "Sky+HD Channel Changed to %s (%s - %s)" % (channelName, speech[1], channelId)
  elif speech[1] == "exit":
    KeepLooping = False
  else:
    print "Command not known!"  

Message from Emily, eldest daughter of the Geek Dad:

hi do you like this boring blog?

Friday, 8 March 2013

Raspberry Pi Environment Monitor

I follow Raspberry Pi on Twitter and one day they posted a link to this site.  Some students from Westminster College have posted full details on how to connect some sensors to your Raspberry Pi to monitor such things as air temperature, barometric pressure, humidity and gas levels. Full credit to them. Brilliant!

They posted full details of how to build one so some eBay purchases, some soldering, some breadboard action and some Linux commands later I had a working one of my own.  The instructions are ace, it was pretty much paint by numbers with only a few minor tweaks.  Here's an image:


Key components I used:

Using the AirPi you can upload measurements to COSM, (see my previous posts), so I'm now measuring pressure, humidity, light levels and temperature and uploading all measurements to COSM feed 104017.

So I can use this to annoy my wife, (e.g. when at working asking "why is it 21 degrees at home, turn the heating off").

I have a de-humidifier at home so it's interesting to see the impact of this, (and show it's not just a noisy consumer of electricity).  Here's an image:


So the humidity cycles up and down over the course of the day and then at 2045 I whacked up the dehumidifier fan level and lowered the desired humidity level.  You can see the quick drop off in humidity.  It works (the dehumidifier that is!).