Overly Complex Doorbell
I wanted to add some kind of doorbell indication to my office, and was almost ready to start buying parts for wired doorbell sensor like this one on hackaday.com , but I wasn’t sure how well it would work with the Beagle Bone Black that’s running my digitemp sensor network in the garage. I’d have to run about 20’ of wire over to the sensor from the Beagle and that was bound to effect the reliability.
So I came up with a ‘better’ solution that doesn’t require stringing any more
wires. Just a bit of duct-taped software. I’m running Strix
with a number of security cameras, including one on the front door right over the doorbell.
Strix uses Motion
to handle detection of movement
and creation of images that Strix turns into movies. Ends up Motion has a nice feature
called area_detect
that can be used to run a script if movement is detected in one or more of the 9 sections of
an image. This is much easier than using on_motion_detected
because I don’t need to limit
it to one part of the image. 3 of the 9 sections work just fine for eliminating
areas that I don’t care about for a doorbell feature.
So I added this to my front door Motion config file:
# Run a script when motion is detected near the door
area_detect "589"
# Pass the width, height, and pixels changed of the motion area
on_area_detected "/home/strix/bin/doorbell %i %J %D"
And wrote a simple python3
script to log the event and make a POST to my desktop:
#!/usr/bin/python3
import argparse
import time
import urllib.request
URL="http://desktop.local:8000/dingdong"
def setup_argparse():
parser = argparse.ArgumentParser(description="Virtual Motion Doorbell")
parser.add_argument("width", type=int, help="Width of motion area")
parser.add_argument("height", type=int, help="Height of motion area")
parser.add_argument("pixels", type=int, help="Number of changed pixels")
return parser.parse_args()
def main():
opts = setup_argparse()
with open("/var/tmp/doorbell.log", "a") as f:
f.write("%s: W=%s H=%s P=%s\n" % (time.ctime(), opts.width, opts.height, opts.pixels))
# Ring the 'doorbell' on ohop
urllib.request.urlopen(URL, b"")
if __name__=='__main__':
main()
I’m passing in the size of the movement area, and the number of pixels changed so that if I need to tweak things in the future I can.
On my desktop there is a totally insecure little service running that listens for the POST and runs a script. I wrote this in Go:
package main
import (
"flag"
"fmt"
"log"
"net/http"
"os/exec"
"time"
)
/* commandline flags */
type cmdlineArgs struct {
Port int // Port to bind to
Script string // Script to run when POST to /dingdong is received
Debug bool // Log debugging information
}
/* commandline defaults */
var cmdline = cmdlineArgs{
Port: 8889,
Script: "/bin/false",
Debug: false,
}
/* parseArgs handles parsing the cmdline args and setting values in the global cmdline struct */
func parseArgs() {
flag.IntVar(&cmdline.Port, "port", cmdline.Port, "Port to bind to")
flag.StringVar(&cmdline.Script, "script", cmdline.Script, "Script to execute when POST is received")
flag.BoolVar(&cmdline.Debug, "debug", cmdline.Debug, "Log debugging information")
flag.Parse()
}
func main() {
parseArgs()
fmt.Printf("Listening for doorbell on port %d\n", cmdline.Port)
fmt.Printf("Running script: %s\n", cmdline.Script)
http.HandleFunc("/dingdong", func(w http.ResponseWriter, r *http.Request) {
// Is it a POST?
if r.Method != "POST" {
http.Error(w, "", http.StatusMethodNotAllowed)
return
}
if cmdline.Debug {
fmt.Printf("%s: DING DONG!\n", time.Now().Local())
}
// Run the script and get the result code
cmd := exec.Command(cmdline.Script)
err := cmd.Run()
if err != nil {
http.Error(w, err.Error(), http.StatusServiceUnavailable)
} else {
fmt.Fprintf(w, "OK\n")
}
})
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", cmdline.Port), nil))
}
Pass it the port to listen on, and the script to run. It’s no more complicated than the examples in the Go net/http documentation.
The final piece is the script that opens the Strix camera page in a new tab of my running browser.
#!/bin/sh
DISPLAY=:0
if xwininfo -d $DISPLAY -root -children | grep -i "Strix"; then
exit 0
fi
exec firefox --new-tab http://cameras.brianlane.com
This looks at the list of window titles and checks to see if the Strix tab is currently selected (it will only be listed if selected) to prevent a stackup of open tabs when I’m away from my desk. If it isn’t already open/selected it opens a new tab with the Strix page to get my attention.
It’s way more complex than a wired doorbell, and I expect it to fail in interesting ways, but it was easy to throw together this morning and seems to be working ok for now. So I’d call it ‘Good Enough(tm)’ for the moment.