“Ultimate” Arduino Doorbell – part 2 (Software)

As mentioned in the previous post about my arduino doorbell I wanted to get the doorbell and my Chumby talking.

As the Chumby runs Linux, is on the network and is able to run a recent version of Python (2.6) it seemed like it would be pretty easy to get it to send Growl notifications to the local network.

This did indeed prove fairly easy and involved three main activities:

  • Listening to the serial port for the doorbell to ring
  • Using Bonjour (formally Rendezvous)/Zeroconf to find computers on the network
  • Sending network Growl notifications to those computers

Getting Python and PySerial running on the Chumby is pretty easy. The Chumby simply listens for the doorbell to send the string ‘DING DONG’ and can then react as needed.


def listen_on_serial_port(port, network):
    ser = serial.Serial(port, 9600, timeout=1)
    try:
        while True:
            line = ser.readline()
            if line is not None:
                line = line.strip()
            if line == 'DING DONG':
                network.send_notification()
    finally:
        if ser:
            ser.close()

port is the string representing the serial port (e.g. /dev/ttyUSB0). network is an object that handles the network notifications (see below).

The network object (an instance of Network) has two jobs. Firstly to find computers on the network (using PicoRendezvous – now called PicoBonjour) and secondly to send network growl notifications to those computers.

A background thread periodically calls Network.find, which uses multicast DNS (Bonjour/Zeroconf) to find the IP addresses of computers to notify:


class Network(object):
    title = 'Ding-Dong'
    description = 'Someone is at the door'
    password = None
    
    def __init__(self):
        self.growl_ips = []
        self.gntp_ips = []

    def _rendezvous(self, service):
        pr = PicoRendezvous()
        pr.replies = []
        return pr.query(service)
    
    def find(self):
        self.growl_ips = self._rendezvous('_growl._tcp.local.')
        self.gntp_ips  = self._rendezvous('_gntp._tcp.local.')
        def start(self):
        import threading
        t = threading.Thread(target=self._run)
        t.setDaemon(True)
        t.start()
    
    def _run(self):
        while True:
            self.find()
            time.sleep(30.0)

_growl._tcp.local. are computers that can handle Growl UDP packets and _gntp._tcp.local. those that can handle the newer GNTP protocol. Currently the former will be Mac OS X computers (running Growl) and the latter Windows computers (running Growl for Windows).

I had to tweak PicoRendezvous slightly to work round a bug on the Chumby version of Python, where socket.gethostname was returning the string '(None)', but otherwise this worked ok.

When the doorbell activates netgrowl is used to send Growl UDP packets to the IP addresses in growl_ips and the Python gntp library to notify those IP addresses in gntp_ips (but not those duplicated in growl_ips). For some reason my Macbook was appearing in both lists of IP addresses, so I made sure that the growl_ips took precedence.


    def send_growl_notification(self):
        growl_ips = self.growl_ips
        
        reg = GrowlRegistrationPacket(password=self.password)
        reg.addNotification()
    
        notify = GrowlNotificationPacket(title=self.title,
                    description=self.description,
                    sticky=True, password=self.password)
        for ip in growl_ips:
            addr = (ip, GROWL_UDP_PORT)
            s = socket(AF_INET, SOCK_DGRAM)
            s.sendto(reg.payload(), addr)
            s.sendto(notify.payload(), addr)

    def send_gntp_notification(self):
        growl_ips = self.growl_ips
        gntp_ips  = self.gntp_ips
        
        # don't send to gntp if we can use growl
        gntp_ips = [ip for ip in gntp_ips if (ip not in growl_ips)]
        
        for ip in gntp_ips:
            growl = GrowlNotifier(
                applicationName = 'Doorbell',
                notifications = ['doorbell'],
                defaultNotifications = ['doorbell'],
                hostname = ip,
                password = self.password,
            )
            result = growl.register()
            if not result:
                continue
            result = growl.notify(
                noteType = 'doorbell',
                title = self.title,
                description = self.description,
                sticky = True,
            )

    def send_notification(self):
        '''
        send notification over the network
        '''
        self.send_growl_notification()
        self.send_gntp_notification()

The code is pretty simple and will need some more tweaking. The notification has failed for some reason a couple of times, but does usually seem to work. I’ll need to start logging the doorbell activity to see what’s going on, but I suspect the Bonjour/Zeroconf is sometimes finding no computers. If so I’ll want to keep the previous found addresses hanging around for a little longer – just in case it’s a temporary glitch.

You can see the full code in my doorbell repository.

2 thoughts on ““Ultimate” Arduino Doorbell – part 2 (Software)

  1. Hi,

    Interesting stuff! I’m pretty new with embedded stuff, but am working on a project that might be similar with the notification use.

    Does this notification need to have internet connectivity? It doesn’t look like it, but I could be wrong. That’s basically what I’m trying to do – use a growl type notification on a local network, that only has intermittent connection to the net, and the notifications need to be pretty quick.

    I’m using a thing called a flyport, which has a pic controller and a wifi connection and built in webserver. You might want to have a look, a pretty cool piece of hardware, with good wifi built in, could easily do the arduino/chumby work within your project, if you wanted to eliminate one piece of hardware from the equation.

    Jim

  2. Hi Jim,

    the growl notification didn’t need internet connectivity – just a connection to the local network. TBH it was a bit flaky, as the Python process on the Chumby would crash sometimes. Suspect that a RaspberryPi or similar would be perfectly ok.

    The Mac Growl stuff was fairly easy – basically you just broadcast a UDP packet and that’s it. IIRC the Windows version wasn’t as straightforward.

    cheers,

    John

Comments are closed.