An example python Bonjour script was modified to listen for Apple's iOS '_apple-mobdev2._tcp' Bonjour service. This is used in the Perceptive Automation Indigo Home Control System to track the presence of iPhones (smartphones) and hence presence of house occupants. The script runs on the same Macintosh as the Indigo server but can also be run on Linux systems and report iPhone status back to an Indigo server. It has been sucessfully tested on a Raspberry Pi in another city, reporting iPhone presence back to the Indigo server. So far it is all working well after a couple months of testing and a few weeks of use.
The only shortcoming observed is that it takes 30 minutes to be sure the iPhone is gone. Usually the Bonjour advertisements occur ever few seconds or minutes but sometimes they go as long as 25 minutes.
A few times an iPhone has stopped advertising the Bonjour service. A reboot of the iPhone usually remedies this. The '_apple-mobdev2._tcp' service is used by iTunes for Wi-Fi sync. The iPhone will not advertise this service unless iTunes has enabled Wi-Fi sync for that device. When the iPhone has stopped advertising, this impacts iTunes Wi-Fi sync also. Worst case, the Wi-Fi sync option in iTunes has to be cycled to restart the advertisements.
The program is based on and uses pybonjour, the Pure-Python interface to Apple Bonjour and compatible DNS-SD libraries available at googlecode. Follow the documentation for setup. If you are putting it on Linux, you will need to install the DNS-SD libraries per the documentation. The DNS-SD libraries built with no problem on a Raspberry Pi running debian.
The customized scripts were cobbled together without any programming elegance. It is the author's first attempt at python so be gentle. They need extreme improvements but they work.
There is a
configuration file to define the Indigo variables and the specific
iPhone names.
The config file and text are included below. Feel free to use them
as you wish with no warranty. The files are configured assuming the
config file goes in /usr/local/etc
and the executable goes in
/usr/local/bin
. A launchd plist can be configured to run the script
at boot on a Mac. Redirect STDOUT to /dev/null, its quite noisy.
It can also be tested from the command line and the
output shows each detection of the service.
This seems to be a good way to determine presence and would be even better if an iPhone app advertised a specific service on a defined frequency of about 5 minutes. An iOS app could start the advertisements when the device attached to a Wi-Fi network.
Another improvement would be to link directly to the Indigo variables instead of using the RESTful method. Although the RESTful method should be an option for remote status reports.
The Configuration File
# radar.conf
# radar.py configuration
# Indigo host and authentication
Host = "http://localhost:8176/variables/"
Realm = "Indigo Control Server"
Username = 'username'
Password = 'password'
# search string from device name and Indigo variable name
# search string is from the device Settings->General->About->Name
# define four devices even if you don't have that many
iOSone='myPhone', 'bonjourTimeOne'
iOStwo='herPhone', 'bonjourTimeTwo'
iOSthree='thatPhone', 'bonjourTimeThree'
iOSfour='anotherPhone','bonjourTimeFour'
# Bonjour service name and timeout
regtype = '_apple-mobdev2._tcp'
timeout = 5
Program Code
#!/usr/bin/python
# Sat Oct 19 14:42:08 EDT 2013
# based on https://code.google.com/p/pybonjour/
import select
import sys
import pybonjour
import datetime
import urllib2
timeout = 5
resolved = []
config = {}
file_name = "/usr/local/etc/radar.conf"
########## read configuration
config_file= open(file_name)
for line in config_file:
line = line.strip()
if line and line[0] is not "#" and line[-1] is not "=":
var,val = line.rsplit("=",1)
# print "val is ",val
config[var.strip()] = val.strip()
############## set variables
# Indigo host and authentication
Host = eval(config["Host"])
Realm = eval(config["Realm"])
Username = eval(config["Username"])
Password = eval(config["Password"])
# search string from device name and Indigo variable name
iOSone = eval(config["iOSone"])
iOStwo = eval(config["iOStwo"])
iOSthree = eval(config["iOSthree"])
iOSfour = eval(config["iOSfour"])
regtype = eval(config["regtype"])
################ functions
def toIndigo(varNameNow, justName, today):
print 'updating ', justName[0]
print
URL = Host + varNameNow + '?_method=put&value=' + today
authhandler = urllib2.HTTPDigestAuthHandler()
authhandler.add_password(Realm, URL, Username, Password)
opener = urllib2.build_opener(authhandler)
urllib2.install_opener(opener)
page_content = urllib2.urlopen(URL)
def resolve_callback(sdRef, flags, interfaceIndex, errorCode, fullname,
hosttarget, port, txtRecord):
if errorCode == pybonjour.kDNSServiceErr_NoError:
now = datetime.datetime.now()
print str(now)
print 'Resolved service:'
print ' fullname =', fullname
print ' hosttarget =', hosttarget
print ' port =', port
resolved.append(True)
justName = hosttarget.split(".", 1)
if iOSone[0] in justName[0]:
varNameNow = iOSone[1]
today = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S")
toIndigo(varNameNow, justName, today)
if iOStwo[0] in justName[0]:
varNameNow = iOStwo[1]
today = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S")
toIndigo(varNameNow, justName, today)
if iOSthree[0] in justName[0]:
varNameNow = iOSthree[1]
today = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S")
toIndigo(varNameNow, justName, today)
if iOSfour[0] in justName[0]:
varNameNow = iOSfour[1]
today = datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S")
toIndigo(varNameNow, justName, today)
def browse_callback(sdRef, flags, interfaceIndex, errorCode, serviceName,
regtype, replyDomain):
if errorCode != pybonjour.kDNSServiceErr_NoError:
return
if not (flags & pybonjour.kDNSServiceFlagsAdd):
now = datetime.datetime.now()
print str(now)
print 'Service removed'
print ' serviceName =', serviceName
print ' regtype =', regtype
return
print 'Service added; resolving'
resolve_sdRef = pybonjour.DNSServiceResolve(0,
interfaceIndex,
serviceName,
regtype,
replyDomain,
resolve_callback)
try:
while not resolved:
ready = select.select([resolve_sdRef], [], [], timeout)
if resolve_sdRef not in ready[0]:
print 'Resolve timed out'
break
pybonjour.DNSServiceProcessResult(resolve_sdRef)
else:
resolved.pop()
finally:
resolve_sdRef.close()
browse_sdRef = pybonjour.DNSServiceBrowse(regtype = regtype,
callBack = browse_callback)
try:
try:
while True:
ready = select.select([browse_sdRef], [], [])
if browse_sdRef in ready[0]:
pybonjour.DNSServiceProcessResult(browse_sdRef)
except KeyboardInterrupt:
pass
finally:
browse_sdRef.close()
No comments:
Post a Comment