Project Doris: Linux Doorbell

Be the first kid on your block to have a linux doorbell!

Back in 2012, the Raspberry Pi computer became available as an educational/hobbyist device. This computer had an amazing amount of computing power and hardware access for inputs, outputs, sound and other things. The first model was “Raspberry Pi Model B”. It made a big splash in England, where it originated and was very hard to get in the USA. I had to wait for the “Model B Version 2” but I managed to purchase a better one than the first available and got to work on my first Raspberry Pi project.  I built a doorbell.  Her name is Doris.

This project consists of a Raspberry Pi model B version 2, small 2 watt audio amplifier, speakers, 2 variable voltage power supplies, small doorbell size enclosure. All of this is powered by the existing 20VAC doorbell transformer and wiring from the old doorbell. The existing doorbell had 2 sets of twisted pair wiring; one pair going back to the transformer and one pair to the doorbell at the front door. Any other wiring arrangement would have required an entirely different design.

The operating system used is Raspbian Wheezy (the version available in 2012/2013 when I built the project). I added the optional java development and runtime package that was available at the time, as well as Pi4J hardware interface implemented by Robert Savage at http://www.pi4j.com , which also adds WiringPi at http://wiringpi.com  by Gordon Henderson.

I purchased the Raspberry Pi and Pi-specific components from Element14 (mail order). All of the electronic parts were obtained from my local electronics parts dealers in the Houston area. The speakers were from a small stereo enclosure purchased at Half Price Books (the enclosure discarded).

Doorbell Schematic

Figure 1. Schematic/block diagram of doorbell

When the doorbell is pressed, 5V is split to about 1.5 volts that hits the input pin of the Raspberry Pi. The Pi recognizes the “pin raised” condition and runs a bash script which plays a doorbell audio file on the speakers. Each doorbell event is logged to a local logfile. The logfile records program start and doorbell ring events. The main code is written in java, with the sound file driven by the bash script to allow rapid changes.

Doris would work fine (as a doorbell) off the network, but I added a wifi dongle so that it would work online, and would be able to figure out the correct time etc. Doing this makes sure that the logfile timestamps are correct, and allows things like backup, updating the operating system, changing the program etc. I also used a direct wired ethernet cable (“the blue cable”) when writing the code and debugging. If you use a “recent” Raspberry Pi, you won’t need to do this hardware addition; there is builtin wifi functionality on almost all models.

The audio transformer was added to avoid a problem with a ground loop that was hard to get around, resulting in “popcorn” sound through the speakers. In my implementation, the speakers are mounted horizontally opposed in a small enclosure, so they were wired out of phase to each other.

Doorbell enclosure

Figure 2. Inside view of the doorbell hardware

The main logic of the software that runs this was written in java. I used the builtin but optional java package available with Raspbian Wheezy. I considered the signal detect and logfile logic to be somewhat stable, but the doorbell ring code to be somewhat dynamic. I wrote the signalling and logfile code in java, with calls to a bash script for the ring events.

There are some interesting sound files available at

http://www.sounddogs.com/sound-effects/25/mp3/

and

http://www.trekcore.com/audio/toscomputer/voice/tos_working.mp3

 

Source code


RunDoorbell.sh

#!/bin/bash
# $CVSHeader: HBCode/DoorBell/src/RunDoorbell.sh,v 1.1 2019/06/22 22:16:24 pi Exp $
# $Log: RunDoorbell.sh,v $
# Revision 1.1 2019/06/22 22:16:24 pi
# Use new amixer syntax
#
#
# Hopefully this will run the doorbell code at system startup
#
# Modification History
# 2013/01/23 HAB
# Initial
# 2019/06/22 HAB
# Add to source control, update run syntax, move logfile

_now=$(date +"%Y_%m_%d")
_file="/opt/doorbell/log/log_$_now.log"
echo "Starting Doorbell, logging to $_file..."

cd /opt/doorbell
sudo java -classpath .:classes:/opt/pi4j/lib/'*' DoorBell &>> "$_file" &

exit 0

ClockEvent.sh

#!/bin/sh
#
# $CVSHeader: HBCode/DoorBell/src/ClockEvent.sh,v 1.5 2019/06/29 17:51:24 pi Exp $
# $Log: ClockEvent.sh,v $
# Revision 1.5  2019/06/29 17:51:24  pi
# Neuter clock sounds while we use doris as sound server
#
# Revision 1.4  2019/06/24 17:00:19  pi
# Change clock event to duck quack, doorbell to ding
#
# Revision 1.3  2019/06/22 22:16:22  pi
# Use new amixer syntax
#
# Revision 1.2  2013/03/19 19:53:16  harvey
# Add/enable clock functionality
#
# Revision 1.1  2013/03/17 03:33:27  harvey
# Add Clock Event handler
#
#
# This file is executed by DoorBell code every 15 minutes
#

#amixer sset Master 100% # old syntax
amixer sset PCM 100%
#aplay /opt/doorbell/doorbell-3.wav
#aplay /opt/doorbell/Dog1.wav

#echo param 0 is $0
#echo param 1 is $1
#echo param 2 is $2
#echo param 3 is $3

case "$2" in
0|15|30|45)
# aplay /opt/doorbell/doorbell-3.wav
# aplay /opt/doorbell/Duck.wav
# mpg321 /opt/doorbell/330111_SOUNDDOGS__cl.mp3
# mpg321 /opt/doorbell/330112_SOUNDDOGS__cl.mp3
# mpg321 /opt/doorbell/330113_SOUNDDOGS__cl.mp3
# mpg321 /opt/doorbell/330114_SOUNDDOGS__cl.mp3
;;
"")
aplay /opt/doorbell/Duck.wav
;;
esac
#

ding.sh

#!/bin/sh
#
# $CVSHeader: HBCode/DoorBell/src/ding.sh,v 1.5 2019/06/24 17:00:19 pi Exp $
# $Log: ding.sh,v $
# Revision 1.5  2019/06/24 17:00:19  pi
# Change clock event to duck quack, doorbell to ding
#
# Revision 1.4  2019/06/24 15:48:32  pi
# Change 'ding ding' sound to Duck Quack
#
# Revision 1.3  2019/06/22 22:16:23  pi
# Use new amixer syntax
#
# Revision 1.2  2013/02/19 17:00:17  harvey
# Add initial commands
#
# Revision 1.1  2013/01/28 05:19:36  harvey
# Add shell files for doorbell ding/dong actions
#
#
# This file will be executed when the doorbell is pressed.
#

#amixer sset Master 100%  # old syntax
amixer sset PCM 100%
aplay /opt/doorbell/doorbell-1.wav

dong.sh

#!/bin/sh
#
# $CVSHeader: HBCode/DoorBell/src/dong.sh,v 1.3 2013/03/17 03:33:10 harvey Exp $
# $Log: dong.sh,v $
# Revision 1.3  2013/03/17 03:33:10  harvey
# Leave volume at 100%
#
# Revision 1.2  2013/02/19 17:00:17  harvey
# Add initial commands
#
# Revision 1.1  2013/01/28 05:19:36  harvey
# Add shell files for doorbell ding/dong actions
#
#
# This file will be executed when the doorbell is released.
#

aplay /opt/doorbell/dog_bark4.wav
aplay /opt/doorbell/dog_bark5.wav
# amixer sset Master 44%

makefile

# Makefile for Doorbell
#
# $CVSHeader: HBCode/DoorBell/src/makefile,v 1.5 2019/06/24 15:47:31 pi Exp $
# $Log: makefile,v $
# Revision 1.5  2019/06/24 15:47:31  pi
# remove *.wma copy since there are none present
#
# Revision 1.4  2019/06/22 22:15:49  pi
# Add sound files
#
# Revision 1.3  2013/03/19 20:01:23  harvey
# Add missing tabs
#
# Revision 1.2  2013/03/19 19:53:16  harvey
# Add/enable clock functionality
#
#

all: DoorBell.class

DoorBell.class: DoorBell.java
	javac -classpath .:classes:/opt/pi4j/lib/'*' -d . $<

#%.class: %.java
#	javac -classpath .:classes:/opt/pi4j/lib/'*' -d . $<

RCTarg: DoorBell.class, DoorBell$1.class
	sudo cp *.class /opt/doorbell
	sudo cp *.sh  /opt/doorbell

CompileAndGo: DoorBell.class
	sudo java -classpath .:classes:/opt/pi4j/lib/'*' DoorBell

clean:
	- @echo "Cleaning up targets"
	- rm *.class

distrib: DoorBell.class
	- sudo mkdir -pv /opt/doorbell/log
#	- sudo rm /opt/doorbell/*.class
	- sudo cp -uvp *.class /opt/doorbell
	- sudo cp -uvp *.sh /opt/doorbell
	- sudo cp -uvp *.mp3 /opt/doorbell
	- sudo cp -uvp *.wav /opt/doorbell
#	- sudo cp -uvp *.wma /opt/doorbell


DoorBell.java

//: $CVSHeader: HBCode/DoorBell/src/DoorBell.java,v 1.7 2019/06/22 22:17:23 pi Exp $
//: $Log: DoorBell.java,v $
//: Revision 1.7  2019/06/22 22:17:23  pi
//: Use new amixer syntax, expand directory spec for stretch
//:
//: Revision 1.6  2013/03/19 19:53:16  harvey
//: Add/enable clock functionality
//:
//: Revision 1.5  2013/03/17 03:32:51  harvey
//: Fix file reference (remove dir)
//:
//: Revision 1.4  2013/02/19 17:00:33  harvey
//: Add support for external shell files
//:
//: Revision 1.3  2013/02/19 05:37:30  harvey
//: Add initial code
//:
//: Revision 1.2  2013/01/12 04:20:57  harvey
//: Initial edit with geany
//:
/*
 * DoorBell.java
 *
 * Copyright 2013 HB 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalInput;
import com.pi4j.io.gpio.PinPullResistance;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;

//Compile:
// javac -classpath .:classes:/opt/pi4j/lib/'*' -d . DoorBell.java
// Run:
// sudo java -classpath .:classes:/opt/pi4j/lib/'*' DoorBell

class DoorBell
{
    public DoorBell()
    {}

    protected void LogToStdOut(String str)
    {
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss ");
        Date date = new Date();
        System.out.print(dateFormat.format(date));
        System.out.println(str);
    }

    protected Process DoSysCommand(String sCmd, boolean bWait)
    {
        Runtime r = Runtime.getRuntime();
        Process retP = null;
        try
        {
            retP = r.exec(sCmd);
            if(bWait)
            {
                retP.waitFor();
            }
        }
        catch(IOException e)
        {
            System.err.println(e.getMessage());
        }
        catch(InterruptedException e)
        {
            System.err.println(e.getMessage());
        }
        return retP;
    }

    protected void Init()
    {
        LogToStdOut("DoorBell Initializing");
        //DoSysCommand("amixer sset Master 100%", true);  // syntax for wheezy version of amixer
        //DoSysCommand("amixer sset PCM 100%", true);     // syntax for stretch version of amixer
        DoSysCommand("mpg321 --gain 100 /opt/doorbell/tos_working.mp3", true);

        // create and register gpio pin listener
        myButton.addListener(new GpioPinListenerDigital()
        {
            @Override
            public void handleGpioPinDigitalStateChangeEvent(
                    GpioPinDigitalStateChangeEvent event)
            {
                // display pin state on console
                // System.out.println(" GPIO pin " + event.getPin() +
                // " change: " + event.getState());
                if(event.getState().isHigh())
                {
                    ding();
                    LogToStdOut("Doorbell button pushed!");
                }
                else
                {
                    dong();
                }
            }
        });
    }

    protected void ding()
    {
        try
        {
            DoSysCommand("/opt/doorbell/ding.sh", true);
        }
        catch(Exception e)
        {
            System.err.println(e.getMessage());
        }
    }

    protected void dong()
    {
        try
        {
            DoSysCommand("/opt/doorbell/dong.sh", true);
        }
        catch(Exception e)
        {
            System.err.println(e.getMessage());
        }
    }

    protected void processClockEvent(int hour, int minute)
    {
        String str = "/opt/doorbell/ClockEvent.sh " + Integer.toString(hour) + " "
                + Integer.toString(minute);

        DoSysCommand(str, true);
    }

    // This API never terminates, except in case of error
    protected void RunClockLoop()
    {
        boolean bDoClockEvent = true;
        try
        {
            while(true)
            {
                Calendar c = Calendar.getInstance();
                int hour = c.get(Calendar.HOUR);
                int minute = c.get(Calendar.MINUTE);
                switch(minute)
                {
                case 0:
                case 15:
                case 30:
                case 45:
                    {
                        if(bDoClockEvent)
                        {
                            processClockEvent(hour, minute);
                            bDoClockEvent = false;
                        }
                        break;
                    }
                default:
                    {
                        bDoClockEvent = true;
                        break;
                    }
                }

                Thread.sleep(5000); // process every 5 seconds
                // System.out.print(".");
            }
        }
        catch(InterruptedException e)
        {
            System.err.println(e.getMessage());
            // TODO: Send notification msg...
            System.exit(0);
        }
    }

    public static void main(String[] args)
    {
        DoorBell db = new DoorBell();
        db.Init();

        System.out.println(" ... waiting for activity");

        // try
        // {
        // while(true)
        // {
        // Thread.sleep(10000); // wake up every 10 seconds, go back to
        // // sleep...
        // // System.out.print(";");
        // }
        // }
        // catch(InterruptedException e)
        // {
        // System.err.println(e.getMessage());
        // }

        db.RunClockLoop();
    }

    private static final GpioController      gpio     = GpioFactory
                                                              .getInstance();

    // provision gpio pin #02 as an input pin with its internal pull down
    // resistor enabled
    private static final GpioPinDigitalInput myButton = gpio
                                                              .provisionDigitalInputPin(
                                                                      RaspiPin.GPIO_02,
                                                                      PinPullResistance.PULL_DOWN);
    // private static Process currentProcess = null;
}

Ah it was a fun project…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s