Using xAP and Python

python-xap.jpg In this section we discuss how to interface with xAP devices, with a bias to the Home Automation Hub, using the Python programming language. Even with very minimal skills you can modify and change the sample programs provided to create your own solutions.

As Python run on Windows as well as Linux these programs will work on both platforms. As well as natively on the livebox if you grab the Python for MIPS bundle.

Remember as xAP is a Broadcast protocol none of these programs need to be executed on the Livebox to interact with its hardware. As these programs are demonstrators none of them are xAP-hub aware so only one can be ran on a computer at a time. They demonstrate the absolute minimum amount of code that is necessary to send and receive xAP messages.

xAP message snooper

Listening to xAP messages on the LAN

#!/usr/bin/env python
# xAP message listener - simplistic
 
import socket, traceback
 
host = ''
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((host, 3639))
 
while 1:
	try:
		message, address = s.recvfrom(8192)
		print "%s: %s" % (address, message)
	except (KeyboardInterrupt, SystemError):
		raise
	except:
		traceback.print_exc()

xAP relay toggle

Toggling the relays on and off using python is very easy. Each will go TICK on/off in sequence.

import socket
import time
 
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
 
for relay in range(1,5):
    for state in ['on','off']:
        msg="""xap-header
{
v=12
hop=1
uid=FF000F00
class=xAPBSC.cmd
source=dbzoo.livebox.demo
target=dbzoo.livebox.controller:relay.%s
}
output.state.1
{
id=*
state=%s
}
"""
        s.sendto(msg % (relay, state), ('<broadcast>', 3639))
        time.sleep(1)

xAP LCD Clock

A simplistic xAP CLOCK in python. Every minute the LCD of the livebox will be updated.

#!/usr/bin/env python
# xAP - Simplistic LCD Clock
 
from socket import *
from time import localtime, strftime, sleep
 
s = socket(AF_INET, SOCK_DGRAM)
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
while 1:
    msg="""xap-header
{
v=12
hop=1
uid=FF000F00
class=xAPBSC.cmd
source=dbzoo.livebox.demo
target=dbzoo.livebox.Controller:lcd
}
output.state.1
{
id=*
text=%s
}
"""
    t = localtime()
    s.sendto(msg % strftime("%d %b, %H:%M", t), ('<broadcast>', 3639))
    sleep(60 - t[5])

Without a HUB we are only able to run a single xAP program on a computer at a time, as port 3639, the default xAP port, can only be accessed by one program.

To get around this limitation we run an xAP hub, this performs the same function a the hardware variant of the same name. It allows multiple xAP applications to share the 3639 port which the HUB will manage for us.

Various hub implementations can be found on the xAP Automation site

Personally I'm a fan of http://patrick.lidstone.net/haweb/hub.htm

The support library xaplib.zip provides additional code to handle integration with a HUB.

#!/usr/bin/env python
#
# XAP support library
 
import socket, traceback, time
 
class Xap:
        def __init__(self, uid, source):
                self.heartbeat_tick = 0;
                self.uid = uid
                self.source = source
                self.port = 0
                self.running = 1
 
        def run(self, func):
                self.connect()
                while self.running:
                        if self.port:
                                self.heartbeat(60)
                        try:
                                func(self)
                        except KeyboardInterrupt:
                                self.done()
                                pass
                        except socket.timeout:
                                pass
                        except:
                                traceback.print_exc()
                                self.done()
 
        def done(self):
                self.gout.close()
                self.gin.close()
                self.running = 0
 
        def connect(self):
                host = ''
                self.gin = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                self.gin.settimeout(60)
                try:
                        self.gin.bind((host, 3639))
                except socket.error, msg:
                        print "Broadcast socket port 3639 in use"
                        print "Assuming a hub is active"
                        host = '127.0.0.1'
                        for self.port in range(3639,4639):
                                try:
                                        self.gin.bind((host, self.port))
                                except socket.error, msg:
                                        print "Socket port %s in use" % self.port
                                        continue
                                print "Discovered port %s" % self.port
                                break
 
                self.gout = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                self.gout.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
 
        def send(self ,msg):
                self.gout.sendto(msg, ('<broadcast>', 3639))
 
        def sendMsg(self, clazz, target, msg):
                msg = """xap-header
{
v=12
hop=1
uid=%s
class=%s
source=%s
target=%s
}
%s""" % (self.uid, clazz, self.source, target, msg)
                self.send(msg)
 
        def sendLCDMsg(self, msg):
                msg = "output.state.1\n{\nid=*\ntext=%s\n}" % msg
                self.sendMsg("xAPBSC.cmd","dbzoo.livebox.Controller:lcd", msg)
 
        def sendSMS(self, num, msg):
                msg = "outbound\n{\nnum=%s\nmsg=%s\n}" % (num, msg)
                self.sendMsg("sms.message","dbzoo.livebox.sms", msg)
 
        def receive(self):
                try:
                        return self.gin.recvfrom(8192)
                except KeyboardInterrupt:
                        self.done()
                        pass
 
# The HUB won't relay messages to us until it see's our heartbeat knows our listening port.
# This must be periodically sent to keep it active on the HUB.
        def heartbeat(self, interval):
                now = time.time()
                if now - self.heartbeat_tick > interval or self.heartbeat_tick == 0:
                        self.heartbeat_tick = now
                        msg="""xap-hbeat
{
nv=12
hop=1
uid=%s
class=xap-hbeat-alive
source=%s
interval=%s
port=%s
}"""
                        self.send(msg % (self.uid, self.source, interval, self.port))

The support library greatly simplifies the process of writing a small xaplet.

xAP message snoop

A message snooper is now trivially implemented

#!/usr/bin/env python
# xAP message listener - HUB aware
 
from xaplib import Xap
 
def snoopTraffic(xap):
    print "MSG: %s ADDR: %s" % xap.receive()
 
Xap("FF000F00","dbzoo.livebox.Snoop").run(snoopTraffic)

xAP LCD Clock

Revisiting our simplistic clock we can rewrite the code using fewer lines and at the same time making it hub friendly.

#!/usr/bin/env python
# LCD Clock
 
from xaplib import Xap
from time import localtime, strftime, sleep
 
def clock(xap):
    t = localtime()
    xap.sendLCDMsg( strftime("%d %b, %H:%M", t) )
    sleep(60 - t[5])
 
Xap("FF000F00","dbzoo.livebox.demo").run(clock)

Demo code

Some other samples which might be useful

  • xap-sms-event.zip - Watch out for an RF or RELAY event when one if detected send an SMS of the change
  • xap-sms-lcd.zip - Watch out for inbound SMS messages when one if detected display the message on the livebox LCD
  • xap-input-smtp.zip - Send an email on an input event