#!/usr/bin/perl use Data::Dumper; use FindBin; # find the script's directory use lib $FindBin::Bin; # add that directory to the library path use xPL::common; ################################################################################ # constants # $vendor_id = 'dspc'; # from xplproject.org $device_id = 'switch'; # max 8 chars $class_id = 'control'; # max 8 chars $separator = '-' x 80; $indent = ' ' x 2; #------------------------------------------------------------------------------- # global variables # my %configuration; $configuration{'pins'} = [25, 28, 29]; $configuration{'command'} = '/usr/local/bin/gpio'; ################################################################################ # Input arguments # use Getopt::Std; my %opts; getopts('hvp:n:t:w:c:', \%opts); die("\n". "Usage: $0 [options]\n". "\n". "Options:\n". "${indent}-h display this help message\n". "${indent}-v verbose\n". "${indent}-p port the base UDP port\n". "${indent}-n id the instance id (max. 12 chars)\n". "${indent}-t mins the heartbeat interval in minutes\n". "${indent}-w secs the startup sleep interval\n". "${indent}-c cmd the shell executable to control the switches\n". "\n". "Controls a RPi with GPIO driven switches.\n". "\n". "More information with: perldoc $0\n". "\n". "" ) if ($opts{h}); my $verbose = $opts{v}; my $client_base_port = $opts{'p'} || 50000; my $instance_id = $opts{'n'} || xpl_build_automatic_instance_id; my $heartbeat_interval = $opts{'t'} || 5; my $startup_sleep_time = $opts{'w'} || 0; $configuration{'command'} = $opts{'c'} || $configuration{'command'}; ################################################################################ # Internal functions # #------------------------------------------------------------------------------- # Get relay controls # sub get_relay_controls { my ($configuration_ref) = @_; my @controls = (); # read GPIOs my $pins_ref = $$configuration_ref{'pins'}; my @pins = @$pins_ref; foreach my $pin (@pins) { my $value = `$$configuration_ref{'command'} read $pin`; chomp($value); $value =~ s/0/on/; $value =~ s/1/off/; push(@controls, $value); } # send message return(@controls) } #------------------------------------------------------------------------------- # Translate an xPL message to the corresponding WiringPi command # sub build_relay_command { my ($body_ref, $configuration_ref) = @_; my $command = ''; # interpret arguments my $outlet_id = lc($$body_ref{outlet}); my $value = lc($$body_ref{command}); my $pins_ref = $$configuration_ref{'pins'}; my $pin = $$pins_ref[$outlet_id-1]; if ( ($outlet_id ne '') and ($value ne '') ) { if ($value =~ m/toggle/i) { $value = `$$configuration_ref{'command'} read $pin`; chomp($value); $value = 1 - $value;; } $value =~ s/off/1/; $value =~ s/on/0/; $command .= "$$configuration_ref{'command'} mode $pin output && "; $command .= "$$configuration_ref{'command'} write $pin $value"; } return($command) } #------------------------------------------------------------------------------- # Translate a MSNswitch XML message to the corresponding status # sub build_status { my (@controls) = @_; my %status; # write to hash foreach my $index (1 .. $#controls+1) { $status{"outlet$index"} = $controls[$index-1]; } return(%status) } ################################################################################ # Catch control-C interrupt # $SIG{INT} = sub{ $xpl_end++ }; ################################################################################ # Main script # sleep($startup_sleep_time); # xPL parameters my $xpl_id = xpl_build_id($vendor_id, $device_id, $instance_id); my $xpl_ip = xpl_find_ip; # create xPL socket my ($client_port, $xpl_socket) = xpl_open_socket($xpl_port, $client_base_port); # display working parameters if ($verbose == 1) { system("clear"); print("$separator\n"); print("Controlling a MSNswitch at \"$configuration{'switch'}\".\n"); print($indent . "class id : $class_id\n"); print($indent . "instance id: $instance_id\n"); print("\n"); } #------------------------------------------------------------------------------- # Main loop # my $timeout = 1; my $last_heartbeat_time = 0; while ( (defined($xpl_socket)) && ($xpl_end == 0) ) { # check time and send heartbeat $last_heartbeat_time = xpl_send_heartbeat( $xpl_socket, $xpl_id, $xpl_ip, $client_port, $heartbeat_interval, $last_heartbeat_time ); # get xpl-UDP message with timeout my ($xpl_message) = xpl_get_message($xpl_socket, $timeout); # process XPL message if ($xpl_message) { my ($type, $source, $target, $schema, %body) = xpl_get_message_elements($xpl_message); if ( xpl_is_for_me($xpl_id, $target) ) { # process commands if ($schema eq "$class_id.basic") { if ($type eq 'xpl-cmnd') { if ($verbose > 0) { print("\n"); print("Received command from \"$source\"\n"); } my $command = build_relay_command(\%body, \%configuration); if ($command ne '') { if ($verbose == 1) { print $indent, "running: \"$command\"\n"; } my $response = `$command`; chomp($response); #print "-> $response\n"; } } } # process replies if ($schema eq "$class_id.request") { if ($type eq 'xpl-cmnd') { if ($verbose == 1) { print "\n"; print("Received request from \"$source\"\n"); } my @controls = get_relay_controls(\%configuration); #print "-> @controls\n"; my %status = build_status(@controls); if ($verbose == 1) { print($indent); foreach my $index (1 .. $#controls+1) { print("outlet$index: "); print($status{"outlet$index"}); if ($index <= $#controls) { print(", "); } } print("\n"); } xpl_send_message( $xpl_socket, $xpl_port, 'xpl-stat', $xpl_id, '*', "$class_id.status", %status ); } } } } } xpl_disconnect($xpl_socket, $xpl_id, $xpl_ip, $client_port); ################################################################################ # Documentation (access it with: perldoc ) # __END__ =head1 NAME xpl-piSwitch.pl - Controls a RPi with GPIO driven switches =head1 SYNOPSIS xpl-piSwitch.pl [options] =head1 DESCRIPTION This xPL client sends commands to a Raspberry Pi relay board in order to power on or off each of its 3 outlets. =head1 OPTIONS =over 8 =item B<-h> Display a help message. =item B<-v> Be verbose. =item B<-p port> Specify the base port from which the client searches for a free port. If not specified, the client will take a default value. =item B<-n id> Specify the instance id (name). The id is limited to 12 characters. If not specified, it is constructed from the host name. =item B<-t mins> Specify the number of minutes between two heartbeat messages. =item B<-w secs> Specify the number of seconds before sending the first heartbeat. This allows to start the client after the hub, thus eliminating an prospective startup delay of one heartbeat interval. =item B<-c command> Specifies the WiringPi C command. =back =head1 TEST Make sure you have an C running on the machine. Start the switch controller: C. Start C in another terminal window. Turn a switch on: C and C<$SCRIPTS_BASE_DIR/xpl-send.pl -v -d dspc-switch.loungeAmps -c control.basic outlet=1 command=on>. Toggle a switch's state: C and C<$SCRIPTS_BASE_DIR/xpl-send.pl -v -d dspc-switch.loungeAmps -c control.basic outlet=1 command=toggle>. Ask for a switch's staus: C<$SCRIPTS_BASE_DIR/xpl-send.pl -v -d dspc-switch.loungeAmps -c control.request command=request>. =head1 AUTHOR Francois Corthay, DSPC =head1 VERSION 1.0, 2017 =cut