Hack My House: ZoneMinder’s Keeping an Eye on the Place
Hacks are often born out of unfortunate circumstances. My unfortunate circumstance was a robbery– the back door of the remodel was kicked in, and a generator was carted off. Once the police report was filed and the door screwed shut, it was time to order cameras. Oh, and record the models and serial numbers of all my tools.
We’re going to use Power over Ethernet (POE) network cameras and a ZoneMinder install. ZoneMinder has a network trigger capability, and we’ll wire some magnetic switches to our network of PXE booting Pis, using those to inform the Zoneminder server of door opening events. Beyond that, many newer cameras support the Open Network Video Interface Forum (ONVIF) protocol and can do onboard motion detection. We’ll use the same script, running on the Pi, to forward those events as well.
Many of you have pointed out that Zoneminder isn’t the only option for open source camera management. MotionEyeOS, Pikrellcam, and Shinobi are all valid options. I’m most familiar with Zoneminder, even interviewing them on FLOSS Weekly, so that’s what I’m using. Perhaps at some point we can revisit this decision, and compare the existing video surveillance systems.
Cameras and Installation
Zoneminder generally works with any camera that follows the modern standards. I’m using a handful of four-megapixel Trendnet TV-IP314PI cameras, which seem to be the sweet spot for cost and quality. These cameras have a particularly odd quirk that took me several days to understand. To get motion detection running on the camera itself, I needed to update the camera firmware, but the browser interface simply refused to select a firmware file to upload. Many IP cameras make use of a browser plugin to view the live stream, and older firmware on the Trendnet units also require that plugin in order to upload the firmware update. I finally turned to a Windows 7 VM, installed the browser plugin, and got the firmware updated.
Camera placement takes planning to be effective. Coverage of the front and back doors is a must, and seeing whether your garage door is open is quite useful as well. I also decided to cover all the windows. If you can manage, it’s useful to stretch the Ethernet cable around the outside of the house, and hold the camera in place while you or a buddy pulls it up on a cell phone, in order to find the best placement. All told, I hung nine cameras.
Ethernet in Living Color
If you’re keeping track, that’s a grand total of many Ethernet cables. To help when sorting them out, I bought several colors of cabling: red, green, yellow, blue, and white. I highly recommend a color code. Mine goes like this: PoE cameras use yellow Ethernet, Raspberry Pis use red. Each room of the house gets two more cables, white for a VoIP phone and blue for a generic connection to the network. The green cable is for running something other than Ethernet over Cat 5, like a door or temperature sensor.
When I finish the interior, I’ll terminate these Ethernet cables on a set of patch panels, using color matching keystone jacks. The colors are more than just a novelty– when you’ve run a bunch of cables, it’s far too easy to lose track of which one is which.
Some ports need PoE and some don’t. I’m also planning to use VLANs to separate the various networks. Once it’s all done, we can take a deeper dive into selecting and configuring the smart switches that run the house. Keeping the Ethernet cabling as neat as possible will make the eventual configuration task much more manageable.
Zoneminder has some great documentation on how to get an install up and running, so rather than cover that ground again, we’ll look closer at how to use the Raspberry Pi network to link door sensors and ONVIF motion detection into the loop. To this end, I’ve put together the
zmhelper service, and made the code available on GitHub. Well look at the more interesting snippets below.
Zmtrigger is the Zoneminder component we’ll be talking to, and it listens on TCP port 6802. On the Zoneminder server, we’ll need to open that port in the firewall, and then enable the
OPT_TRIGGERS option through the web interface. This allows our service, running on a Pi, to inform Zoneminder that something important happened, and that it should start recording. Zoneminder can do motion detection natively, but offloading that work can be helpful when trying to run multiple cameras.
A door sensor is just a simple magnetic switch installed into the door jamb. When the door is closed, the magnet in the door pulls the switch closed, completing the circuit. The Pi’s GPIO ports are perfect to monitor this. One wire from the sensor goes to ground, the other to the GPIO pin. It’s best to put a resistor between that pin and the switch, to protect in the case of accidentally powering it. Some of the GPIO pins go high or low during the boot process, and shorting a hot GPIO directly to ground is a BAD THING(tm). Some of you might be thinking about pull-up resistors. The Raspberry Pi has software configurable pull-up and pull-down resistors that are already built in, so we don’t need the external resistors.
GPIO event detection isn’t limited to just door sensors. Motion detectors are the other interesting possibility. I hope to eventually look at this in more detail, in the context of retooling an old security system to be powered by a Pi.
Rather than poll that GPIO pin every few seconds, we set up an interrupt handler. This allows the event reporting code to trigger more quickly, and allows us to watch for ONVIF events at the same time. The other bit of magic going on here is the debouncing logic. The GPIO library does well at filtering out the bounces when there is a legitimate trigger, like the door opening. It fails at filtering out the bounces generated by the switch closing. To overcome this, we sleep for the bouncetime and check the GPIO status again.
GPIO.setmode(GPIO.BOARD) GPIO.setup(gpio_pinnum, GPIO.IN, pull_up_down=gpio_resistor) def handler(pin): time.sleep(gpio_bouncetime/1000) if GPIO.input(gpio_pinnum) == gpio_active_state: #Debounce check. If we're still active, it's a real event. s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((zmip, zmport)) s.send(gpio_mid +'|on+20|' + gpio_escore + '|' + gpio_ecause + '|' + gpio_etext) s.close() print("DOOR opened!") GPIO.add_event_detect(gpio_pinnum, gpio_edge, handler, bouncetime=gpio_bouncetime)
If ONVIF is configured, then we connect to the camera, pull any events in the cue, and wait for new events. The code here is blocking– it stops and waits for the response from the camera. That response is intentionally delayed until a new event is ready. The code then scans through the event data looking for whether there was motion detected.
mycam = ONVIFCamera(camIP, 80, username, password, wsdl_dir='/home/pi/.local/wsdl/') event_service = mycam.create_events_service() pullpoint = mycam.create_pullpoint_service() req = pullpoint.create_type('PullMessages') req.MessageLimit=100 while True: messages = Client.dict(pullpoint.PullMessages(req)) if 'NotificationMessage' in messages: try: messages = messages['NotificationMessage'] for x in messages: message = Client.dict(Client.dict(Client.dict(Client.dict(Client.dict(x)['Message'])['Message'])['Data'])['SimpleItem']) if message['_Name'] == 'IsMotion' and message['_Value'] == 'true': if time.time() - last_trigger > 15: print("Triggering!") last_trigger = time.time() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((zmip, zmport)) s.send(onvif_mid +'|on+20|' + onvif_escore+ '|' + onvif_ecause + '|' + onvif_etext) s.close() break except: print('Error fetching event')
We’ve looked at our first real use of the Raspberry Pi, feeding events into a Zoneminder instance. There’s more to come, like breaking the proprietary protocol on a garage door opener, building a touchscreen thermostat, and looking at how to securely access everything remotely. As always, sound off below about what you want to see in the future. Until next time, Happy Hacking!
Read More here.