#!/usr/bin/perl use Device::SerialPort; 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 = 'serPort'; # max 8 chars $class_id = 'serPort'; # max 8 chars $tty_name_length = 4; $separator = '-' x 80; $indent = ' ' x 2; #------------------------------------------------------------------------------- # global variables # my %configuration; $configuration{'comPort'} = '/dev/ttyS0'; $configuration{'baudrate'} = 9600; $configuration{'databits'} = 7; $configuration{'parity'} = 'even'; $configuration{'stopbits'} = 1; $configuration{'handshake'} = 'none'; $configuration{'commandStart'} = ''; $configuration{'commandStop'} = '\n'; my $string_from_serial_port = ''; ################################################################################ # Input arguments # use Getopt::Std; my %opts; getopts('hvp:n:b:e:t:w:', \%opts); die("\n". "Usage: $0 [serial_port_device] [serial_port_settings]\n". "\n". "Parameters:\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}-b str command start string\n". "${indent}-e str command end string\n". "${indent}-t mins the heartbeat interval in minutes\n". "${indent}-w secs the startup sleep interval\n". "\n". "Transfers messages between xPL and a serial port.\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{commandStart} = $opts{'b'} || $configuration{commandStart}; $configuration{commandStop} = $opts{'e'} || $configuration{commandStop}; $configuration{'comPort'} = $ARGV[0] || $configuration{'comPort'}; if ($configuration{'comPort'} =~ m/tty\.PL2303-\d\Z/) { my $index = $configuration{'comPort'}; $index =~ s/.*tty\.PL2303-//; chomp(my $device = `ls -m /dev/tty.PL2303-*`); my @devices = sort(split(/,\s+/, $device)); $configuration{'comPort'} = $devices[$index-1]; } for ($index = 1; $index <= $#ARGV; $index++) { $argument = $ARGV[$index]; #print "-> $argument\n"; if ($argument eq 'speed') { $configuration{'baudrate'} = $ARGV[$index+1]; } elsif ($argument eq 'baudrate') { $configuration{'baudrate'} = $ARGV[$index+1]; } elsif ($argument eq 'databits') { $configuration{'databits'} = $ARGV[$index+1]; } elsif ($argument eq 'parity') { $configuration{'parity'} = $ARGV[$index+1]; } elsif ($argument eq 'stopbits') { $configuration{'stopbits'} = $ARGV[$index+1]; } elsif ($argument eq 'handshake') { $configuration{'handshake'} = $ARGV[$index+1]; } elsif ($argument eq 'cs8') { $configuration{'databits'} = 8; } elsif ($argument eq 'cs7') { $configuration{'databits'} = 7; } elsif ($argument eq '-parenb') { $configuration{'parity'} = 'none'; } elsif ($argument eq 'parenb') { $configuration{'parity'} = 'even'; } elsif ($argument eq '-parodd') { $configuration{'parity'} = 'odd'; } elsif ($argument eq 'parodd') { $configuration{'parity'} = 'odd'; } elsif ($argument eq '-cstopb') { $configuration{'stopbits'} = 1; } elsif ($argument eq 'cstopb') { $configuration{'stopbits'} = 2; } elsif ($argument eq '-crtscts') { $configuration{'handshake'} = 'none'; } elsif ($argument eq 'crtscts') { $configuration{'handshake'} = 'rts'; } } ################################################################################ # Internal functions # #------------------------------------------------------------------------------- # Open serial port # sub open_serial_port { my ($com_port) = @_; # open serial port my $serial_port = new Device::SerialPort($com_port) || die "Can't open $com_port: $!\n"; # set default communication parameters $serial_port->baudrate($configuration{baudrate}); $serial_port->databits($configuration{databits}); $serial_port->parity($configuration{parity}); $serial_port->stopbits($configuration{stopbits}); $serial_port->handshake($configuration{handshake}); return($serial_port) } #------------------------------------------------------------------------------- # Get a string from serial port and send it to xPL # sub get_serial_string { my ($serial_port) = @_; # get a string $string_from_serial_port .= $serial_port->read(255); my $string = ''; if ($string_from_serial_port =~ m/$configuration{commandStop}/) { $string = $string_from_serial_port; $string =~ s/$configuration{commandStop}.*//s; eval('$string .= "' . $configuration{commandStop} . '"'); $string_from_serial_port =~ s/.*?$configuration{commandStop}//s; } # remove start and stop delimiters $string =~ s/$configuration{commandStop}\Z//; $string =~ s/\A$configuration{commandStart}//; # replace unprintable chars my $printable_string = ''; for (my $index = 0; $index < length($string); $index++) { my $ord = ord(substr($string, $index, 1)); if ( ($ord < ord(' ')) or ($ord > ord('~')) ) { $printable_string .= '\x' . sprintf("%02X", $ord); } elsif ($ord == ord('\\')) { $printable_string .= chr($ord) . chr($ord); } else { $printable_string .= chr($ord); } } return($printable_string) } #------------------------------------------------------------------------------- # Get a string from xPL and send it to serial port # sub send_serial_string { my ($serial_port, $string) = @_; # add sart and stop strings $string = $configuration{commandStart} . $string . $configuration{commandStop}; # transform the string for non-printable chars eval('$string = "' . $string . '"'); # write it to the serial port $serial_port->write($string); } ################################################################################ # Catch control-C interrupt # $SIG{INT} = sub{ $xpl_end++ }; ################################################################################ # Main script # sleep($startup_sleep_time); # open serial port my $serial_port = open_serial_port($configuration{'comPort'}); # 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("Transferring data between xPL port $client_port and serial port $configuration{'comPort'}.\n"); print($indent, "instance id: $instance_id\n"); print($indent, "baudrate: $configuration{'baudrate'}\n"); print($indent, "databits: $configuration{'databits'}\n"); print($indent, "parity: $configuration{'parity'}\n"); print($indent, "stopbits: $configuration{'stopbits'}\n"); print($indent, "handshake: $configuration{'handshake'}\n"); print($indent, "command start: \"$configuration{'commandStart'}\"\n"); print($indent, "command stop: \"$configuration{'commandStop'}\"\n"); print("$separator\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) { #print "$xpl_message\n"; my ($type, $source, $target, $schema, %body) = xpl_get_message_elements($xpl_message); if ( xpl_is_for_me($xpl_id, $target) ) { if (lc($schema) eq lc("$class_id.basic")) { if ($type eq 'xpl-cmnd') { my $command = $body{'command'}; if ($verbose > 0) { print("\n"); print("Received \"$command\" from \"$source\"\n"); } send_serial_string($serial_port, $command); } } } } # check if string available my $string = get_serial_string($serial_port); if (length($string) > 0) { if ($verbose == 1) { print("${indent}Received \"$string\" from serial port\n"); } xpl_send_message( $xpl_socket, $xpl_port, 'xpl-trig', $xpl_id, '*', "$class_id.response", ('status' => $string) ); } } xpl_disconnect($xpl_socket, $xpl_id, $xpl_ip, $client_port); ################################################################################ # Documentation (access it with: perldoc ) # __END__ =head1 NAME xpl-serial_port.pl - Transfers commands and responses between xPL and a serial port =head1 SYNOPSIS xpl-serial_port.pl [options] [serial_port_device] [serial_port_settings] =head1 DESCRIPTION This xPL client sends commands to a device attached to the serial port C. Default value is C. It also returns responses from the device. An xPL command message's string is sent to the serial port. Before being sent, the command start and stop strings are appended and the string is evaluated in the same way as it would have been in Perl. As an example, with empty command start and command stop being C<\n>, a C would send C to the serial port. Characters arriving to the serial port are scanned for the command stop string and sent as an xPL C status message. Unprintable characters are replaced by their hexadecimal representation. As an example, a linefeed is transmitted as C<\x0A>. =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 8 characters. If not specified, it is constructed from the host name and the serial port device name =item B<-b str> Specify the command start (beginning). The command start string is appended to the beginning of the command sent to the serial port. =item B<-e str> Specify the command stop (end). The command stop string is appended to the end of the command sent to the serial port. The command stop string is also searched for dertemining the end of a response from the device attached to the serial port. =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. =back =head1 CONFIGURATION This xPL client replies to C and C commands and modifies its configuration based on C commands. Configuration items are: =over 16 =item B Serial port baud rate. Typical values are 9600 and 19200. =item B Serial port data bit number. Typically 7 or 8. =item B Serial port parity. Values are C, C or C. =item B Serial port stop bit number. Usually 1, sometimes 2. =item B Serial port handshake type. Usually C, sometimes C. =item B String added to the beginning of each string sent to the serial port. =item B String added to the end of each string sent to the serial port. =back =head1 TEST In order to test this xPL client, let's fix a null-modem cable between the two first serial ports of a machine. We'll drive C by the xPL client and C with a serial communication program such as C. Make sure you have an C running on the machine. Start C in a terminal window. Start C in another terminal window. Launch the command C. You should see the corresponding string appearing in the serial communication program's window. Type some text in the serial communication program in order to simulate responses from the serial port device. You should see C displaying the corresponding message (among other heartbeats) each time the command stop string has been typed. =head1 AUTHOR Francois Corthay, DSPC =head1 VERSION 2.0, 2012 =cut