#!/usr/bin/env perl # -*- perl -*- #----------------------------------------------------------------------- # Module Name: $Source: /Users/goeke/programming/perl/crater/RCS/CComment,v $ # Purpose: UDP packet stream in; LRO-compatible, CCSDS packet out # Language: Perl 5 # Assumptions: Data ICD is 32-02001 # Part Number: # Author: Robert F. Goeke (goeke@space.mit.edu # References: # Copyright: Massachusetts Institute of Technology 2006 #----------------------------------------------------------------------- ######################################################################## # Initial Constants ######################################################################## $Version = '$Id: CComment,v 1.2 2007/08/13 13:51:18 goeke Exp goeke $'; require 5.002; use Socket; # use File::Copy; use Time::Local; use Time::gmtime; $Epoch = timegm(0,0,0,1,0,2001); # Epoch for LRO MET $LeapSec = 1; # Occured 1/1/06 $TLM_OUT = 11403; # default UDP telemetry output port $HOST_OUT = "localhost"; # default output UDP is this host name $APP_ID = 123; # default AppId for comments $MAXLEN = 4096; # max length of UDP packet in bytes ######################################################################## ######################################################################## # Main Program executes here ######################################################################## ######################################################################## &Setup; &Doctor; # should not return printf STDERR "Zounds!!!!\n"; exit 5; # Should never get here! ##########################That's all, folks! ########################### ######################################################################## ######################################################################## # Read a packet, add CCSDS header if necessary, and write the result # We really are assuming that the input stream contains # a) a binary CCSDS packet, which we pass unchanged # b) ASCII text to which we add a header and pack # Does not return ######################################################################## sub Doctor { while(1) { if (!defined($TLM_IN)) { &GetStdin; # packs stdin into $Packet } else { &GetPacket; # copies UDP input into $Packet } if (&FixPacket) { # Pass packets which already have headder printf ">> Adding CCSDS header\n" if ($Verbose); &MakePacket; # Add the CCSDS header } else { printf "." if ($Verbose); $Telemetry = $Packet; # $Packet is assumed to be pre-packed } &SendPacket; # Assumes output is in $Telemetry } } ######################################################################## # This answers the question -- does the packet need to be fixed? # (header checks taken from "verify"; not exhaustive, but probably adequate) ######################################################################## sub FixPacket { $VERSION = '0'; $TYPE = '0'; $SECFLAG = '1'; $SEGFLAG = '3'; $RESERVED = '0'; my ($one,$two,$three,$four,$five,$six,@contents) = unpack("nnnnnnC*",$Packet); my $version = 0x07 & $one>>13; my $type = 0x01 & $one>>12; my $secFlag = 0x01 & $one>>11; my $appId = 0x3FF & $one; my $segFlag = 0x03 & $two>>14; my $seqCount = 0x3FFF & $two; my $length = $three; my $res1 = 0x01 & $four>>15; my $sec_time = (0x7FFF&$four)<<16 | $five; my $subsec_time = 0x0F & $six>>12; my $res2 = 0x1F & $six>>7; my $test = 0x01 & $six>>6; my $tick = 0x01 & $six>>5; my $sn = 0x1F & $six; return 0 if ( $version==$VERSION && $type==$TYPE && $secFlag==$SECFLAG && $segFlag==$SEGFLAG && $res1==$RESERVED && $res2==$RESERVED && $appId>119 && $appId<130 ); # So the packet needs a header return 1; } ######################################################################## # Get a complete packet -> $packet ######################################################################## sub GetPacket { recv(INSTUFF, $Packet, $MAXLEN, 0) || die $!; } ######################################################################## # Get a complete packet -> $packet ######################################################################## sub GetStdin { my $in_line = <>; chop($in_line); if (length($in_line)>$MAXLEN) { printf STDERR ">> Input line on STDIN too long; IGNORED\n"; $in_line = "INPUT LINE ERROR"; } printf ">> STDIN: $in_line\n" if ($Verbose>1); $Packet = pack("A*",$in_line); } ######################################################################## # The general HELP! message # Short if $Verbose = 0, else not # NB this subroutine does not return ######################################################################## sub Help { print " usage: $0 [-a AppId] [-h] [-p UDP_in] [-m out_to] [-q UDP_out] [-v] "; exit if (!$Verbose); print " flag: -a specify AppID to be placed in CCSDS header [$APP_ID] -h displays this message -p selects port for input telemetry stream [$TLM_IN] -m selects machine name for output telemetry stream [$HOST_OUT] -q selects port for output telemetry stream [$TLM_OUT] -v specifies verbose operation Program accepts text from STDIN, adds an appropriate CRaTER-style CCSDS telemetry header, and emits the result as a UDP packet. Lines must be newline terminated and less than $MAXLEN characters long. When used with the -p flag to set an input port for UDP packets, the program adds, only if necessary, the CCSDS header; in this case STDIN is ignored. "; exit; } ######################################################################## # Convert LRO time to local version ######################################################################## sub Localtime { my ($sec,$min,$hour,$mday,$mon,$year) = localtime($Epoch + $_[0]); return sprintf("%02d:%02d:%02d %d/%d/%02d", $hour, $min, $sec, $mon+1, $mday, $year-100); } ######################################################################## # Add a header to these contents # (Taken from "primary-packet") ######################################################################## sub MakePacket { ### CCSDS Primary Header is 3 words long; last 2 must be set later my $c_version = 0; # CCSDS Version Number my $c_type = 0; # CCSDS "1" for Telecommand my $c_secFlag = 1; # CCSDS "1" for secondary header present my $c_appId; # CCSDS CRaTER Application ID my $c_segFlag = 3; # CCSDS "3" to indicate no segmentation # $c_seqCount; # CCSDS incremented for each packet sent # NB this is a global so it saves state to increment my $c_length; # CCSDS byes following primary header - 1 # my $CCSDS_Pone = $c_version<<13 | $c_type<<12 | $c_secFlag<<11 | $c_appId ; # $CCSDS_Ptwo = $c_segFlag<<14 | $c_seqCount ; # $CCSDS_Pthree = $c_length; ### CCSDS Secondary Header is 3 words long # $s_reserved_0 = 0; # bit 48: "0" means non-standard header # $s_time_sec; # bits 49-79: s/c time in seconds my $s_time_subsec = 0; # bits 80-83: s/c time in subseconds # $s_reserved_1 = 0; # bits 84-88: constant 0 my $s_test_mode = 0; # bit 89: "1" means in test mode my $s_oneHertz = 0; # bit 90: 1 Hz tick received my $s_serialNo = 0; # bits 91-95: Instrument Serial No. # $CCSDS_Sone = $s_time_sec&0x7FFFFFFF; # $CCSDS_Stwo = $s_time_sbsec<<11 | $s_oneHertz<<5 | $s_serialno; $c_appId = $APP_ID; my $CCSDS_Pone = $c_version<<13 | $c_type<<12 | $c_secFlag<<11 | $c_appId ; # This should roll properly in the following pack() my $CCSDS_Ptwo = $c_segFlag<<14 | $c_seqCount++ ; $c_length = 5 + length($Packet); my $CCSDS_Pthree = $c_length; $s_time_sec = time() - $Epoch + $LeapSec; my $CCSDS_Sone = $s_time_sec & 0x7FFFFFFF; my $CCSDS_Stwo = ($s_time_subsec&0x0F)<<12 | $s_test_mode<<6 | $s_oneHertz<<5 | $s_serialNo; $Telemetry = pack("n n n N n", $CCSDS_Pone,$CCSDS_Ptwo,$CCSDS_Pthree, $CCSDS_Sone,$CCSDS_Stwo); $Telemetry = $Telemetry.$Packet; } ######################################################################## # Send a complete packet contained in $Telemetry ######################################################################## sub SendPacket { send(OUTSTUFF,$Telemetry,0,$Portaddr) || die $!; } ######################################################################## # Process all the command line arguments # Open input and output files ######################################################################## sub Setup { $Verbose = 0; my ($foo,$jj); local ($inport); while ( $foo = shift(@ARGV) ) { if ( $foo =~ /^-[aA]/ ) { # Specify appid if ( !defined($APP_ID=shift(@ARGV)) || $APP_ID =~ /[^0-9]/) { print STDERR "Flag $foo requires a numeric argument\n"; &Help; } next; } if ( $foo =~ /^-[hH]/ ) { # Ask for help $Verbose++; &Help; } if ( $foo =~ /^-[mM]/ ) { # Specify target output machine if ( !defined($HOST_OUT=shift(@ARGV)) ) { print STDERR "Flag $foo requires an argument\n"; &Help; } next; } if ( $foo =~ /^-[pP]/ ) { # Specify input port if ( !defined($TLM_IN=shift(@ARGV)) || $TLM_IN =~ /[^0-9]/) { print STDERR "Flag $foo requires a numeric argument\n"; &Help; } $inport++; # used as a sanity check later next; } if ( $foo =~ /^-[qQ]/ ) { # Specify output port if ( !defined($TLM_OUT=shift(@ARGV)) || $TLM_OUT =~ /[^0-9]/) { print STDERR "Flag $foo requires a numeric argument\n"; &Help; } next; } if ( $foo =~ /^-[vV]/ ) { $Verbose++; next; } print STDERR "Unknown argument: $foo\n"; &Help; } print ">> $Version\n" if ($Verbose); if ($APP_ID<123 || $APP_ID>126) { printf STDERR "AppId must be in the range 123 to 125\n"; exit 2; } if (!defined($TLM_IN)) { print ">> Taking input from STDIN\n" if ($Verbose); } else { socket(INSTUFF, PF_INET, SOCK_DGRAM, getprotobyname('udp')) || di $!; bind(INSTUFF, sockaddr_in($TLM_IN,INADDR_ANY)) || die $!; print ">> Accepting packets on socket $TLM_IN\n" if ($Verbose); } # we establish, but do not bind this socket socket(OUTSTUFF, PF_INET, SOCK_DGRAM, getprotobyname('udp')) || die $!; $Portaddr = sockaddr_in($TLM_OUT,inet_aton("$HOST_OUT")); print ">> Writing on socket $TLM_OUT to $HOST_OUT\n" if ($Verbose); } ######################################################################## # Pod follows ######################################################################## =for html CRaTER -- CComment =head2 NAME B -- CRaTER data merge into packet stream =head2 USAGE CComment [-a AppId] [-h] [-p UDP_in] [-m out_to] [-q UDP_out] [-v] =head2 FLAGS -a sets AppId [123] -h displays this message -p selects port for input telemetry stream; cancels STDIN -m selects machine name to which output stream is directed [localhost] -q selects port to which output stream is directed [11403] -v selects verbose operation =head2 DESCRIPTION Program accepts text from STDIN and formats each line into a separate CRaTER-style CCSDS telemetry packet, stripping off the trailing newline. The maximum line length is restricted to 4096 characters. If an input data stream port is selected via the B<-p> flag, the each received UDP packet is screened for a proper 12-byte header. If the header is present, the packet is passed unchanged. If the header is absent, one is added. If a new header is required, the time inserted will be the current machine time, translated to the LRO epoch. =head2 ENVIRONMENT perl5.002 Minimum version of Perl interpreter required CRATER_GSE Environment variable containing EGSE machine name =head2 BUGS There are implicit assumptions that the input to be formatted into UDP packets is newline-terminated ASCII strings. =head2 SEE ALSO rtlm =head2 AUTHOR Bob Goeke =head2 RCS Information $Id: CComment,v 1.2 2007/08/13 13:51:18 goeke Exp goeke $ =cut ######################################################################## # history follows ######################################################################## # $Log: CComment,v $ # Revision 1.2 2007/08/13 13:51:18 goeke # Restrict appids to 123-126 # # Revision 1.1 2007/08/11 14:40:02 goeke # Initial revision # # Revision 3.14 2007/06/22 17:34:09 goeke # Add -z flag # # Revision 3.13 2007/06/14 16:00:47 goeke # Fix appId selection for packet diagnostics # # Revision 3.12 2007/06/14 15:55:59 goeke # Add diagnostic for packets with bad (CRaTER) appIds # # Revision 3.11 2007/05/29 20:04:43 goeke # Fixed bug in &GetOneSecond; now does &Spray every time. # # Revision 3.10 2007/05/24 14:45:04 goeke # Fixed format of "echo" and "mask" output in -R mode # Changed logic for detecting end-of-file so that sync will work # over the 64-byte header (which starts off with NULs) # Added verbose display of last packet time. # # Revision 3.9 2007/05/03 15:07:35 goeke # Added time message to "F" command in file-stepping mode; # Fixed exit bug in file-stepping mode # # Revision 3.8 2007/04/17 15:36:16 goeke # Add POD # # Revision 3.7 2007/02/26 13:40:37 goeke # Fixed bug in emitting WARN for too many sockets open # # Revision 3.6 2007/02/23 16:59:48 goeke # Added "end of message" mark to "state" response # # Revision 3.5 2007/02/23 16:39:30 goeke # "state" now returns machine's name instead of IP address # # Revision 3.4 2006/11/01 13:33:45 goeke # Fix typo in &Setup opening input telemetry channel # # Revision 3.3 2006/10/31 21:54:36 goeke # Fix bug in display of active AppIds # # Revision 3.2 2006/10/30 23:17:37 goeke # Separated out the -r and -R versions of file retrieval # # Revision 3.1 2006/10/30 22:23:58 goeke # Start new major rev incorporating LRO-standard archive file retrieval # # Revision 2.12 2006/10/30 22:19:28 goeke # Fixed bug in -h flag; added -x flag # Now first packet is used to set $lasttime # # Revision 2.11 2006/10/30 20:28:16 goeke # Add -e flag # # Revision 2.10 2006/10/30 19:43:16 goeke # Add ability to scroll through LRO-standard data files # # Revision 2.9 2006/10/13 15:26:37 goeke # Add EXTRACT command # More error checking on appid numbers (but "foo" still works?) # # Revision 2.8 2006/09/29 20:27:15 goeke # Fix bugs in close commands # Eliminate the "flow" command and do all opens immediate # # Revision 2.7 2006/09/07 20:42:18 goeke # Added HELP response to UDP user. # # Revision 2.6 2006/09/07 20:28:35 goeke # Added STATE command # Implemented -p, -P, -s, -S commands # # Revision 2.5 2006/09/07 19:53:28 goeke # Fix bug in CLOSE port_number # # Revision 2.4 2006/09/07 19:39:20 goeke # Updated help(); took out signal handler as unnecessary. # Added CLOSE port_number feature # # Revision 2.3 2006/09/07 18:44:43 goeke # Seems to work; need to clean up help # # Revision 2.2 2006/09/07 18:28:50 goeke # Distributes packets OK, but CLOSE isn't working # # Revision 2.1 2006/09/07 15:47:57 goeke #