#! /usr/bin/perl use strict; #use warnings; # Had to turn off warnings as Device::SerialPort threw warnings after closing and re-using see re_initialise_serial_port use Getopt::Std; use Sys::Syslog qw(:standard :macros); use Device::SerialPort; use Proc::Daemon; Proc::Daemon::Init; #TODO add response checking to write commands #TODO turn into service running on boot after zoneminder, maybe from zoneminder init.d script # boolean type variables my $true = 1; my $false = 0; my $serviceRunning = $false; my $switchStateOn = $false; # Service to control - Change these to zoneminder specific strings my $serviceName = "cups"; my $serviceStatusCommand = "status"; my $serviceStopCommand = "stop"; my $serviceStartCommand = "start"; # service command response strings my $serviceStoppedResponse = "cupsd is not running"; my $serviceRunningResponse = "cupsd is running"; # commands to control switch box my $ledGreenOn = "ledgrnon"; my $ledGreenOff = "ledgrnoff"; my $ledRedOn = "ledredon"; my $ledRedOff = "ledredoff"; my $ledPending = "ledpending"; my $switchStatus = "swstatus"; # command response my $respGreenOn = "greenon"; my $respGreenOff = "greenoff"; my $respRedOn = "redon"; my $respRedOff = "redoff"; my $respSwitchOn = "on"; my $respSwitchOff = "off"; # Set up the serial port for arduino serial communication # 9600, 81N on the USB ftdi driver my $portPath = "/dev/ttyUSB0"; my $baudRate = 9600; my $dataBits = 8; my $parity = "none"; my $stopBits = 1; my $waitConnect = 5; # How long to wait to retry connecting to serial port # check for option -d to determin level of logging my %options=(); getopts("d", \%options); # set up logging to syslog my $identity = "ZoneSwitchControl"; openlog ($identity, "ndelay,pid", LOG_DAEMON); if ($options{d}){ #if program called with -d for debugging, allow debug info to go to log setlogmask( LOG_MASK(LOG_DEBUG) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_INFO) ); }else{ setlogmask( LOG_MASK(LOG_ERR) | LOG_MASK(LOG_INFO) ); } syslog(LOG_INFO, "Starting ZoneSwitch to control $serviceName service"); my $port = initialise_serial_port(); #TODO on first boot as part of service init, wait for zoneminder to start normally? my $continue = 1; $SIG{TERM} = sub { $continue = 0 }; while ($continue) { #Check to see if service running check_service (); #turn on led's to show state maintain_led_status ($port); #check switch position on or off check_switch ($port); #act on switch state mantain_service(); } sub mantain_service{ my $message = ""; if (($switchStateOn == $true) && ($serviceRunning == $false)){ #start service my $return = start_service(); $message = "Starting service - $serviceName service "; $message = $message . "returning: $return"; } if (($switchStateOn == $false) && ($serviceRunning == $true)){ #stop service my $return = stop_service(); $message = "Stopping service - $serviceName service "; $message = $message . "returning: $return"; } $message eq "" || syslog(LOG_INFO, $message); } sub led_pending { my $port = shift; send_command($port, $ledPending ); return (); } sub red_on { my $port = shift; send_command($port, $ledRedOn ); send_command($port, $ledGreenOff ); return (); } sub green_on { my $port = shift; send_command($port, $ledGreenOn ); send_command($port, $ledRedOff ); return (); } sub send_command{ my $port = shift; my $command = shift; clear_input($port); $port->write($command); $port->write("\n"); #not handling response yet #read and return response return(); } sub check_service { #check service state using back ticks to run command in shell my $return = `service $serviceName $serviceStatusCommand`; my $message = ""; if ( $return =~ m/$serviceRunningResponse/ ) { $serviceRunning = $true; $message = "Service $serviceName is running"; } elsif ( $return =~ m/$serviceStoppedResponse/ ) { $serviceRunning = $false; $message = "Service $serviceName is not running\n"; } ($message eq "") || syslog(LOG_DEBUG, $message); } sub maintain_led_status { my $port = shift; if ( $serviceRunning == $false ) { red_on($port); #clear any response for now } else { green_on($port); #clear any response for now } return (); } sub clear_input{ #by reading anything that may be waiting my $port = shift; my $STALL_DEFAULT = 1; # how many seconds to wait for new input my $timeout = $STALL_DEFAULT; $port->read_char_time(0); # don't wait for each character $port->read_const_time(1000); # 1 second per unfulfilled "read" call eval { my $chars = 0; my $buffer = ""; while ( $timeout > 0 ) { my ( $count, $saw ) = $port->read(255) # will read _up to_ 255 chars or die "Cant connect to serial port"; if ( $count > 0 ) { $chars += $count; $buffer .= $saw; } else { $timeout--; } } }; if ($@) { syslog(LOG_ERR, "Cant get response from ZoneSwitch device "); re_initialise_serial_port(); # Then try reconnecting port to device } } sub check_switch { my $port = shift; my $message = ""; my $STALL_DEFAULT = 1; # how many seconds to wait for new input my $timeout = $STALL_DEFAULT; $port->read_char_time(0); # don't wait for each character $port->read_const_time(1000); # 1 second per unfulfilled "read" call send_command($port, $switchStatus ); # send command to request switch status eval { my $chars = 0; my $buffer = ""; while ( $timeout > 0 ) { my ( $count, $saw ) = $port->read(255) # will read _up to_ 255 chars or die "Cant connect to serial port"; # if the port has been disconnected or we cant read the port if ( $count > 0 ) { $chars += $count; $buffer .= $saw; # Check here to see if what we want is in the $buffer # say "last" if we find it if ( $buffer =~ m/\n/ ) { #print "Switch status returned \"$buffer\"\n"; $timeout = 1; } } else { $timeout--; } } if ( $buffer =~ m/on/ ) { $message = "Switch found to be ON"; $switchStateOn = $true; } else { $message = "Switch found to be OFF"; $switchStateOn = $false; } $message eq "" || syslog(LOG_DEBUG, $message); if ( $timeout == 0 ) { # havnt decided about this yet, it was in the example code #die "Waited $STALL_DEFAULT seconds and never saw what I wanted\n"; } }; if ($@) { syslog(LOG_ERR, "Cant get response from ZoneSwitch device "); re_initialise_serial_port(); # Then try reconnecting port to device } } sub re_initialise_serial_port{ my $return = start_service(); # Assume someone maybe fiddling with switchbox and start service $switchStateOn = $true; syslog(LOG_ERR, "Started service $serviceName in case this is deliberate damage"); # Getting errors from SerialPort.pm when disconnecting and connecting usb lead from switch box # close and undef then re-connecting serial port threw # Use of uninitialized value in subroutine entry at /usr/lib/perl5/Device/SerialPort.pm line 1732. # # couldnt cleanly destoy original serial connection had to turn off warnings at head of script $port->close; undef $port; syslog(LOG_ERR, "Trying to re-initialise connection"); $port = initialise_serial_port(); } sub initialise_serial_port { my $port; while (){ $port = Device::SerialPort->new($portPath); last if ($port); syslog(LOG_ERR, "Serial port not ready waiting $waitConnect seconds to try reconnect"); sleep($waitConnect); } syslog(LOG_DEBUG, "Serial port connected"); sleep(2); # allow microcontroller in switch box to initialise $port->databits($dataBits); $port->baudrate($baudRate); $port->parity($parity); $port->stopbits($stopBits); return $port; } sub start_service { my $return = `service $serviceName $serviceStartCommand`; return $return; } sub stop_service { my $return = `service $serviceName $serviceStopCommand`; return $return; } #### #! /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: Daemon to monitor switch box connected on usb port and control zoneminder # Description: # ### END INIT INFO # Author: Gordon Endersby : gordon.endersby@blueyonder.co.uk # # 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="Daemon to monitor switch box connected on usb port and control zoneminder" NAME=zoneswitch DAEMON=/usr/sbin/$NAME 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.0-6) to ensure that this file is present. . /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 # 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. 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 : #### /* ZoneMinder switch control box */ #define LED_GRN_ON "ledgrnon" #define LED_GRN_OFF "ledgrnoff" #define LED_RED_ON "ledredon" #define LED_RED_OFF "ledredoff" #define LED_PENDING "ledpending" #define SWITCH_STATUS "swstatus" #define SWITCH_PIN 2 #define GREEN_LED_PIN 3 #define RED_LED_PIN 4 String inputString = ""; boolean stringComplete = false; boolean greenLedOn = false; boolean redLedOn = false; boolean ledPendingState = true; // true so led's flash on boot of device long previousMillis = 0; // for flashing pending state long interval = 1000; // interval at which to blink (milliseconds) boolean fledState = false; void setup() { // initialize serial: Serial.begin(9600); // reserve 20 bytes for the inputString: inputString.reserve(20); pinMode(GREEN_LED_PIN, OUTPUT); pinMode(RED_LED_PIN, OUTPUT); pinMode(SWITCH_PIN, INPUT); } void loop() { if (stringComplete) { ledControlEvent(); inputString = ""; stringComplete = false; } if(ledPendingState == true){ // maintain led pending flashing state unsigned long currentMillis = millis(); if(currentMillis - previousMillis > interval) { // save the last time you blinked the LED previousMillis = currentMillis; // if the LED is off turn it on and vice-versa: if (fledState == LOW) fledState = HIGH; else fledState = LOW; // set the LED with the ledState of the variable: digitalWrite(GREEN_LED_PIN, fledState); digitalWrite(RED_LED_PIN, !fledState); } } } /* SerialEvent occurs whenever a new data comes in the hardware serial RX. This routine is run between each time loop() runs, so using delay inside loop can delay response. Multiple bytes of data may be available. */ void serialEvent() { while (Serial.available()) { // get the new byte: char inChar = (char)Serial.read(); // add it to the inputString: inputString += inChar; // if the incoming character is a newline, set a flag // so the main loop can do something about it: if (inChar == '\n') { stringComplete = true; } } } void ledControlEvent(){ //Serial.println(inputString); echo back string, dont want this to happen for perlscript if (inputString.indexOf(LED_PENDING)!=-1){ ledPendingState = true; Serial.println("ledpendingon"); } if (inputString.indexOf(LED_GRN_ON)!=-1){ digitalWrite(GREEN_LED_PIN, HIGH); if(ledPendingState == true){ ledPendingState = false; digitalWrite(RED_LED_PIN, LOW); } Serial.println("greenon"); } if (inputString.indexOf(LED_GRN_OFF)!=-1){ ledPendingState = false; digitalWrite(GREEN_LED_PIN, LOW); Serial.println("greenoff"); } if (inputString.indexOf(LED_RED_ON)!=-1){ digitalWrite(RED_LED_PIN, HIGH); if(ledPendingState == true){ ledPendingState = false; digitalWrite(GREEN_LED_PIN, LOW); } Serial.println("redon"); } if (inputString.indexOf(LED_RED_OFF)!=-1){ ledPendingState = false; digitalWrite(RED_LED_PIN, LOW); Serial.println("redoff"); } if (inputString.indexOf(SWITCH_STATUS)!=-1){ if(digitalRead(SWITCH_PIN) == HIGH){ Serial.println("on"); }else{ Serial.println("off"); } } }