#!/usr/bin/env perl # -*- perl -*- # 'di'; # 'ig00'; #----------------------------------------------------------------------- # Module Name: $Source: /Users/goeke/programming/perl/crater/RCS/bcmd,v $ # Purpose: Process ASCII CRaTER commands into CCSDS packets # Language: Perl 5 # Assumptions: Data base is RFG-standard *.cdb format # Part Number: # Author: Robert F. Goeke (goeke@space.mit.edu # References: # Copyright: Massachusetts Institute of Technology 2006 #----------------------------------------------------------------------- ######################################################################## # Initial Constants ######################################################################## $Version = '$Id: bcmd,v 3.2 2008/02/27 15:30:46 goeke Exp goeke $'; require 5.002; use Socket; use Time::Local; use Time::gmtime; $UDPADDR = 11402; # UDP to use this default port $UDPHOST = "localhost"; # UDP uses this host name # use 255.255.255.255 for broadcast $c_version = 0; # CCSDS Version Number $c_type = 1; # CCSDS "1" for Telecommand $c_secFlag = 1; # CCSDS "1" for secondary header present $c_appId = 100; # CCSDS 0x64 for CRaTER Application ID $c_segFlag = 3; # CCSDS "3" to indicate no segmentation # $c_seqCount; # CCSDS incremented for each packet sent # $c_length; # CCSDS byes following this header - 1 ### CCSDS Primary Header is 3 words long; last 2 must be set later $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 1 word long; must be set later $cs_reserved = 0; # CCSDS Sec Hdr "0" means non-standard header # $cs_commandID; # CCSDS Sec Hdr applied as 1553 sub-addr # $cs_checksum; # CCSDS Sec Hdr checksum; XOR to yield 0xFF # $CCSDS_Sone = $cs_reserved<<15 | $cs_commandID<<8 | $cs_checksum); use Time::Local; use Time::gmtime; $Epoch = timegm(0,0,0,1,0,2001); # Epoch for LRO MET $LeapSec = 1; # occured 1/1/06 # NB Perl time() function does not include leap seconds $Bypass = 0; # Used as a flag between &Number and &Emit $SUBJECT = 'CRaTER_Command'; # subject line for e-mail message # Valid command list $Valid = ' bias on | off cal low | high on | off calamp 8-bit-number calrate low | high disc thin | thick 8-bit-number mask c1 c2 c3 ... /mask c1 c2 c3 ... litmask 32-bit-number(high) 32-bit-number(low bytes) process d1 | d2 | d3 | d4 | d5 | d6 on | off echo 16-bit-number time - | seconds:subseconds wait number-of-seconds test clear reset 1553 A | B clock on | off '; ######################################################################## ######################################################################## # Main Program executes here ######################################################################## ######################################################################## &Setup; &Stream; ##########################That's all, folks! ########################### ######################################################################## ################################################################### # Emit either CCSDS packet or STOL command # Socket or ASCII output much have been set up # Arg: 1553 sub-address, command value in binary packed format ################################################################### sub Emit { my ($addr,$val1,$val2); $addr = $_[0]; $val1 = $_[1]; $val2 = $_[2]; if ($Bypass) { # if &Number says bad stuff, just return $Bypass = 0; # after reseting flag return; } if (!defined($ASCII)) { # this is the default case &Emit_Packet($addr,$val1,$val2); return; } else { &Emit_Ascii($addr,$val1,$val2); } } ################################################################### # Emit ASCII STOL command either to STDOUT or email ################################################################### sub Emit_Ascii { my ($addr,$val1,$val2,$stol); $addr = $_[0]; $val1 = $_[1]; $val2 = $_[2]; if ($Verbose) { if (defined($val2)) { printf "%d 0x%04x 0x%04x -> ",$addr,$val1,$val2; } else { printf "%d 0x%04x -> ",$addr,$val1; } } SW: { # large SWITCH statement if ($addr == 1) { $stol = sprintf("CRSYNCCMD %s.%d",&Localtime($val1),$val2); next SW; } if ($addr == 2) { $stol = sprintf("%s 0x%04x",'CRCMDECHO',$val1); next SW; } if ($addr == 3) { if ($val1 == 1<<13) { $stol = 'CRDETBIAS OFF'; next SW; } if ($val1 == 1<<12) { $stol = 'CRDETBIAS ON'; next SW; } if ($val1 == 1<<11) { $stol = 'CRELECCALLOW OFF'; next SW; } if ($val1 == 1<<10) { $stol = 'CRELECCALLOW ON'; next SW; } if ($val1 == 1<<9) { $stol = 'CRELECCALHIGH OFF'; next SW; } if ($val1 == 1<<8) { $stol = 'CRELECCALHIGH ON'; next SW; } if ($val1 == 1<<7) { $stol = 'CRELECCALRATE LOW'; next SW; } if ($val1 == 1<<6) { $stol = 'CRELECCALRATE HIGH'; next SW; } if ($val1 == 1<<4) { $stol = 'CRDATATESTMODE'; next SW; } if ($val1 == 1<<1) { $stol = 'CRCLRCMDS'; next SW; } if ($val1 == 1) { $stol = 'CRSYSRST'; next SW; } print STDERR "ERR Bad argument value supplied for sub-address $addr\n"; last SW; } if ($addr == 4) { if ($val1 == 1<<15) { $stol = 'CRDETD!PROC DIS'; next SW; } if ($val1 == 1<<14) { $stol = 'CRDETD!PROC ENA'; next SW; } if ($val1 == 1<<13) { $stol = 'CRDETD2PROC DIS'; next SW; } if ($val1 == 1<<12) { $stol = 'CRDETD2PROC ENA'; next SW; } if ($val1 == 1<<11) { $stol = 'CRDETD3PROC DIS'; next SW; } if ($val1 == 1<<10) { $stol = 'CRDETD3PROC ENA'; next SW; } if ($val1 == 1<<9) { $stol = 'CRDETD4PROC DIS'; next SW; } if ($val1 == 1<<8) { $stol = 'CRDETD4PROC ENA'; next SW; } if ($val1 == 1<<7) { $stol = 'CRDETD5PROC DIS'; next SW; } if ($val1 == 1<<6) { $stol = 'CRDETD5PROC ENA'; next SW; } if ($val1 == 1<<5) { $stol = 'CRDETD6PROC DIS'; next SW; } if ($val1 == 1<<4) { $stol = 'CRDETD6PROC ENA'; next SW; } print STDERR "ERR Bad argument value supplied for sub-address $addr\n"; last SW; } if ($addr == 5) { $stol = sprintf("%s 0x%08x 0x%08x",'CRDISCACCMASK',$val1,$val2); next SW; } if ($addr == 6) { if ($val1>255) { print STDERR "ERR Bad argument value supplied for CREVTAMPDISCTHN\n"; last SW; } $stol = sprintf("%s 0x%02x",'CREVTAMPDISCTHN',$val1); next SW; } if ($addr == 7) { if ($val1>255) { print STDERR "ERR Bad argument value supplied for CREVTAMPDISCTHK\n"; last SW; } $stol = sprintf("%s 0x%02x",'CREVTAMPDISCTHK',$val1); next SW; } if ($addr == 8) { if ($val1>255) { print STDERR "ERR Bad argument value supplied for CRELECCALAMP\n"; last SW; } $stol = sprintf("%s 0x%02x",'CRELECCALAMP',$val1); next SW; } if ($addr == 30) { $stol = "### EGSE control: 1553 Side A" if ($val1 == 1); $stol = "### EGSE control: 1553 Side B" if ($val1 == 2); $stol = "### EGSE control: 1Hz tick ON" if ($val1 == 3); $stol = "### EGSE control: 1Hz tick OFF" if ($val1 == 4); if ($val1==0 || $val1>4) { print STDERR "ERR Bad argument value supplied for EGSE control\n"; } else { next SW; } } print STDERR "ERR Bad command sub-address $addr\n"; last SW; } continue { if (defined($mailaddr)) { printf "$stol\n" if $Verbose; $MAIL = "mail -s $SUBJECT"; open(SPEAK,"| $MAIL $mailaddr"); printf SPEAK "%s\n.",$stol; close(SPEAK); } else { printf STDOUT "%s\n",$stol; } } } ################################################################### # Emit a single CCSDS packet on the socket # Req: socket must have been set up # Arg: 1553 sub-address, command value in binary packed format ################################################################### sub Emit_Packet { $c_seqCount++; # This should roll properly in the following pack() my $cs_commandId = $_[0]; my $first = $_[1]; # The length here is the secondary header + data value - 1 # or data value + 1 (in bytes) $c_length = ($cs_commandId == 1 || $cs_commandId == 5) ? 9 : 3; my $second = $_[2] if ($c_length == 9); $cs_checksum = 0xFF; # this is only temporary $CCSDS_Ptwo = $c_segFlag<<14 | $c_seqCount ; $CCSDS_Pthree = $c_length; $CCSDS_Sone = $cs_reserved<<15 | $cs_commandId<<8 | $cs_checksum; $cs_checksum = 0xFF; # now we reset and recalculate if ($c_length == 3) { $telecommand = pack("n5", $CCSDS_Pone, $CCSDS_Ptwo, $CCSDS_Pthree, $CCSDS_Sone, $first); foreach $ii (unpack("C*",$telecommand)) { $cs_checksum ^= $ii; } } else { $telecommand = pack("n4 N2", $CCSDS_Pone, $CCSDS_Ptwo, $CCSDS_Pthree, $CCSDS_Sone, $first, $second); foreach $ii (unpack("C*",$telecommand)) { $cs_checksum ^= $ii; } } # Now need to rebuild the Seconday Header $CCSDS_Sone = $cs_reserved<<15 | $cs_commandId<<8 | $cs_checksum; # And the final answer is if ($c_length == 3) { $telecommand = pack("n5", $CCSDS_Pone, $CCSDS_Ptwo, $CCSDS_Pthree, $CCSDS_Sone, $first); printf "STAT %04x %04x %04x %04x %04x\n", unpack("n n n n n", $telecommand) if ($Verbose); } else { $telecommand = pack("n4 N2", $CCSDS_Pone, $CCSDS_Ptwo, $CCSDS_Pthree, $CCSDS_Sone, $first, $second); printf "STAT %04x %04x %04x %04x %04x%04x %04x%04x\n", unpack("n n n n n2 n2", $telecommand) if ($Verbose); } send(SPEAK,$telecommand,0,$Portaddr) || die $!; print "$Command\n" if ($Batch); # echo to STDOUT if in batch mode sleep(2) if ($Batch); # enforce >1sec if in batch mode } ######################################################################## # The general error handler # NB this subroutine does not return ######################################################################## sub Error { print "ERR @_\n"; } ######################################################################## # The general HELP! message # Short if $Verbose = 0, else not # NB this subroutine does not return ######################################################################## sub Help { $udphost = "-undefined-"; if (defined($ENV{CRATER_GSE_CMD})) { $udphost = $ENV{CRATER_GSE_CMD}; } elsif (defined($ENV{CRATER_GSE})) { $udphost = $ENV{CRATER_GSE}; } print " usage: $0 [-a] [-b] [-h] [-m target] [-p ###] [-v] [-z foo\@bar] "; exit if (!$Verbose); print " flag -a set ASCII output mode: STOL mnemonics to STDOUT -b sets Batch mode; echo command and guarantees >1sec command separation -h displays this message -m selects a target machine for UDP packets [$udphost} -p selects a port for UDP packets [$UDPADDR] -v specifies verbose operation -z email ASCII output to foo@bar Program accepts ASCII commands from STDIN and generates CCSDS packets on UDP socket by default. Lines beginning with # are treated as comments; no packet is generated. Valid Instrument Commands per Data ICD 32-02001:"; print $Valid; print " Numeric arguments may be given in decimal, octal, or hex format. The \"time\" command, if given a \"-\" argument, transmits current system time. The \"mask\" command ORs a series of detector coincidence selections; e.g.: to select a coincidence of D1D2D3D4 and D3D4D5D6 the command is \"mask 1234 3456\" To accept all possible combinations except single detector hits \"mask all /1 /2 /3 /4 /5 /6\" Or, alternatively \"/mask\" 1 2 3 4 5 6 The \"1553\" command is directed to the GSE, explicitly choosing the 1553 bus to be used in communicating with the instrument. The \"clock\" command is directed to the GSE, turning on and off the external 1Hz tick. This program may be exited by typing \"exit\" on STDIN. $Version "; exit; } ######################################################################## # Covert LRO time in seconds to local wall time ######################################################################## sub Localtime { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($Epoch + $_[0]); return sprintf("%02d-%03d-%02d:%02d:%02d", $year-100,$yday,$hour,$min,$sec); } ######################################################################## # Convert octal, hex, or decimal numeric input to standard number # NB $Bypass is used to signal &Emit that the numbers are good (0 = OK) # but reset it there, because &Number could be called several times # in building &Emit arguments ######################################################################## sub Number { return $_[0] if ($_[0] =~ /^[1-9]/); return $_[0] if ($_[0] =~ /^0$/); # do this before octal test! return oct($_[0]) if ($_[0] =~ /^0[1-7]+$|^0x[0-9a-fA-F]+$|^0X[0-9a-fA-F]+$/); &Error("Not recognized as a number> $_[0]"); $Bypass++; return 0; } ######################################################################## # Process all the command line arguments # Open input and output files # NB that output must be closed and write protected before progam exit! ######################################################################## sub Setup { $Verbose = 0; if (defined($ENV{CRATER_GSE_CMD})) { $UDPHOST = $ENV{CRATER_GSE_CMD}; } elsif (defined($ENV{CRATER_GSE})) { $UDPHOST = $ENV{CRATER_GSE}; } my $foo; while ( $foo = shift(@ARGV) ) { if ( $foo =~ /^-[aA]/ ) { $ASCII++; next; } if ( $foo =~ /^-[bB]/ ) { $Batch++; next; } if ( $foo =~ /^-[hH]/ ) { $Verbose++; &Help; } if ( $foo =~ /^-[mM]/ ) { if ( !defined($UDPHOST=shift(@ARGV)) ) { print STDERR "ERR Flag $foo requires an argument\n"; } next; } if ( $foo =~ /^-[pP]/ ) { if ( !defined($UDPADDR=shift(@ARGV)) ) { print STDERR "ERR Flag $foo requires an argument\n"; } next; } if ( $foo =~ /^-[vV]/ ) { $Verbose++; next; } if ( $foo =~ /^-[zZ]/ ) { if ( !defined($mailaddr=shift(@ARGV)) ) { print STDERR "ERR Flag $foo requires an argument\n"; } if ($mailaddr !~ /@/ ) { print STDERR "ERR Argument to flag $foo doesn't look like an e-mail address\n"; } $ASCII++; # gotta do this by implication next; } print STDERR "Unknown argument: $foo\n"; &Help; } print "STAT $Version\n" if ($Verbose); print "STAT Accepting input on STDIN\n" if ($Verbose); printf "STAT Epoch used to calculate MET: %s plus %d leap second\n", gmctime($Epoch), $LeapSec if ($Verbose); if (!defined($ASCII)) { socket(SPEAK, PF_INET, SOCK_DGRAM, getprotobyname('udp')) || die $!; $Portaddr = sockaddr_in($UDPADDR,inet_aton("$UDPHOST")); print "STAT Writing on socket $UDPADDR to $UDPHOST\n" if ($Verbose); } elsif (defined($Mailaddr)) { print "STAT Sending STOL to e-mail account $mailaddr\n" if ($Verbose); } else { print "STAT Sending STOL to STDOUT\n" if ($Verbose); } } ######################################################################## # Read input line by line; send one command packet per line ######################################################################## sub Stream { my ($bit,$id,$seconds,$subs); while(<>) { next if (/^#/); if (/(^[hH])|(^\?)/) { print "$Valid\n"; next; } chop $_; $Command = $_; # saved for echo at end of &Emit @_ = split; next unless (defined($_[0])); if ($_[0] eq "bias") { $bit = 1<<13; # This is the "bias off" position if ($_[1] !~ /on|off/) { &Error ("Bad arg> $_[1]"); next; } $bit >>= 1 if ($_[1] =~ /on/); &Emit(3,$bit); next; } if ($_[0] eq "cal") { $bit = 1<<11; if ($_[1] !~ /low|high/) { &Error ("Bad arg> $_[1]"); next; } $bit >>= 2 if ($_[1] =~ /high/); if ($_[2] !~ /on|off/) { &Error ("Bad arg> $_[2]"); next; } $bit >>= 1 if ($_[2] =~ /on/); &Emit(3,$bit); next; } if ($_[0] eq "calamp") { if (!defined($_[1])) { &Error("calamp requires an argument"); next; } $magnitude = &Number($_[1]); if ($magnitude>255 || $magnitude<0) { &Error("calamp argument must be in range 0..255"); next; } &Emit(8,$magnitude); next; } if ($_[0] eq "calrate") { $bit = 1<<7; if ($_[1] !~ /low|high/) { &Error ("Bad arg> $_[1]"); next; } $bit >>= 1 if ($_[1] =~ /high/); &Emit(3,$bit); next; } if ($_[0] eq "disc") { if ($_[1] !~ /thin|thick/) { &Error ("Bad arg> $_[1]"); next; } if ($_[1] =~ /thin/) { $id = 6; } else { $id = 7; } if (!defined($_[2])) { &Error("disc requires magnitude arguments"); next; } $low = &Number($_[2]); if ($low>255 || $low<0) { &Error("disc magnitude must be in range 0..255"); next; } &Emit($id, $low); next; } if ($_[0] eq "exit") { # Just quit this program exit 0; } # This isn't obvious! # Take a single coincidence event -- d1d2d3d4 -- and turn it # into a number: 0b001111 = decimal 15 # Then OR that numbered bit -- 15 here -- into the coincidence mask # Since d1-d6 yield a numbers 0->63, we need a 64-bit mask if ($_[0] eq "mask" || $_[0] eq "/mask") { my $reverse++ if ($_[0] =~ /\//); shift; if ($reverse) { $dmask = $emask = 0xffffffff; # pass all events } else { $dmask = $emask = 0; # start with clean slate } while (defined($coinc = shift)) { # so $coinc = 0 gets through if ($coinc =~ /[^\/1-6]/) { if ($coinc eq "all") { $dmask = $emask = 0xffffffff; next; } if ($coinc eq "none") { $dmask = $emask = 0; next; } &Error ("Incorrect coincidence spec> $coinc"); $Bypass++; next; } $num = 0; # form number for this coincidence $num |= 1<<0 if ($coinc =~ /1/); $num |= 1<<1 if ($coinc =~ /2/); $num |= 1<<2 if ($coinc =~ /3/); $num |= 1<<3 if ($coinc =~ /4/); $num |= 1<<4 if ($coinc =~ /5/); $num |= 1<<5 if ($coinc =~ /6/); if ($coinc =~ /\// || $reverse) { # the reject case if ($num < 32) { $dmask ^= 1<<($num); } else { $emask ^= 1<<($num-32); } } else { # the accept case if ($num < 32) { $dmask |= 1<<($num); } else { $emask |= 1<<($num-32); } } } &Emit(5,$emask,$dmask); next; } if ($_[0] eq "litmask") { &Emit(5,&Number($_[1]),&Number($_[2])); shift; shift; next; } if ($_[0] eq "process") { $bit = 1; if ($_[1] !~ /^d[1-6]$/) { &Error ("Bad arg> $_[1]"); next; } $bit <<= 15 if ($_[1] =~ /d1/); $bit <<= 13 if ($_[1] =~ /d2/); $bit <<= 11 if ($_[1] =~ /d3/); $bit <<= 9 if ($_[1] =~ /d4/); $bit <<= 7 if ($_[1] =~ /d5/); $bit <<= 5 if ($_[1] =~ /d6/); &Error ("Bad arg> $_[2]") if ($_[2] !~ /on|off/); $bit >>= 1 if ($_[2] =~ /on/); &Emit(4,$bit); next; } if ($_[0] eq "echo") { $magnitude = &Number($_[1]); if ($magnitude>0xFFFF || $magnitude<0) { &Error("echo argument must be in range 0..65535"); next; } &Emit(2,$magnitude); next; } if ($_[0] eq "time") { unless (defined($_[1])) { &Error("Requires argument"); next; } if ($_[1] !~ /^-$|^[0-9]+:[0-9]+$/) { &Error("$_[0]: bad argument> $_[1]"); next; } if ($_[1] eq "-") { $seconds = time() - $Epoch + $LeapSec; $subs = 0; print "STAT use local MET> $seconds:$subs\n" if $Verbose; } else { ($seconds,$subs) = split(/:/,$_[1]); print "STAT use given time> $seconds:$subs\n" if $Verbose; } &Emit(1,&Number($seconds),&Number($subs)); next; } if ($_[0] eq "test") { &Emit(3,1<<4); next; } if ($_[0] eq "clear") { &Emit(3,1<<1); next; } if ($_[0] eq "reset") { &Emit(3,1); next; } if ($_[0] eq "wait") { $magnitude = &Number($_[1]); if ($magnitude>0xFFFF || $magnitude<0) { &Error("echo argument must be in range 0..65535"); next; } sleep $magnitude; next; } if ($_[0] eq "1553") { if ($_[1] !~ /^A|^B/) { &Error("1553 argument must be either \"A\" or \"B\" "); next; } $bit = $_[1] =~ /^A/ ? 1 : 2; &Emit(30,$bit); next; } if ($_[0] eq "clock") { if ($_[1] !~ /^on|^off/) { &Error("clock argument must be either \"on\" or \"off\" "); next; } $bit = $_[1] =~ /^on/ ? 3 : 4; &Emit(30,$bit); next; } &Error("Unknown command> $_"); } } ######################################################################## # Pod follows ######################################################################## =for html