#!/usr/bin/perl -wT
# plcx
# Last edited 1/13/2004 7:00am
#
# example program for the PLC perl module by Dan Baker (dan.baker@bigfoot.com)
# Not affiliated with Allen-Bradley Company, Incorporated.

# (For PLC5, SLC, and MicroLogix Processors)

# You must edit the PLC rc file (usually /plc/PLCproc.rc)
# with information on your PLC's before using this software.
# (See the README file, the example PLCproc.rc file and PLC.pm)

use PLC 0.14;

# There is no warrantee for this software, but then you didn't pay
# for it either, so we're even.

# Currently only Allen-Bradley(tm) PLC's and SLC's are supported
# See PLC.pm for more information

# This program and the PLC.pm module are extremely experimental.
# If you think the following warnings are too extreme,
# you need to gain more experience with industrial controllers
# before using this program.

# This is experimental software, not to be used for mission-critical
# applications. You assume all responsibility for its use.
# Your production lines can grind to a halt, causing lost revenue,
# or worse, equipment can be destroyed or people injured or killed.
# You use this program at your own risk.

# DANGER: DON'T write to a PLC unless you are certain it is safe to do so!!!
# It is assumed that you are experienced in PLC programming/troubleshooting
# and that you know EXACTLY what you are doing. PLC's are used to control
# industrial processes, motors, steam valves, hydraulic presses, etc.
# You are ABSOLUTELY RESPONSIBLE for ensuring that NO-ONE is in danger
# of being injured or killed because you affected the operation
# of a running PLC (with this software or any other).

# Usage:
# plcx [-r] [-D] [-x] [-b] [-c CMD] [-f FNC] [-W writedata] PLCNAME [ADDRESS]

# options are:
#  -D		debug mode (lots of information you never wanted to know)
#  -q		quiet mode
#  -x		PLC::printreturndata prints returned data in hexadecimal
#  -b		PLC::printreturndata prints returned data in binary
#  -c CMD	CMD is in hex (usually 6 or F).
#  -f FNC	FNC is in hex
#  -W DATA[,DATA] Data to write to PLC
#  -r		raw print mode (don't use PLC::printreturndata, just print)
#  -s SEPCHAR	separation character for printing (if using -r)

# example: plcx -cF -f29 LINE_1 N7
#  sends a Read Section Size request (F-29) to PLC LINE_1 for data file N7

# example: plcx -cF -f68 LINE_1 N7:5
#  sends a PLC Typed Data Read request (F-68) to PLC LINE_1 for data word N7:5

# If ADDRESS isn't specified, CMD-FNC defaults to 6-3 (Get Diagnostic Status)
# Otherwise,
#  For PLC's, CMD-FNC defaults to F-68 (PLC5 Typed Read) if no option -W
#                              or F-67 (PLC5 Typed Write) if -W DATA (writing)
#  For SLC's, CMD-FNC defaults to F-A2 (SLC Typed Read) if no option -W
#                              or F-AA (SLC Typed Write) if -W DATA (writing)
# example: plcx LINE_2 T4:0.ACC
#    sends a PLC Typed Data Read request (F-68) if LINE_2 is a PLC,
# or sends a SLC Typed Data Read request (F-A2) if LINE_2 is a SLC.

sub usage {
 print STDERR "\nusage: $0 processorname file:element [# of elements]\n";
 print STDERR << "USAGE";
 options are:
  -D		debug mode (lots of information you never wanted to know)
  -q		quiet mode
  -x		PLC::printreturndata prints returned data in hexadecimal
  -b		PLC::printreturndata prints returned data in binary
  -c CMD	CMD is in hex (usually 6 or F).
  -f FNC	FNC is in hex
  -W DATA[,DATA] Data to write to PLC
  -r		raw print mode (don't use PLC::printreturndata, just print)
  -s SEPCHAR	separation character for printing (forces -r raw mode)

USAGE
 print STDERR " Available processors are: \n";
 my $i = 0;
 PLC::read_rcfile();
 for (sort keys %PLC::AvailPLCs) {
  printf STDERR "%25s", $_; $i++;
  if ($i >= 3) { print STDERR "\n"; $i = 0 }
 }
 print STDERR "\n";
 exit 1;
}

sub default_to {
 ($cmd,$fnc) = @_;
 unless ($PLC::quiet) {
  printf("\n=== Defaulting to Command %X-%02X (hex) ===\n\n", $cmd, $fnc);
 }
}

####----------------------XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
#### START OF MAIN PROGRAM
####----------------------XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

use vars qw($opt_q $opt_D $opt_x $opt_b $opt_f $opt_c $opt_W $opt_r $opt_s);

use vars qw($cmd $fnc $debug @data_array);

### EXAMPLE - if tns_server is running on another host:
### $PLC::tns_server_host = 'example.tns.server'; # (default = 'localhost')

PLC::getopts('qDxbc:f:W:rs:');
$PLC::hex =  $opt_x;
$PLC::bin =  $opt_b;
$debug = $opt_D || 0;
PLC::debug($debug);
$cmd = hex($opt_c) if defined $opt_c;
$fnc = hex($opt_f) if defined $opt_f;
$PLC::quiet = $opt_q unless $PLC::debug;
$PLC::AlwaysInitGatewayChannel = 0;
$opt_r = 1 if $opt_s; # force raw print mode if separation char specified
$opt_s ||= ','; # Separation character ( if using raw print mode ($opt_r) )

my $PLCname = uc shift; # IP address of PLC (hostname is OK too)
my $PLCtype;
if ($debug >= 5) {
 print "==================== START OF DEBUG DATA =======================\n";
}
unless ($PLC::quiet) {
 print $PLC::COPYRIGHT;
 print "\nCommand line=$PLC::commandline\n" if $debug;
 print "\nUsing PLC.pm version $PLC::VERSION, revision date $PLC::REVDATE\n\n";
}
usage unless $PLCname;
print "PLCname = $PLCname\n" if $PLC::debug;
PLC::read_rcfile($PLCname);
usage unless defined $PLC::AvailPLCs{$PLCname};

my $data_addr = shift;

unless ( (defined $cmd) || (defined $fnc) ) {
 if ($data_addr) {
  $PLCtype = $PLC::AvailPLCs{$PLCname};
  if ( ($PLCtype =~ /^SLC/i) || ($PLCtype =~ /^MicroLogix/i) ) {
   if (defined $opt_W) {
    default_to(0x0F,0xAA); # default to F-AA SLC typed data write
   }
   else {
    default_to(0x0F,0xA2); # default to F-A2 SLC typed data read
   }
  } # end if SLC
  else { # assume it's a PLC
   if (defined $opt_W) {
    default_to(0x0F,0x67); # default to F-67 PLC typed data write
   }
   else {
    default_to(0x0F,0x68); # default to F-68 PLC typed data read
   }
  } # end if PLC
 } # end if data_addr
 else {			# no data address specified, so ...
  default_to(6,3);	# default to diagnostic status (6-03)
 }
}

usage unless ( (defined $cmd) && (defined $fnc) );

$data_addr = uc $data_addr;
my $elements = shift || 1;
$data_addr =~ s/\;/\:/g;

#############################
 if ($PLC::debug) {
  print "PLCname = $PLCname, ";
  printf("CMD=0x%02X, FNC=0x%02X\n", $cmd, $fnc);
  if ($data_addr) {
   print "Data address = $data_addr\n";
   print "Elements = $elements\n";
   print "Write Data = \'$opt_W\'\n" if defined $opt_W;
  }
 }

 my @writedata;
 if (defined $opt_W) { # change write data to an array
  if ($opt_W eq '') {
   @writedata =( '' );
  }
  else {
   @writedata = (split ',',$opt_W);
  }
  $elements = scalar @writedata;
 }

# use PLC::readPLCdata if reading multiple strings (ST)
 my $readPLCdata = ( ($data_addr =~ /^ST/i)
                  && ($elements > 1)
                  && !(defined $opt_W) ) ? 1 : 0 ;

 if ($readPLCdata) { # use PLC::readPLCdata
  @data_array = PLC::readPLCdata($PLCname,$data_addr,$elements);
 }
 else { # use PLC::PLCcommand
  my $hash_parms = 1;
  if ($hash_parms) {		# You can call PLCcommand this way ...
   @data_array = PLC::PLCcommand (PLCname   => $PLCname,
				  CMD       => $cmd,
				  FNC       => $fnc,
				  DataAddr  => $data_addr,
				  Elements  => $elements,
				  WriteData => \@writedata);
  }
  else {				# or this way.
   @data_array
    = PLC::PLCcommand($PLCname,$cmd,$fnc,$data_addr,$elements,@writedata);
  }
 }

 if ($PLC::errorcode && !$PLC::quiet) {
  print "\n\n++ ERROR $PLC::errorcode ++\n";
  print "++ $PLC::errormsg[$PLC::errorcode] ++\n\n";
  if ($PLC::errorcode == 6) {
   print "PLC returned error code: ", PLC::ABerrormessage($PLCname),"\n";
  }
  exit(1);
 }

 if( ($data_addr =~ /^ST/i) && ($elements > 1) && !(defined $opt_W) ) {
  $opt_r = 1; # force raw print mode when reading multiple string elements
 }
 if (defined($data_array[0])) {
  unless ($opt_r) {
   print "\nPLC name = $PLCname\n" unless $PLC::quiet;
   PLC::printreturndata($PLC::PLCproc{$PLCname});
   print "\n" unless $PLC::quiet;
  }
  if ($opt_r) { # raw print mode, don't use PLC::printreturndata
   print (join "$opt_s", @data_array); print "\n";
  }
 }
 else { # PLCcommand returned undef
  print STDERR "\n\n++ Some kind of error ++\n\n" unless $PLC::quiet;
  exit(1);
 }

if ($debug >= 5) {
 print "===================== END OF DEBUG DATA ========================\n";
}

# End of Program
