Raspberry Pi - Moduł Zilog ePIR Alarm

Autor: Marcin Kasiński
15.02.2014 18:07:49 +0200

Moduł Zilog ePIR SEN-09587

W niniejszym artykule postaram się opisać wykorzystanie Raspberry Pi oraz czujnika ruchu Zilog ePIR do zbudowania alarmu. Czujnik, przedstawiony na zdjęciu obok, to ePIR SEN-09587 jeszcze niedawno możliwy był do kupienia w sklepie sparkfun. Obecnie wycofany on jest ze sklepu sparkfun, ale można go kupić na aukcjach internetowych. Do obsługi czujnika stworzyłem dedykowaną aplikację JAVA wykorzystującą bibliotekę Pi4J. Aplikacja reaguje na zdarzenia generowane przez czujnik w momencie detekcji ruchu, które to zdarzenia będą powodowały wysyłanie emaila na zadany adres. Jako , że zaobserwowałem czasami alarmy "nieprawdziwe" do aplikacji wprowadziłem poprawkę generującą powiadomienie w momencie pojawienia się kilku zdarzeń następujących po sobie.

W przedstawionym rozwiązaniu potrzebne nam będzie:

  • Moduł Raspberry Pi
  • Moduł Zilog ePIR SEN-09587
  • Biblioteka Pi4J

Podłączenie Raspberry Pi z ePIR

Fizyczne rozstawienie pinów Raspberry Pi zgodnie z opisem ze strony http://elinux.org/RPi_Low-level_peripherals przedstawia poniższe zdjęcie.

Sposób odszukania konkretnego pinu w urządzeniu przedstawia poniższa grafika.

Rozstawienie pinów Raspberry Pi (dla biblioteki Pi4j) wygląda inaczej. Trzeba o tym pamiętać pisząc aplikacje z wykorzystaniem biblioteki Pi4j. W bibliotece wykorzystujemy logiczne numery pinów nie odpowiadające ich fizycznemu rozstawieniu.

Poniżej przedstawione jest rozstawienie pinów modułu Zilog ePIR zgodnie ze specyfikacją.

PinNazwaOpis
1GND
2VDD
3RXD/DLY
4TXD/SNS
5MD/RTS
6LG
7SLP
8GND

Przedstawiony czujnik ePir posiada standard stanów logicznych: 3.3 V i powinien być zasilany napięciem z zakresie od 2.7 V do 3.6 V. Poniższa tabela przedstawia podłączenie Raspberry Pi z ePIR.

Pin ePIRpin Raspberry PiOpis
pin 1GND
pin 2Zasilanie 3.3V
pin 3Pin 18 (TX1)
pin 4Pin 19 (RX1) + 10k rezystor podciągający
pin 6Zasilanie 3.3V

Instalowanie i przygotowanie środowiska

Pierwszym krokiem jaki należy zrobić jest zainstalowanie obrazu wheezy-raspbian na Raspberry Pi. http://www.raspberrypi.org/downloads . Po zainstalowaniu obrazu na karcie, podłączeniu urządzenia kablem sieciowym do sieci i uruchomieniu Raspbery PI możemy już zalogować się na serwer. Po zalogowaniu wskazane byłoby dokonanie aktualizacji systemu operacyjnego. Dokonujemy tego poprzez polecenia:

sudo apt-get -y update
sudo apt-get -y upgrade

Konfiguracja komunikacji Serial

Czysty system operacyjny linux oparty na wheezy-raspbian wymaga jeszcze skonfigurowania komunikacji szeregowej. W tym celu po zalogowaniu się na konto root należy wyedytować plik /boot/cmdline.txt . W nim należy zamienić linię

dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

na

dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait

Po tej modyfikacji trzeba zrobić restart maszyny. Po restarcie i po zalogowaniu się na konto root należy wyedytować plik /etc/inittab W nim należy zamienić linię

T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100

na

#T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100

Po powyższych operacjach i po restarcie Raspberry Pi mamy skonfigurowaną komunikację szeregową.

Konfiguracja Wifi

W przypadku kiedy chcemy w Raspberry Pi korzystać z wifi po zakupieniu modułu wifi i podłączeniu poprzez USB należy w systemie skonfigurować wifi. W tym celu należy wyedytować plik /etc/wpa_supplicant/wpa_supplicant.conf Plik ten powinien mieć postać jak poniżej (w moim przypadku dla komunikacji WPA2)


ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="SSID sieci WIFI do której chcemy się łączyć"
    proto=RSN
    key_mgmt=WPA-PSK
    pairwise=CCMP TKIP
    group=CCMP TKIP
    psk="hasło do sieci"
}

Oczywiście w nim należy odpowiednio ustawić parametr ssid oraz psk. Po zapisie tego pliku należy tylko zrobić restart sieci

sudo /etc/init.d/networking restart

Aplikacja - development

W tym momencie możemy przejść do pisania aplikacji JAVA realizującej kluczową funkcjonalność. Aplikacją będzie nasłuchiwała na zdarzenia czujnika i po nadejściu zdarzenia stwierdzającego ruch (kilku takich zdarzeń następujących po sobie) Pierwszy plik to Epir.java . To główny program zajmujący się komunikacją z czujnikiem.


package mk;

import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.management.ManagementFactory;
import java.security.acl.LastOwnerException;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;

import javax.management.MXBean;

import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinDigitalOutput;
import com.pi4j.io.gpio.PinState;
import com.pi4j.io.gpio.RaspiPin;
import com.pi4j.io.serial.Serial;
import com.pi4j.io.serial.SerialDataEvent;
import com.pi4j.io.serial.SerialDataListener;
import com.pi4j.io.serial.SerialFactory;
import com.pi4j.io.serial.SerialPortException;

import org.apache.log4j.Logger;

public class Epir {

	Properties prop = new Properties();

	static boolean debug = false;

	Logger logger;

	// ile opoźnienia przystarcie
	long start_delay_in_seconds = 60;
	// co ile godzin ma pinga wysyłać
	long ping_interval = 4;
	// co ile minut ma alarm wysyłąć
	long alarm_interval = 10;
	// ilosc wystapien jeden za drugim alarmu po jakim wysylany jest email
	int max_events_count = 3;

	long lastping = 0;
	long lastalarm = 0;

	int current_events_count = 0;

	String email_SMTP_AUTH_USER = null;
	String email_SMTP_AUTH_PWD = null;
	String email_host = null;
	String email_port = null;
	String email_from = null;
	String email_to = null;
	String email_start_message = null;
	String email_ping_message = null;
	String email_alert_message = null;

	public void init() {

		logger = Logger.getLogger("Epir");

		try {
			prop.load(new FileInputStream("config.properties"));

			start_delay_in_seconds = Long.parseLong(prop
					.getProperty("start_delay_in_seconds"));
			ping_interval = Long.parseLong(prop.getProperty("ping_interval"));
			alarm_interval = Long.parseLong(prop.getProperty("alarm_interval"));
			max_events_count = Integer.parseInt(prop
					.getProperty("max_events_count"));

			email_SMTP_AUTH_USER = prop.getProperty("email_SMTP_AUTH_USER");
			email_SMTP_AUTH_PWD = prop.getProperty("email_SMTP_AUTH_PWD");
			email_host = prop.getProperty("email_host");
			email_port = prop.getProperty("email_port");
			email_from = prop.getProperty("email_from");
			email_to = prop.getProperty("email_to");
			email_start_message = prop.getProperty("email_start_message");
			email_ping_message = prop.getProperty("email_ping_message");
			email_alert_message = prop.getProperty("email_alert_message");

			logger.info("Epir Configuration...");

			logger.info("start_delay_in_seconds=" + start_delay_in_seconds);
			logger.info("ping_interval=" + ping_interval);
			logger.info("alarm_interval=" + alarm_interval);
			logger.info("max_events_count=" + max_events_count);

			logger.info("email_SMTP_AUTH_USER=" + email_SMTP_AUTH_USER);
			logger.info("email_host=" + email_host);
			logger.info("email_port=" + email_port);
			logger.info("email_from=" + email_from);

			logger.info("email_start_message=" + email_start_message);
			logger.info("email_ping_message=" + email_ping_message);
			logger.info("email_alert_message=" + email_alert_message);

		} catch (Exception e1) {
			logger.error("error " + e1.getMessage());
		}

		SendSimpleEmail.init(logger, email_SMTP_AUTH_USER, email_SMTP_AUTH_PWD,
				email_host, email_port, email_from, email_to);

		Calendar mCalendar = Calendar.getInstance();

		mCalendar.set(Calendar.MINUTE, 0);
		mCalendar.set(Calendar.SECOND, 0);
		mCalendar.set(Calendar.MILLISECOND, 0);

		lastping = mCalendar.getTimeInMillis();
		lastalarm = 0;

		logger.info("Epir starting in " + start_delay_in_seconds + " seconds");

		try {
			if (debug != true)
				Thread.sleep(start_delay_in_seconds * 1000);
		} catch (InterruptedException e) {

			e.printStackTrace();
		}
		logger.info("Epir started");

		logger.info("ping_interval " + ping_interval + ", alarm_interval "
				+ alarm_interval);

		try {
			if (debug != true)
				SendSimpleEmail.send(email_start_message);
		} catch (Exception e) {

			logger.error(e.getMessage());

		}

	}

	public void generateAlarm() {

		long current = Calendar.getInstance().getTimeInMillis();

		// ile minut od ostatniego pinga
		long last_alarm_interval = (current - lastalarm) / 1000 / 60;

		if (last_alarm_interval >= alarm_interval) {

			try {
				logger.info("Epir Alarm");

				SendSimpleEmail.send(email_alert_message);
			} catch (Exception e) {

				logger.error(e.getMessage());

			}
			lastalarm = current;
		}

	}

	public void generatePing() {

		Calendar mCalendar = Calendar.getInstance();

		long current = mCalendar.getTimeInMillis();

		// ile godzin od ostatniego pinga
		long last_ping_interval = (current - lastping) / 1000 / 60 / 60;

		if (last_ping_interval >= ping_interval) {
			logger.info("last_ping_interval " + last_ping_interval
					+ " ping_interval " + ping_interval + " current " + current
					+ " lastping " + lastping + " mCalendar "
					+ mCalendar.get(Calendar.HOUR_OF_DAY) + " "
					+ mCalendar.get(Calendar.MINUTE) + " "
					+ mCalendar.get(Calendar.SECOND));

			logger.info("Epir Ping");

			try {
				if (debug != true)
					SendSimpleEmail.send(email_ping_message);
			} catch (Exception e) {

				logger.error(e.getMessage());

			}

			mCalendar.set(Calendar.MINUTE, 0);
			mCalendar.set(Calendar.SECOND, 0);
			mCalendar.set(Calendar.MILLISECOND, 0);
			current = mCalendar.getTimeInMillis();

			lastping = current;
		}

	}

	public void start() throws Exception {

		logger.info("START alarm");

		// create an instance of the serial communications class
		final Serial serial = SerialFactory.createInstance();

		try {
			// open the default serial port provided on the GPIO header
			serial.open(Serial.DEFAULT_COM_PORT, 9600);
			logger.info("serial opened");

			for (;;) {

				generatePing();

				try {

					String STATUS_CMD = "a";

					serial.write(STATUS_CMD);

					char redchar = serial.read();

					if (redchar == 'Y') {

						current_events_count++;

						logger.info("Epir activated " + current_events_count
								+ "/" + max_events_count);

						try {

							if (current_events_count == max_events_count) {
								generateAlarm();
								current_events_count = 0;
							}
						} catch (Exception e) {

							logger.error(e.getMessage());

						}

					}// if
					else {
						current_events_count = 0;
					}

				} catch (IllegalStateException ex) {
					ex.printStackTrace();
				}

				Thread.sleep(1000);
			}

		} catch (SerialPortException ex) {
			System.out
					.println(" ==>> SERIAL SETUP FAILED : " + ex.getMessage());
			return;
		}

	}

	public static void main(String[] args) throws Exception {

		Epir epir = new Epir();
		epir.init();

		epir.start();

	}
}


Druga i ostatnia klasą jest SendSimpleEmail.java , klasa używana do wysyłania emaili.


package mk;

import java.util.*;

import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
import javax.mail.Authenticator;
import javax.mail.PasswordAuthentication;

import org.apache.log4j.Logger;

public class SendSimpleEmail {

	static Logger logger;


	static String email_SMTP_AUTH_USER ;
	static String email_SMTP_AUTH_PWD ;
	static String email_host;
	static String email_port;
	static String email_from;
	static String email_to;

	
	public static void init(
			Logger in_logger,
			String in_SMTP_AUTH_USER,
			String in_SMTP_AUTH_PWD,
			String in_host,
			String in_port,
			String in_from,
			String in_to
) 
	{
		logger=in_logger;
		email_SMTP_AUTH_USER=in_SMTP_AUTH_USER;
		email_SMTP_AUTH_PWD=in_SMTP_AUTH_PWD;
		email_host=in_host;
		email_port=in_port;
		email_from=in_from;
		email_to=in_to;
		
		
	}
	
	public static void send(String textMessage) throws Exception {

		Properties properties = System.getProperties();


		properties.setProperty("mail.smtp.host", email_host);
		properties.setProperty("mail.smtp.auth", "true");
		properties.setProperty("mail.smtp.sendpartial", "true");
		properties.setProperty("mail.smtp.starttls.enable", "true");

		properties.setProperty("mail.smtp.port", email_port);

		Authenticator auth = new Authenticator();
		Session session = Session.getDefaultInstance(properties, auth);

		try {

			MimeMessage message = new MimeMessage(session);


			message.setFrom(new InternetAddress(email_from));


			message.addRecipient(Message.RecipientType.TO, new InternetAddress(
					email_to));

			message.setSubject(textMessage);


			message.setText(textMessage);


			Transport.send(message);

		} catch (MessagingException mex) {

			logger.error(mex.getMessage());
		}
	}

	public static class Authenticator extends javax.mail.Authenticator {
		private PasswordAuthentication authentication;

		public Authenticator() {
			String username = email_SMTP_AUTH_USER;
			String password = email_SMTP_AUTH_PWD;
			authentication = new PasswordAuthentication(username, password);
		}

		protected PasswordAuthentication getPasswordAuthentication() {
			return authentication;
		}
	}
}

Aplikacja - wdrożenie

W pierwszej kolejności przygotowujemy odpowiednią strukturę katalogową po zalogowaniu się na konto pi.


mkdir /home/pi/apps/
mkdir /home/pi/apps/RaspberryTester1/
mkdir /home/pi/apps/RaspberryTester1/in/
mkdir /home/pi/apps/RaspberryTester1/bin/
mkdir /home/pi/apps/RaspberryTester1/lib/
mkdir /home/pi/apps/RaspberryTester1/log/

Do katalogu /home/pi/apps/RaspberryTester1/bin/ przegrywamy skompilowane klasy zgodnie ze strukturą katalogową.
Do katalogu /home/pi/apps/RaspberryTester1/lib/ przegrywamy potrzebne nam biblioteki jar , tj. pliki jar biblioteki pi4j, mail.jar oraz bibliotekę log4j np. log4j-1.2.16.jar
W aplikacji używamy logowania log4j, więc potrzebujemy pliku konfiguracyjnego. Plik ten to /home/pi/apps/RaspberryTester1/in/log4j.properties .


log4j.rootLogger=INFO, Epir

log4j.appender.Epir=org.apache.log4j.DailyRollingFileAppender
log4j.appender.Epir.File=/home/pi/apps/RaspberryTester1/log/Epir.log
log4j.appender.Epir.DatePattern='.'yyyy-MM-dd
log4j.appender.Epir.layout=org.apache.log4j.PatternLayout
log4j.appender.Epir.layout.ConversionPattern=%d{dd.MM.yyyy HH\:mm\:ss,SSS} [%t] %-5p %c %x - %m%n

Wszystkie parametry aplikacji są zapisywane w pliku konfiguracyjnym /home/pi/apps/RaspberryTester1/in/config.properties .


// czas opoznienia przy starcie
start_delay_in_seconds=30
// co ile godzin ma pinga wysylac
ping_interval = 4
// co ile minut ma alarm wysylac
alarm_interval = 10
// ilosc wystapien jeden za drugim alarmu po jakim wysylany jest email
max_events_count = 3

email_SMTP_AUTH_USER=konto_z_ktorego_beda_wysylane_emaile@gmail.com
email_SMTP_AUTH_PWD=haslo_do_konta_gmail
email_host=smtp.gmail.com
email_port=587
email_from=konto_z_ktorego_beda_wysylane_emaile@gmail.com
email_to=konto_na_ktore_beda_wysylane_emaile@gmail.com

email_start_message=Alarm wystartowany
email_ping_message=Alarm PING
email_alert_message=Ktos sie kreci po domu

Do wystartowania aplikacji przygotowany jest plik /home/pi/apps/RaspberryTester1/in/startclient.sh .


#!/bin/sh


cd /home/pi/apps/RaspberryTester1/in/

#export CP=../lib/pi4j-core.jar
export CP=../lib/*

# plik pid . Standardowe położenie
pidFile="/var/run/alarmservice.pid"

echo "$# arguments were supplied."

echo "pidFile $pidFile"

# skrypt oczekuje 1 parametry 
# czy jest parametr z plikiem dla pidu. Jesli jest podmieniana jest zmienna pidFile
if [ -n "$1" ]
then
    pidFile=$1
fi

echo "pidFile $pidFile"

nohup java -cp ../bin:$CP mk.Epir > log.txt 2> errors.txt < /dev/null &
PID=$!
echo $PID > $pidFile

Następną czynnością jest utworzenie serwisu pozwalającego na uruchomieniu naszej aplikacji przy każdym starcie serwera. W tym celu logujemy się na konto root i kopiujemy poniższą zawartość do pliku /etc/init.d/alarmservice .


#! /bin/sh
### BEGIN INIT INFO
# Provides:          skeleton
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Example initscript
# Description:       This file should be used to construct scripts to be
#                    placed in /etc/init.d.
### END INIT INFO

# Author: Foo Bar <foobar@baz.org>
#
# Please remove the "Author" lines above and replace them
# with your own name if you copy and modify this script.

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Description of the service"
NAME=alarmservice
DAEMON=/home/pi/apps/RaspberryTester1/in/startclient.sh
DAEMON_ARGS="--options args"
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
	# Return
	#   0 if daemon has been started
	#   1 if daemon was already running
	#   2 if daemon could not be started
	
	#start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
	#	|| return 1
	#start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
	#	$DAEMON_ARGS \
	#	|| return 2
	
	$DAEMON
	
	# Add code here, if necessary, that waits for the process to be ready
	# to handle requests from services started subsequently which depend
	# on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
	# Return
	#   0 if daemon has been stopped
	#   1 if daemon was already stopped
	#   2 if daemon could not be stopped
	#   other if a failure occurred
	
	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
	RETVAL="$?"
	
	[ "$RETVAL" = 2 ] && return 2
	# Wait for children to finish too if this is a daemon that forks
	# and if the daemon is only ever run from this initscript.
	# If the above conditions are not satisfied then add some other code
	# that waits for the process to drop all resources that could be
	# needed by services started subsequently.  A last resort is to
	# sleep for some time.
	start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
	[ "$?" = 2 ] && return 2
	# Many daemons don't delete their pidfiles when they exit.

	kill -9 $(cat $PIDFILE)
	rm -f $PIDFILE
	return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
	#
	# If the daemon can reload its configuration without
	# restarting (for example, when it is sent a SIGHUP),
	# then implement that here.
	#
	start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
	return 0
}

case "$1" in
  start)
	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
	do_start
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  stop)
	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
	do_stop
	case "$?" in
		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
	esac
	;;
  status)
	status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
	;;
  #reload|force-reload)
	#
	# If do_reload() is not implemented then leave this commented out
	# and leave 'force-reload' as an alias for 'restart'.
	#
	#log_daemon_msg "Reloading $DESC" "$NAME"
	#do_reload
	#log_end_msg $?
	#;;
  restart|force-reload)
	#
	# If the "reload" option is implemented then remove the
	# 'force-reload' alias
	#
	log_daemon_msg "Restarting $DESC" "$NAME"
	do_stop
	case "$?" in
	  0|1)
		do_start
		case "$?" in
			0) log_end_msg 0 ;;
			1) log_end_msg 1 ;; # Old process is still running
			*) log_end_msg 1 ;; # Failed to start
		esac
		;;
	  *)
		# Failed to stop
		log_end_msg 1
		;;
	esac
	;;
  *)
	#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
	echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
	exit 3
	;;
esac

:

Następnie wykonujemy poniższe czynności:

chmod a+x /etc/init.d/alarmservice 
chmod a+x /home/pi/apps/RaspberryTester1/in/startclient.sh

Ostatnim elementem jest włączenie serwisu.

update-rc.d alarmservice defaults
update-rc.d alarmservice enable

Po wszystkich powyższych zmianach możemy zrestartować Raspberry Pi. Po restarcie aplikacja powinna uruchamiać się automatycznie.


powrót
Zachęcam do przedstawienia swoich uwag i opinii w polu komentarzy.

Komentarze

Dodaj Komentarz