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