#! /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; }