RDI_PD0_IO.pl
changeset 14 8c79b38a7086
child 18 bb7bb9f83db9
equal deleted inserted replaced
13:b176da8559b3 14:8c79b38a7086
       
     1 #======================================================================
       
     2 #                    R D I _ P D 0 _ I O . P L 
       
     3 #                    doc: Sat Jan 18 14:54:43 2003
       
     4 #                    dlm: Tue Nov 26 01:29:26 2013
       
     5 #                    (c) 2003 A.M. Thurnherr
       
     6 #                    uE-Info: 873 10 NIL 0 0 72 74 2 4 NIL ofnI
       
     7 #======================================================================
       
     8 
       
     9 # Read RDI BroadBand Binary Data Files (*.[0-9][0-9][0-9])
       
    10 
       
    11 # HISTORY:
       
    12 #	Jan 18, 2003: - incepted aboard the Aurora Australis (KAOS)
       
    13 #	Jan 19, 2003: - continued
       
    14 #	Jan 20, 2003: - replaced INTENSITY by AMPLITUDE
       
    15 #	Jan 21, 2003: - changed heading-correction field names
       
    16 #	Jan 27, 2003: - cosmetics
       
    17 #	Feb 14, 2003: - moved BT setup into header
       
    18 #	Mar 15, 2003: - moved 10th xmit voltage into header as BATTERY field
       
    19 #				  - removed again, because values are not meaningful
       
    20 #	Feb 24, 2004: - continued aboard Nathaniel B. Palmer (Anslope II)
       
    21 #				  - BUG: ensemble # was wrong on error messages
       
    22 #	Feb 26, 2004: - removed ESW_ERROR and bitmasking of ERROR_STATUS_WORD
       
    23 #	Feb 27, 2004: - removed some unused (commented-out) baggage
       
    24 #	Mar 11, 2004: - BUG: renamed ACD -> ADC
       
    25 #	Mar 18, 2004: - cosmetics
       
    26 #	Mar 30, 2004: - rewrote to speed up reading; new version takes
       
    27 #					~40% less time
       
    28 #	Sep 14, 2005: - made BT data optional (NUMBER_OF_DATA_TYPES)
       
    29 #				  - added DATA_FORMAT
       
    30 #	Sep 15, 2005: - debugged
       
    31 #				  - implement checksum to robustly find EOF
       
    32 #				  - renamed to RDI_BB_Read.pl
       
    33 #				  - BUG: had used POSIX::mktime with wrong month def!
       
    34 #	Oct 30, 2005: - added WH300 FW16.27 file format
       
    35 #				  - added DATA_FORMAT_VARIANT
       
    36 #				  - changed semantics so that first valid ensemble is
       
    37 #				    in E[0] (instead of E[$ensNo-1])
       
    38 #	Nov  8, 2005: - replaced UNIXTIME by UNIX_TIME
       
    39 #				  - added SECNO
       
    40 #	Aug 31: 2006: - added DAYNO
       
    41 #	Aug  1, 2007: - BUG: typo in monthLength()
       
    42 #	Sep 18, 2007: - modified readHeader() readDta() WBRhdr() WBRens() to
       
    43 #					conserve memory (no large arrays as retvals)
       
    44 #	Jun  4, 2008: - BUG: BB150 code was not considered on Sep 18, 2007
       
    45 #	Aug 15, 2010: - downgraded "unexpected number of data types" from error to warning
       
    46 #				  - BUG: WBRcfn had not been set correctly
       
    47 #				  - modified to allow processing files without time info
       
    48 #	May 12, 2011: - added code to report built-in-test errors
       
    49 #	Mar 19, 2013: - added support for WH600 data file (58 fixed leader bytes)
       
    50 #	Mar 20, 2013: - removed DATA_FORMAT stuff
       
    51 #				  - added support for BT data in subset of ensembles
       
    52 #	Apr 29, 2013: - changed semantics to assume EOF when unexpected number of data types
       
    53 #					are present in an ensemble
       
    54 #	Nov 25, 2013: - renamed from [RDI_BB_Read.pl]
       
    55 #				  - begin implementing WBWens()
       
    56 #				  - checkEnsemble() expunged
       
    57 
       
    58 # FIRMWARE VERSIONS:
       
    59 #	It appears that different firmware versions generate different file
       
    60 #	structures. Currently (Sep 2005) these routines have been tested
       
    61 #	with the following firmware versions (as reported by [listHdr]):
       
    62 #
       
    63 #	Firmw.	DATA_FORMAT(_VARIANT)	Owner 	Cruise	FIXED_LEADER_LENGTH
       
    64 #------------------------------------------------------------
       
    65 #	05.52	BB150 (1)				UH 		CLIVAR/P16S 42
       
    66 #	16.12	WH300 (1)				FSU 	A0304		53
       
    67 #	16.21	WH300 (1)				LDEO 	NBP0402		53
       
    68 #	16.27	WH300 (2)				Nash 	?			59
       
    69 
       
    70 # NOTES:
       
    71 #	- RDI stores data in VAX/Intel byte order (little endian)
       
    72 #	- the output data structure does not exactly mirror the file data
       
    73 #	  structure; the header is not stored at all and the fixed leader
       
    74 #	  data are not duplicated in every ensemble
       
    75 #	- in the RDI files some fields that logically belong into the header
       
    76 #	  or the fixed leader (e.g. BT_MIN_CORRELATION) appear in the 
       
    77 #	  ensemble data --- these are not read on input
       
    78 #	- the field names are generally unabbreviated except for
       
    79 #	  BT (= Bottom Track), RL (= Reference Layer), MIN and MAX
       
    80 #	- all arrays are 0-referenced, but the ensemble number is not!
       
    81 #	- a list of filenames can be be passed to readData() so that
       
    82 #	  files split onto several memory cards (typically .000 .001 &c)
       
    83 #	  can be read --- not sure if this works, actually
       
    84 #	- the RDI manuals are not entirely clear everywhere; I have made
       
    85 #	  guesses in some cases, but they should not affect the main
       
    86 #	  fields of interest
       
    87 #	- some fields in the fixed leader are not really fixed in a LADCP
       
    88 #	  setting (e.g. xducer orientation); I'v made an educated guess
       
    89 #	  as to which fields to move to the ENS array
       
    90 #	- all units except pressure are SI, i.e. in m and m/s
       
    91 #	- I don't understand the ERROR_STATUS_WORD; here's what 3 different
       
    92 #	  instruments returned:
       
    93 #	  	0x88000100	FSU instrument during A0304 (Firmware 16.12)
       
    94 #		0x88008180	LDEO uplooker (slave) during NBP0402 (Firmware 16.21)
       
    95 #		0x00008180	LDEO downlooker (master) during NBP0402 (Firmware 16.21)
       
    96 #	  According to the manual (January 2001 version) this would, for example,
       
    97 #	  indicate power failures on both FSU and LDEO slave instruments...
       
    98 
       
    99 # &readData() returns perl obj (ref to anonymous hash) with the following
       
   100 # structure:
       
   101 #
       
   102 #	NUMBER_OF_DATA_TYPES			scalar		6 (no BT) or 7
       
   103 #	ENSEMBLE_BYTES					scalar		?, number of bytes w/o checksum
       
   104 #	HEADER_BYTES					scalar		?
       
   105 #	FIXED_LEADER_BYTES				scalar		42 for BB150; 53 for WH300, 58 for WH600, 59 for WH300(Nash)
       
   106 #	VARIABLE_LEADER_BYTES			scalar		?
       
   107 #	VELOCITY_DATA_BYTES				scalar		?
       
   108 #	CORRELATION_DATA_BYTES			scalar		?
       
   109 #	ECHO_INTENSITY_DATA_BYTES		scalar		?
       
   110 #	PERCENT_GOOD_DATA_BYTES			scalar		?
       
   111 #	BT_PRESENT						bool		NUMBER_OF_DATA_TYPES == 7
       
   112 #	BT_DATA_BYTES					scalar		undefined, ? if BT_PRESENT
       
   113 #	CPU_FW_VER						scalar		0--255
       
   114 #	CPU_FW_REV						scalar		0--255
       
   115 #	BEAM_FREQUENCY					scalar		75, 150, 300, 600, 1200, 2400 [kHz]
       
   116 #	CONVEX_BEAM_PATTERN				bool		undefined, 1
       
   117 #	CONCAVE_BEAM_PATTERN			bool		undefined, 1
       
   118 #	SENSOR_CONFIG					scalar		1--3
       
   119 #	XDUCER_HEAD_ATTACHED			bool		undefined, 1
       
   120 #	BEAM_ANGLE						scalar		15,20,30,undefined=other [deg]
       
   121 #	N_BEAMS							scalar		4--5
       
   122 #	N_DEMODS						scalar		2--3(???),undefined=n/a
       
   123 #	N_BINS							scalar		1--128
       
   124 #	PINGS_PER_ENSEMBLE				scalar		0--16384
       
   125 #	BIN_LENGTH						scalar		0.01--64 [m]
       
   126 #	BLANKING_DISTANCE				scalar		0-99.99 [m]
       
   127 #	MIN_CORRELATION					scalar		0--255
       
   128 #	N_CODE_REPETITIONS				scalar		0--255
       
   129 #	MIN_PERCENT_GOOD				scalar		1--100 [%]
       
   130 #	MAX_ERROR_VELOCITY				scalar		0--5 [m/s]
       
   131 #	TIME_BETWEEN_PINGS				scalar		0--? [s]
       
   132 #	BEAM_COORDINATES				bool		undefined,1
       
   133 #	INSTRUMENT_COORDINATES			bool		undefined,1
       
   134 #	SHIP_COORDINATES				bool		undefined,1
       
   135 #	EARTH_COORDINATES				bool		undefined,1
       
   136 #	PITCH_AND_ROLL_USED				bool		undefined,1
       
   137 #	USE_3_BEAM_ON_LOW_CORR			bool		undefined,1
       
   138 #	BIN_MAPPING_ALLOWED				bool		undefined,1
       
   139 #	HEADING_ALIGNMENT 				scalar		-179.99..180 [deg]
       
   140 #	HEADING_BIAS			 		scalar		-179.99..180 [deg]
       
   141 #	CALCULATE_SPEED_OF_SOUND		bool		undefined,1
       
   142 #	USE_PRESSURE_SENSOR				bool		undefined,1
       
   143 #	USE_COMPASS						bool		undefined,1
       
   144 #	USE_PITCH_SENSOR				bool		undefined,1
       
   145 #	USE_ROLL_SENSOR					bool		undefined,1
       
   146 #	USE_CONDUCTIVITY_SENSOR			bool		undefined,1
       
   147 #	USE_TEMPERATURE_SENSOR			bool		undefined,1
       
   148 #	SPEED_OF_SOUND_CALCULATED		bool		undefined,1
       
   149 #	PRESSURE_SENSOR_AVAILABLE		bool		undefined,1
       
   150 #	COMPASS_AVAILABLE				bool		undefined,1
       
   151 #	PITCH_SENSOR_AVAILABLE			bool		undefined,1
       
   152 #	ROLL_SENSOR_AVAILABLE			bool		undefined,1
       
   153 #	CONDUCTIVITY_SENSOR_AVAILABLE	bool		undefined,1
       
   154 #	TEMPERATURE_SENSOR_AVAILABLE	bool		undefined,1
       
   155 #	DISTANCE_TO_BIN1_CENTER			scalar		0--655.35 [m]
       
   156 #	TRANSMITTED_PULSE_LENGTH		scalar		0--655.35 [m]
       
   157 #	RL_FIRST_BIN					scalar		1--128
       
   158 #	RL_LAST_BIN						scalar		1--128
       
   159 #	FALSE_TARGET_THRESHOLD			scalar		0--254, undefined=disabled
       
   160 #	LOW_LATENCY_SETTING				scalar		0--5(???)
       
   161 #	TRANSMIT_LAG_DISTANCE			scalar		0--655.35 [m]
       
   162 #	CPU_SERIAL_NUMBER				scalar		undefined, 0--65535 if WH300
       
   163 #	NARROW_BANDWIDTH				bool		undefined,1 (only set if WH300)
       
   164 #	WIDE_BANDWIDTH					bool		undefined,1 (only set if WH300)
       
   165 #	TRANSMIT_POWER					scalar		undefined, 0--255(high) if WH300
       
   166 #	TRANSMIT_POWER_HIGH				bool		undefined,1 (only set if WH300)
       
   167 #	BT_PINGS_PER_ENSEMBLE			scalar		0--999
       
   168 #	BT_DELAY_BEFORE_REACQUIRE		scalar		0--999
       
   169 #	BT_MIN_CORRELATION				scalar		0--255
       
   170 #	BT_MIN_EVAL_AMPLITUDE			scalar		0--255
       
   171 #	BT_MIN_PERCENT_GOOD				scalar		0--100 [%]
       
   172 #	BT_MODE							scalar		4,5,6(?)
       
   173 #	BT_MAX_ERROR_VELOCITY			scalar		0--5 [m/s], undef=not screened
       
   174 #	BT_RL_MIN_SIZE					scalar		0--99.9 [m]
       
   175 #	BT_RL_NEAR						scalar		0--999.9 [m]
       
   176 #	BT_RL_FAR						scalar		0--999.9 [m]
       
   177 #	BT_MAX_TRACKING_DEPTH			scalar		8--999.9 [m]
       
   178 #	ENSEMBLE[ensemble_no-1]			array		ensemble info
       
   179 #		XDUCER_FACING_UP			bool		undefined, 1
       
   180 #		XDUCER_FACING_DOWN			bool		undefined, 1
       
   181 #		N_BEAMS_USED				scalar		3,4,5(?)
       
   182 #		NUMBER						scalar		1--16777215
       
   183 #		BUILT_IN_TEST_ERROR			scalar		?,undefined=none
       
   184 #		SPEED_OF_SOUND				scalar		1400--1600 [m/s]
       
   185 #		XDUCER_DEPTH				scalar		0.1--999.9 [m]
       
   186 #		HEADING						scalar		0--359.99 [deg]
       
   187 #		PITCH						scalar		-20.00-20.00 [deg]
       
   188 #		ROLL						scalar		-20.00-20.00 [deg]
       
   189 #		SALINITY					scalar		0-40 [psu]
       
   190 #		TEMPERATURE					scalar		-5.00--40.00 [deg]
       
   191 #		MIN_PRE_PING_WAIT_TIME		scalar		? [s]
       
   192 #		HEADING_STDDEV				scalar		0-180 [deg]
       
   193 #		PITCH_STDDEV				scalar		0.0-20.0 [deg]
       
   194 #		ROLL_STDDEV					scalar		0.0-20.0 [deg]
       
   195 #		ADC_XMIT_CURRENT			scalar		0--255
       
   196 #		ADC_XMIT_VOLTAGE			scalar		0--255
       
   197 #		ADC_AMBIENT_TEMPERATURE		scalar		0--255
       
   198 #		ADC_PRESSURE_PLUS			scalar		0--255
       
   199 #		ADC_PRESSURE_MINUS			scalar		0--255
       
   200 #		ADC_ATTITUDE_TEMPERATURE	scalar		0--255
       
   201 #		ADC_ATTITUDE				scalar		0--255
       
   202 #		ADC_CONTAMINATION			scalar		0--255
       
   203 #		ERROR_STATUS_WORD			scalar		undefined, ? (only set if WH300)
       
   204 #		PRESSURE					scalar		undefined, ?-? [dbar] (only set if WH300)
       
   205 #		PRESSURE_STDDEV				scalar		undefined, ?-? [dbar] (only set if WH300)
       
   206 #		DATE						string		MM/DD/YYYY
       
   207 #		YEAR						scalar		?
       
   208 #		MONTH						scalar		1--12
       
   209 #		DAY							scalar		1--31
       
   210 #		TIME						string		HH:MM:SS.hh
       
   211 #		HOUR						scalar		0--23
       
   212 #		MINUTE						scalar		0--59
       
   213 #		SECONDS						scalar		0--59.99
       
   214 #		UNIX_TIME					scalar		0--?
       
   215 #		SECNO						scalar		0--? (number of seconds since daystart)
       
   216 #		DAYNO						double		fractional day number since start of current year (1.0 is midnight Jan 1st)
       
   217 #		VELOCITY[bin][beam]			scalars		-32.767--32.768 [m/s], undef=bad
       
   218 #		CORRELATION[bin][beam]		scalars		1--255, undefined=bad
       
   219 #		ECHO_AMPLITUDE[bin][beam]	scalars		0--255
       
   220 #		PERCENT_GOOD[bin][beam]		scalars		0--255
       
   221 #		BT_RANGE[beam]				scalars		tons [m]
       
   222 #		BT_VELOCITY[beam]			scalars		see VELOCITY
       
   223 #		BT_CORRELATION[beam]		scalars		see CORRELATION
       
   224 #		BT_EVAL_AMPLITUDE[beam]		scalars		0--255
       
   225 #		BT_PERCENT_GOOD[beam]		scalars		see PERCENT_GOOD
       
   226 #		BT_RL_VELOCITY[beam]		scalars		see VELOCITY
       
   227 #		BT_RL_CORRELATION[beam]		scalars		see CORRELATION
       
   228 #		BT_RL_ECHO_AMPLITUDE[beam]	scalars		see ECHO_AMPLITUDE
       
   229 #		BT_RL_PERCENT_GOOD[beam]	scalars		see PERCENT_GOOD
       
   230 #		BT_SIGNAL_STRENGTH[beam]	scalars		0--255
       
   231 #		HIGH_GAIN					bool		1, undefined
       
   232 #		LOW_GAIN					bool		1, undefined
       
   233 
       
   234 use strict;
       
   235 use Time::Local;						# timegm()
       
   236 
       
   237 #----------------------------------------------------------------------
       
   238 # Time Conversion Subroutines
       
   239 #----------------------------------------------------------------------
       
   240 
       
   241 sub monthLength($$)										# of days in month
       
   242 {
       
   243     my($Y,$M) = @_;
       
   244 
       
   245     return 31 if ($M==1 || $M==3 || $M==5 || $M==7 ||
       
   246                   $M==8 || $M==10 || $M==12);
       
   247     return 30 if ($M==4 || $M==6 || $M==9 || $M==11);
       
   248     return 28 if ($Y%4 != 0);
       
   249     return 29 if ($Y%100 != 0);
       
   250     return 28 if ($Y%400 > 0);
       
   251     return 29;
       
   252 }
       
   253 
       
   254 { my($epoch,$lM,$lD,$lY,$ldn);							# static scope
       
   255 
       
   256   sub dayNo($$$$$$)
       
   257   {
       
   258 	  my($Y,$M,$D,$h,$m,$s) = @_;
       
   259 	  my($dn);
       
   260   
       
   261 	  if ($Y==$lY && $M==$lM && $D==$lD) {				# same day as last samp
       
   262 		  $dn = $ldn;
       
   263 	  } else {											# new day
       
   264 		  $epoch = $Y unless defined($epoch);			# 1st call
       
   265 		  $lY = $Y; $lM = $M; $lD = $D;					# store
       
   266   
       
   267 		  for ($dn=0,my($cY)=$epoch; $cY<$Y; $cY++) {	# multiple years
       
   268 			  $dn += 337 + &monthLength($Y,$M);
       
   269 		  }
       
   270   
       
   271 		  $dn += $D;									# day in month
       
   272 		  while (--$M > 0) {							# preceding months
       
   273 			  $dn += &monthLength($Y,$M);
       
   274 		  }
       
   275 
       
   276 		  $ldn = $dn;									# store
       
   277 	  }
       
   278 	  return $dn + $h/24 + $m/24/60 + $s/24/3600;
       
   279   }
       
   280 
       
   281 } # static scope
       
   282 
       
   283 #----------------------------------------------------------------------
       
   284 # Read Data
       
   285 #----------------------------------------------------------------------
       
   286 
       
   287 my($WBRcfn,$WBPcfn);									# current file names for reading/patching
       
   288 my($BIT_errors) = 0;									# built-in-test errors
       
   289 
       
   290 my($FmtErr) = "%s: illegal %s Id 0x%04x at ensemble %d";
       
   291 
       
   292 #----------------------------------------------------------------------
       
   293 # readHeader(file_name,^dta) WBRhdr(^data)
       
   294 #	- read header data
       
   295 #	- also includes some data from 1st ens
       
   296 #----------------------------------------------------------------------
       
   297 
       
   298 sub readHeader(@)
       
   299 {
       
   300 	my($fn,$dta) = @_;
       
   301 	$WBRcfn = $fn;
       
   302     open(WBRF,$WBRcfn) || die("$WBRcfn: $!");
       
   303     WBRhdr($dta);    
       
   304 }
       
   305 
       
   306 sub WBRhdr($)
       
   307 {
       
   308 	my($dta) = @_;
       
   309 	my($buf,$hid,$did,$Ndt,$B,$W,$i,$dummy,$id,@WBRofs);
       
   310 	my($B1,$B2,$B3,$B4,$B5,$B6,$B7,$W1,$W2,$W3,$W4,$W5);
       
   311 	
       
   312 	#--------------------
       
   313 	# HEADER
       
   314 	#--------------------
       
   315 
       
   316 	sysread(WBRF,$buf,6) == 6 || die("$WBRcfn: $!");
       
   317 	($hid,$did,$dta->{ENSEMBLE_BYTES},$dummy,$dta->{NUMBER_OF_DATA_TYPES})
       
   318 		= unpack('CCvCC',$buf);
       
   319 	$hid == 0x7f || die(sprintf($FmtErr,$WBRcfn,"Header",$hid,0));
       
   320 	$did == 0x7f || die(sprintf($FmtErr,$WBRcfn,"Data Source",$did,0));
       
   321 	printf(STDERR "\n$WBRcfn: WARNING: unexpected number of data types (%d)\n",
       
   322 		$dta->{NUMBER_OF_DATA_TYPES})
       
   323 			unless ($dta->{NUMBER_OF_DATA_TYPES} == 6 ||
       
   324 					$dta->{NUMBER_OF_DATA_TYPES} == 7);
       
   325 	$dta->{BT_PRESENT} = ($dta->{NUMBER_OF_DATA_TYPES} == 7);
       
   326 					  
       
   327 	sysread(WBRF,$buf,2*$dta->{NUMBER_OF_DATA_TYPES})
       
   328 		== 2*$dta->{NUMBER_OF_DATA_TYPES}
       
   329 			|| die("$WBRcfn: $!");
       
   330 	@WBRofs = unpack("v$dta->{NUMBER_OF_DATA_TYPES}",$buf);
       
   331 
       
   332 	$dta->{HEADER_BYTES} 					= $WBRofs[0];
       
   333 	$dta->{FIXED_LEADER_BYTES} 				= $WBRofs[1] - $WBRofs[0];
       
   334 	$dta->{VARIABLE_LEADER_BYTES}			= $WBRofs[2] - $WBRofs[1];
       
   335 	$dta->{VELOCITY_DATA_BYTES}				= $WBRofs[3] - $WBRofs[2];
       
   336 	$dta->{CORRELATION_DATA_BYTES}			= $WBRofs[4] - $WBRofs[3];
       
   337 	$dta->{ECHO_INTENSITY_DATA_BYTES}		= $WBRofs[5] - $WBRofs[4];
       
   338 	if ($dta->{BT_PRESENT}) {
       
   339 		$dta->{PERCENT_GOOD_DATA_BYTES}		= $WBRofs[6] - $WBRofs[5];
       
   340 		$dta->{BT_DATA_BYTES}				= $dta->{ENSEMBLE_BYTES} - 4 - $WBRofs[6];
       
   341 	} else {
       
   342 		$dta->{PERCENT_GOOD_DATA_BYTES}		= $dta->{ENSEMBLE_BYTES} - 4 - $WBRofs[5];
       
   343 	}
       
   344 
       
   345 	if ($dta->{FIXED_LEADER_BYTES} == 42) {				# Eric Firing's old instrument I used in 2004
       
   346 		$dta->{INSTRUMENT_TYPE} = 'BB150';
       
   347 	} elsif ($dta->{FIXED_LEADER_BYTES} == 53) {		# old firmware: no serial numbers
       
   348 		$dta->{INSTRUMENT_TYPE} = 'Workhorse';	
       
   349 	} elsif ($dta->{FIXED_LEADER_BYTES} == 59) {		# new firmware: with serial numbers
       
   350 		$dta->{INSTRUMENT_TYPE} = 'Workhorse';
       
   351     } elsif ($dta->{FIXED_LEADER_BYTES} == 58) {		# DVL
       
   352 		$dta->{INSTRUMENT_TYPE} = 'Explorer';
       
   353     } 
       
   354 
       
   355 #	for ($i=0; $i<$dta->{NUMBER_OF_DATA_TYPES}; $i++) {
       
   356 #		printf(STDERR "\nWBRofs[$i] = %d",$WBRofs[$i]);
       
   357 #	}
       
   358 
       
   359 	#----------------------------------
       
   360 	# Check Data Format of 1st Ensemble
       
   361 	#----------------------------------
       
   362 
       
   363 	sysseek(WBRF,$WBRofs[1],0) || die("$WBRcfn: $!");
       
   364 	sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   365 	$id = unpack('v',$buf);
       
   366 	$id == 0x0080 || die(sprintf($FmtErr,$WBRcfn,"Variable Leader",$id,1));
       
   367 
       
   368 	sysseek(WBRF,$WBRofs[2],0) || die("$WBRcfn: $!");
       
   369 	sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   370 	$id = unpack('v',$buf);
       
   371 	$id == 0x0100 || die(sprintf($FmtErr,$WBRcfn,"Velocity Data",$id,1));
       
   372 
       
   373 	sysseek(WBRF,$WBRofs[3],0) || die("$WBRcfn: $!");
       
   374 	sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   375 	$id = unpack('v',$buf);
       
   376 	$id == 0x0200 || die(sprintf($FmtErr,$WBRcfn,"Correlation Data",$id,1));
       
   377     
       
   378 	sysseek(WBRF,$WBRofs[4],0) || die("$WBRcfn: $!");
       
   379 	sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   380 	$id = unpack('v',$buf);
       
   381 	$id == 0x0300 || die(sprintf($FmtErr,$WBRcfn,"Echo Intensity",$id,1));
       
   382 
       
   383 	sysseek(WBRF,$WBRofs[5],0) || die("$WBRcfn: $!");
       
   384 	sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   385 	$id = unpack('v',$buf);
       
   386 	$id == 0x0400 || die(sprintf($FmtErr,$WBRcfn,"Percent-Good Data",$id,1));
       
   387 
       
   388 	if ($dta->{BT_PRESENT}) {
       
   389 		sysseek(WBRF,$WBRofs[6],0) || die("$WBRcfn: $!");
       
   390 		sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   391 		$id = unpack('v',$buf);
       
   392 		$id == 0x0600 || die(sprintf($FmtErr,$WBRcfn,"Bottom Track",$id,1));
       
   393     }
       
   394 
       
   395 	#--------------------
       
   396 	# FIXED LEADER
       
   397 	#--------------------
       
   398 
       
   399 	sysseek(WBRF,$WBRofs[0],0) || die("$WBRcfn: $!");
       
   400 	sysread(WBRF,$buf,42) == 42 || die("$WBRcfn: $!");
       
   401 	($id,$dta->{CPU_FW_VER},$dta->{CPU_FW_REV},$B1,$B2,$dummy,$dummy,$dummy,
       
   402 	 $dta->{N_BINS},$dta->{PINGS_PER_ENSEMBLE},$dta->{BIN_LENGTH},
       
   403 	 $dta->{BLANKING_DISTANCE},$dummy,$dta->{MIN_CORRELATION},
       
   404 	 $dta->{N_CODE_REPETITIONS},$dta->{MIN_PERCENT_GOOD},
       
   405 	 $dta->{MAX_ERROR_VELOCITY},$dta->{TIME_BETWEEN_PINGS},$B3,$B4,$B5,
       
   406 	 $dta->{HEADING_ALIGNMENT},$dta->{HEADING_BIAS},$B6,$B7,
       
   407 	 $dta->{DISTANCE_TO_BIN1_CENTER},$dta->{TRANSMITTED_PULSE_LENGTH},
       
   408 	 $dta->{REF_LAYER_FIRST_BIN},$dta->{REF_LAYER_LAST_BIN},
       
   409 	 $dta->{FALSE_TARGET_THRESHOLD},$dta->{LOW_LATENCY_SETTING},
       
   410 	 $dta->{TRANSMIT_LAG_DISTANCE}) =
       
   411 		unpack('vCCCCC3CvvvCCCCvCCCCvvCCvvCCCCv',$buf);
       
   412 
       
   413 	$id == 0x0000 || die(sprintf($FmtErr,$WBRcfn,"Fixed Leader",$id,0));
       
   414 
       
   415     $dta->{BEAM_FREQUENCY} = 2**($B1 & 0x07) * 75;
       
   416     $dta->{CONVEX_BEAM_PATTERN} = 1 if ($B1 & 0x08);
       
   417     $dta->{CONCAVE_BEAM_PATTERN} = 1 if (!($B1 & 0x08));
       
   418     $dta->{SENSOR_CONFIG} = ($B1 & 0x30) >> 4;
       
   419     $dta->{XDUCER_HEAD_ATTACHED} = 1 if ($B1 & 0x40);
       
   420 
       
   421 	if	  (($B2 & 0x03) == 0x00) { $dta->{BEAM_ANGLE} = 15; }
       
   422 	elsif (($B2 & 0x03) == 0x01) { $dta->{BEAM_ANGLE} = 20; }
       
   423 	elsif (($B2 & 0x03) == 0x02) { $dta->{BEAM_ANGLE} = 30; }
       
   424 	if	  (($B2 & 0xF0) == 0x40) { $dta->{N_BEAMS} = 4; }
       
   425 	elsif (($B2 & 0xF0) == 0x50) { $dta->{N_BEAMS} = 5; $dta->{N_DEMODS} = 3; }
       
   426     elsif (($B2 & 0xF0) == 0xF0) { $dta->{N_BEAMS} = 5; $dta->{N_DEMODS} = 2; }
       
   427     
       
   428     $dta->{BIN_LENGTH} /= 100;
       
   429     $dta->{BLANKING_DISTANCE} /= 100;
       
   430 
       
   431     $dta->{MAX_ERROR_VELOCITY} /= 1000;
       
   432     $dta->{TIME_BETWEEN_PINGS} *= 60;
       
   433 	$dta->{TIME_BETWEEN_PINGS} += $B3 + $B4/100;
       
   434 
       
   435 	$dta->{BEAM_COORDINATES}		  = 1 if (($B5 & 0x18) == 0x00);
       
   436 	$dta->{INSTRUMENT_COORDINATES}	  = 1 if (($B5 & 0x18) == 0x08);
       
   437 	$dta->{SHIP_COORDINATES}		  = 1 if (($B5 & 0x18) == 0x10);
       
   438 	$dta->{EARTH_COORDINATES}		  = 1 if (($B5 & 0x18) == 0x18);
       
   439 	$dta->{PITCH_AND_ROLL_USED} 	  = 1 if ($B5 & 0x04);
       
   440 	$dta->{USE_3_BEAM_ON_LOW_CORR}	  = 1 if ($B5 & 0x02);
       
   441     $dta->{BIN_MAPPING_ALLOWED}       = 1 if ($B5 & 0x01);
       
   442         
       
   443 	$dta->{HEADING_ALIGNMENT} =
       
   444 		($dta->{EARTH_COORDINATES} || $dta->{SHIP_COORDINATES}) ?
       
   445 			$dta->{HEADING_ALIGNMENT} / 100 : undef;
       
   446 	$dta->{HEADING_BIAS} =
       
   447 		($dta->{EARTH_COORDINATES} || $dta->{SHIP_COORDINATES}) ?
       
   448 			$dta->{HEADING_BIAS} / 100 : undef;
       
   449 
       
   450 	$dta->{CALCULATE_SPEED_OF_SOUND}  = 1 if ($B6 & 0x40); 
       
   451 	$dta->{USE_PRESSURE_SENSOR} 	  = 1 if ($B6 & 0x20); 
       
   452 	$dta->{USE_COMPASS} 			  = 1 if ($B6 & 0x10); 
       
   453 	$dta->{USE_PITCH_SENSOR}		  = 1 if ($B6 & 0x08); 
       
   454 	$dta->{USE_ROLL_SENSOR} 		  = 1 if ($B6 & 0x04); 
       
   455 	$dta->{USE_CONDUCTIVITY_SENSOR}   = 1 if ($B6 & 0x02); 
       
   456     $dta->{USE_TEMPERATURE_SENSOR}    = 1 if ($B6 & 0x01); 
       
   457 
       
   458 	$dta->{SPEED_OF_SOUND_CALCULATED}	  = 1 if ($B7 & 0x40); 
       
   459 	$dta->{PRESSURE_SENSOR_AVAILABLE}	  = 1 if ($B7 & 0x20); 
       
   460 	$dta->{COMPASS_AVAILABLE}			  = 1 if ($B7 & 0x10); 
       
   461 	$dta->{PITCH_SENSOR_AVAILABLE}		  = 1 if ($B7 & 0x08); 
       
   462 	$dta->{ROLL_SENSOR_AVAILABLE}		  = 1 if ($B7 & 0x04); 
       
   463 	$dta->{CONDUCTIVITY_SENSOR_AVAILABLE} = 1 if ($B7 & 0x02); 
       
   464     $dta->{TEMPERATURE_SENSOR_AVAILABLE}  = 1 if ($B7 & 0x01); 
       
   465 
       
   466     $dta->{DISTANCE_TO_BIN1_CENTER}  /= 100;
       
   467     $dta->{TRANSMITTED_PULSE_LENGTH} /= 100;
       
   468 
       
   469     $dta->{FALSE_TARGET_THRESHOLD} = undef
       
   470 		if ($dta->{FALSE_TARGET_THRESHOLD} == 255);
       
   471     $dta->{TRANSMIT_LAG_DISTANCE} /= 100;
       
   472 
       
   473 	if ($dta->{INSTRUMENT_TYPE} eq 'Workhorse') {
       
   474 		sysread(WBRF,$buf,11) == 11 || die("$WBRcfn: $!");
       
   475 		($W1,$W2,$W3,$W4,$W5,$dta->{TRANSMIT_POWER}) = 
       
   476 			unpack('vvvvvC',$buf);
       
   477 
       
   478 		$dta->{CPU_SERIAL_NUMBER} = sprintf("%04X%04X%04X%04X",$W1,$W2,$W3,$W4);
       
   479 	
       
   480 		$dta->{NARROW_BANDWIDTH} = ($W5 == 1);
       
   481 		$dta->{WIDE_BANDWIDTH}	 = ($W5 == 0);
       
   482 	    $dta->{TRANSMIT_POWER_HIGH} = ($dta->{TRANSMIT_POWER} == 255);
       
   483 
       
   484 		if ($dta->{FIXED_LEADER_BYTES} == 59) {					# new style with serial number
       
   485 			sysread(WBRF,$buf,6) == 6 || die("$WBRcfn: $!");
       
   486 			($dummy,$dta->{SERIAL_NUMBER},$dummy) =				# last bytes is beam angle, but that info has
       
   487 				unpack('CVC',$buf);								# already been provided above
       
   488 		}
       
   489     }
       
   490 
       
   491 	if ($dta->{INSTRUMENT_TYPE} eq 'Explorer') {
       
   492 		sysread(WBRF,$buf,16) == 16 || die("$WBRcfn: $!");
       
   493 		($dummy,$dummy,$W5,$dummy,$dta->{SERIAL_NUMBER}) = 
       
   494 			unpack('VVvvV',$buf);
       
   495 		$dta->{NARROW_BANDWIDTH} = ($W5 == 1);
       
   496 		$dta->{WIDE_BANDWIDTH}	 = ($W5 == 0);
       
   497     }
       
   498 
       
   499 	#-----------------------
       
   500 	# 1st ENSEMBLE, BT Setup
       
   501 	#-----------------------
       
   502 
       
   503 	if ($dta->{BT_PRESENT}) {
       
   504 		sysseek(WBRF,$WBRofs[6],0) || die("$WBRcfn: $!");
       
   505 		sysread(WBRF,$buf,12) == 12 || die("$WBRcfn: $!");
       
   506 		($id,$dta->{BT_PINGS_PER_ENSEMBLE},$dta->{BT_DELAY_BEFORE_REACQUIRE},
       
   507 		 $dta->{BT_MIN_CORRELATION},$dta->{BT_MIN_EVAL_AMPLITUDE},
       
   508 		 $dta->{BT_MIN_PERCENT_GOOD},$dta->{BT_MODE},
       
   509 		 $dta->{BT_MAX_ERROR_VELOCITY}) = unpack('vvvCCCCv',$buf);
       
   510 		 
       
   511 		$id == 0x0600 ||
       
   512 			die(sprintf($FmtErr,$WBRcfn,"Bottom Track",$id,0,tell(WBRF)));
       
   513 	
       
   514 		$dta->{BT_MAX_ERROR_VELOCITY} =
       
   515 			$dta->{BT_MAX_ERROR_VELOCITY} ? $dta->{BT_MAX_ERROR_VELOCITY} / 1000
       
   516 										  : undef;
       
   517 	
       
   518 		sysseek(WBRF,28,1) || die("$WBRcfn: $!");
       
   519 		sysread(WBRF,$buf,6) == 6 || die("$WBRcfn: $!");
       
   520 		($dta->{BT_RL_MIN_SIZE},$dta->{BT_RL_NEAR},$dta->{BT_RL_FAR})
       
   521 			= unpack('vvv',$buf);
       
   522 	
       
   523 		$dta->{BT_RL_MIN_SIZE} /= 10;
       
   524 		$dta->{BT_RL_NEAR} /= 10;
       
   525 		$dta->{BT_RL_FAR} /= 10;
       
   526 	    
       
   527 		sysseek(WBRF,20,1) || die("$WBRcfn: $!");		# skip data
       
   528 		sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   529 	    $dta->{BT_MAX_TRACKING_DEPTH} = unpack('v',$buf) / 10;
       
   530     }
       
   531     
       
   532     return $dta;
       
   533 }
       
   534 
       
   535 #----------------------------------------------------------------------
       
   536 # readData(file_name,^data) WBRens(nbins,fixed_leader_bytes,^data)
       
   537 # 	- read all ensembles
       
   538 #----------------------------------------------------------------------
       
   539 
       
   540 sub readData(@)
       
   541 {
       
   542 	my($fn,$dta) = @_;
       
   543 	$WBRcfn = $fn;
       
   544     open(WBRF,$WBRcfn) || die("$WBRcfn: $!");
       
   545     WBRhdr($dta);
       
   546 	WBRens($dta->{N_BINS},$dta->{FIXED_LEADER_BYTES},
       
   547 		   \@{$dta->{ENSEMBLE}});
       
   548 	print(STDERR "$WBRcfn: $BIT_errors built-in-test errors\n")
       
   549 		if ($BIT_errors);
       
   550 }
       
   551 
       
   552 sub WBRens($$$)
       
   553 {
       
   554 	my($nbins,$fixed_leader_bytes,$E) = @_;
       
   555 	my($start_ens,$B1,$B2,$B3,$B4,$I,$id,$bin,$beam,$buf,$dummy,@dta,$i,$cs,@WBRofs);
       
   556 	my($ens,$ensNo,$dayStart,$ens_length,$BT_present,$hid,$did,$ndt);
       
   557 
       
   558 	for ($ens=$start_ens=0; 1; $ens++,$start_ens+=$ens_length+2) {
       
   559 #		print(STDERR "ens = $ens\n");
       
   560 #		print(STDERR "start_ens = $start_ens\n");
       
   561 
       
   562 		#----------------------------------------
       
   563 		# Get ensemble length and # of data types 
       
   564 		#----------------------------------------
       
   565 
       
   566 		sysseek(WBRF,$start_ens,0) || die("$WBRcfn: $!");
       
   567 		sysread(WBRF,$buf,6) == 6 || last;
       
   568 		($hid,$did,$ens_length,$dummy,$ndt) = unpack('CCvCC',$buf);
       
   569 		$hid == 0x7f || die(sprintf($FmtErr,$WBRcfn,"Header",$hid,0));
       
   570 		$did == 0x7f || die(sprintf($FmtErr,$WBRcfn,"Data Source",$did,0));
       
   571 		printf(STDERR "\n$WBRcfn: WARNING: unexpected number of data types (%d, ens=$ens)\n",$ndt),last
       
   572 				unless ($ndt == 6 || $ndt == 7);
       
   573 		$BT_present = ($ndt == 7);
       
   574 		sysread(WBRF,$buf,2*$ndt) == 2*$ndt || die("$WBRcfn: $!");
       
   575 		@WBRofs = unpack("v$ndt",$buf);
       
   576 		$fixed_leader_bytes = $WBRofs[1] - $WBRofs[0];
       
   577 #		print(STDERR "@WBRofs\n");
       
   578 	
       
   579 		#-------------------------------
       
   580 		# Make Sure Ensemble is Complete
       
   581 		#-------------------------------
       
   582 
       
   583 		# UH BB150 writes incomplete ensembles (i.e. short read
       
   584 		# indicates EOF). FSU WH300 has bogus data in incomplete
       
   585 		# final ensemble.
       
   586 
       
   587 		sysseek(WBRF,$start_ens,0) || die("$WBRcfn: $!");
       
   588 		sysread(WBRF,$buf,$ens_length) == $ens_length || last;
       
   589 
       
   590 		sysread(WBRF,$cs,2) == 2 || last;
       
   591 		last unless (unpack('%16C*',$buf) == unpack('v',$cs));
       
   592 
       
   593 		#------------------------------
       
   594 		# Variable Leader
       
   595 		#------------------------------
       
   596 	
       
   597 		sysseek(WBRF,$start_ens+$WBRofs[1],0) || die("$WBRcfn: $!");
       
   598 		sysread(WBRF,$buf,4) == 4 || die("$WBRcfn: $!");
       
   599 		($id,$ensNo) = unpack("vv",$buf);
       
   600 
       
   601 		$id == 0x0080 ||
       
   602 			die(sprintf($FmtErr,$WBRcfn,"Variable Leader",$id,$ensNo+1));
       
   603 
       
   604 		if ($fixed_leader_bytes==42 || $fixed_leader_bytes==58) {			# BB150 & Explorer DVL
       
   605 			sysread(WBRF,$buf,7) == 7 || die("$WBRcfn: $!");
       
   606 			(${$E}[$ens]->{YEAR},${$E}[$ens]->{MONTH},
       
   607 			 ${$E}[$ens]->{DAY},${$E}[$ens]->{HOUR},${$E}[$ens]->{MINUTE},
       
   608 			 ${$E}[$ens]->{SECONDS},$B4) = unpack('CCCCCCC',$buf);
       
   609 			${$E}[$ens]->{SECONDS} += $B4/100;
       
   610 			${$E}[$ens]->{YEAR} += (${$E}[$ens]->{YEAR} > 80) ? 1900 : 2000;
       
   611 		} else {
       
   612 			sysseek(WBRF,7,1) || die("$WBRcfn: $!");							# use Y2K RTC instead
       
   613 		}
       
   614 
       
   615 		sysread(WBRF,$buf,1) == 1 || die("$WBRcfn: $!");
       
   616 		$ensNo += unpack('C',$buf) << 16;
       
   617 		${$E}[$ens]->{NUMBER} = $ensNo;
       
   618 		
       
   619 		sysread(WBRF,$buf,30) == 30 || die("$WBRcfn: $!");
       
   620 		(${$E}[$ens]->{BUILT_IN_TEST_ERROR},${$E}[$ens]->{SPEED_OF_SOUND},
       
   621 		 ${$E}[$ens]->{XDUCER_DEPTH},${$E}[$ens]->{HEADING},
       
   622 		 ${$E}[$ens]->{PITCH},${$E}[$ens]->{ROLL},
       
   623 		 ${$E}[$ens]->{SALINITY},${$E}[$ens]->{TEMPERATURE},
       
   624 		 ${$E}[$ens]->{MIN_PRE_PING_WAIT_TIME},$B1,$B2,
       
   625 		 ${$E}[$ens]->{HEADING_STDDEV},${$E}[$ens]->{PITCH_STDDEV},
       
   626 		 ${$E}[$ens]->{ROLL_STDDEV},${$E}[$ens]->{ADC_XMIT_CURRENT},
       
   627 		 ${$E}[$ens]->{ADC_XMIT_VOLTAGE},${$E}[$ens]->{ADC_AMBIENT_TEMPERATURE},
       
   628 		 ${$E}[$ens]->{ADC_PRESSURE_PLUS},${$E}[$ens]->{ADC_PRESSURE_MINUS},
       
   629 		 ${$E}[$ens]->{ADC_ATTITUDE_TEMPERATURE},${$E}[$ens]->{ADC_ATTITUDE},
       
   630 		 ${$E}[$ens]->{ADC_CONTAMINATION})
       
   631 			= unpack('vvvvvvvvCCCCCCCCCCCCCC',$buf);
       
   632 
       
   633 		${$E}[$ens]->{BUILT_IN_TEST_ERROR} = undef
       
   634 			unless (${$E}[$ens]->{BUILT_IN_TEST_ERROR});
       
   635 		$BIT_errors++ if (${$E}[$ens]->{BUILT_IN_TEST_ERROR});
       
   636 
       
   637 		${$E}[$ens]->{XDUCER_DEPTH} /= 10;
       
   638 		${$E}[$ens]->{HEADING} /= 100;
       
   639 		${$E}[$ens]->{PITCH} = unpack('s',pack('S',${$E}[$ens]->{PITCH})) / 100;
       
   640 		${$E}[$ens]->{ROLL}  = unpack('s',pack('S',${$E}[$ens]->{ROLL})) / 100;
       
   641 		${$E}[$ens]->{TEMPERATURE} =
       
   642 			unpack('s',pack('S',${$E}[$ens]->{TEMPERATURE})) / 100;
       
   643 		${$E}[$ens]->{MIN_PRE_PING_WAIT_TIME} *= 60;
       
   644 		${$E}[$ens]->{MIN_PRE_PING_WAIT_TIME} += $B1 + $B2/100;
       
   645 		${$E}[$ens]->{PITCH_STDDEV} /= 10;
       
   646 		${$E}[$ens]->{ROLL_STDDEV} /= 10;
       
   647 
       
   648 		if ($fixed_leader_bytes==53 || $fixed_leader_bytes==59) {			# Workhorse instruments
       
   649 			sysread(WBRF,$buf,23) == 23 || die("$WBRcfn: $!");
       
   650 			(${$E}[$ens]->{ERROR_STATUS_WORD},
       
   651 		 	 $dummy,${$E}[$ens]->{PRESSURE},${$E}[$ens]->{PRESSURE_STDDEV},
       
   652 			 $dummy,${$E}[$ens]->{YEAR},$B3,${$E}[$ens]->{MONTH},
       
   653 			 ${$E}[$ens]->{DAY},${$E}[$ens]->{HOUR},${$E}[$ens]->{MINUTE},
       
   654 			 ${$E}[$ens]->{SECONDS},$B4)
       
   655 				= unpack('VvVVCCCCCCCCC',$buf);
       
   656 
       
   657 			${$E}[$ens]->{PRESSURE} /= 1000;
       
   658 			${$E}[$ens]->{PRESSURE_STDDEV} /= 1000;
       
   659 			${$E}[$ens]->{YEAR} *= 100; ${$E}[$ens]->{YEAR} += $B3;
       
   660 			${$E}[$ens]->{SECONDS} += $B4/100;
       
   661 		}
       
   662 
       
   663 		if ($fixed_leader_bytes == 58) {									# Explorer DVL
       
   664 			sysread(WBRF,$buf,14) == 14 || die("$WBRcfn: $!");
       
   665 			(${$E}[$ens]->{ERROR_STATUS_WORD},
       
   666 		 	 $dummy,${$E}[$ens]->{PRESSURE},${$E}[$ens]->{PRESSURE_STDDEV})
       
   667 				= unpack('VvVV',$buf);
       
   668 			${$E}[$ens]->{PRESSURE} /= 1000;
       
   669 			${$E}[$ens]->{PRESSURE_STDDEV} /= 1000;
       
   670 		}
       
   671 		
       
   672 		${$E}[$ens]->{DATE}
       
   673 			= sprintf("%02d/%02d/%d",${$E}[$ens]->{MONTH},
       
   674 									 ${$E}[$ens]->{DAY},
       
   675 									 ${$E}[$ens]->{YEAR});
       
   676 		${$E}[$ens]->{TIME}
       
   677 			= sprintf("%02d:%02d:%05.02f",${$E}[$ens]->{HOUR},
       
   678 										  ${$E}[$ens]->{MINUTE},
       
   679 									 	  ${$E}[$ens]->{SECONDS});
       
   680 		${$E}[$ens]->{DAYNO}
       
   681 			= &dayNo(${$E}[$ens]->{YEAR},${$E}[$ens]->{MONTH},${$E}[$ens]->{DAY},
       
   682 					 ${$E}[$ens]->{HOUR},${$E}[$ens]->{MINUTE},${$E}[$ens]->{SECONDS});
       
   683 
       
   684 		# when analyzing an STA file from an OS75 SADCP (Poseidion),
       
   685 		# I noticed that there is no time information. This causes
       
   686 		# timegm to bomb. 
       
   687 		if (${$E}[$ens]->{MONTH} == 0) {					# no time info
       
   688 			${$E}[$ens]->{UNIX_TIME} = 0;
       
   689 			${$E}[$ens]->{SECNO} = 0;
       
   690         } else {
       
   691 			${$E}[$ens]->{UNIX_TIME}
       
   692 				= timegm(0,${$E}[$ens]->{MINUTE},
       
   693 						   ${$E}[$ens]->{HOUR},
       
   694 						   ${$E}[$ens]->{DAY},
       
   695 						   ${$E}[$ens]->{MONTH}-1,			# timegm jan==0!!!
       
   696 						   ${$E}[$ens]->{YEAR})
       
   697 				  + ${$E}[$ens]->{SECONDS};
       
   698 	
       
   699 			$dayStart = timegm(0,0,0,${$E}[$ens]->{DAY},
       
   700 									 ${$E}[$ens]->{MONTH}-1,
       
   701 									 ${$E}[$ens]->{YEAR})
       
   702 				unless defined($dayStart);
       
   703 	        ${$E}[$ens]->{SECNO} = ${$E}[$ens]->{UNIX_TIME} - $dayStart;
       
   704         }
       
   705 
       
   706 		sysseek(WBRF,$start_ens+$WBRofs[0]+4,0)		# System Config / Fixed Leader
       
   707 			|| die("$WBRcfn: $!");
       
   708 
       
   709 		sysread(WBRF,$buf,5) == 5 || die("$WBRcfn: $!");
       
   710 		($B1,$dummy,$dummy,$dummy,${$E}[$ens]->{N_BEAMS_USED})
       
   711 			= unpack('CCCCC',$buf);		
       
   712 		${$E}[$ens]->{XDUCER_FACING_UP}   = 1 if     ($B1 & 0x80);
       
   713 		${$E}[$ens]->{XDUCER_FACING_DOWN} = 1 unless ($B1 & 0x80);
       
   714 
       
   715 		#--------------------
       
   716 		# Velocity Data
       
   717 		#--------------------
       
   718 
       
   719 		my($ndata) = $nbins * 4;
       
   720 
       
   721 		sysseek(WBRF,$start_ens+$WBRofs[2],0) || die("$WBRcfn: $!");
       
   722 		sysread(WBRF,$buf,2+$ndata*2) == 2+$ndata*2 || die("$WBRcfn: $!");
       
   723 		($id,@dta) = unpack("vv$ndata",$buf);
       
   724 
       
   725 		$id == 0x0100 ||
       
   726 			die(sprintf($FmtErr,$WBRcfn,"Velocity Data",$id,$ens));
       
   727 		
       
   728 		for ($i=0,$bin=0; $bin<$nbins; $bin++) {
       
   729 			for ($beam=0; $beam<4; $beam++,$i++) {
       
   730 				${$E}[$ens]->{VELOCITY}[$bin][$beam] =
       
   731 					unpack('s',pack('S',$dta[$i])) / 1000
       
   732 						if ($dta[$i] != 0x8000);
       
   733 			}
       
   734 		}
       
   735 
       
   736 		#--------------------
       
   737 		# Correlation Data
       
   738 		#--------------------
       
   739 
       
   740 		sysseek(WBRF,$start_ens+$WBRofs[3],0) || die("$WBRcfn: $!");
       
   741 		sysread(WBRF,$buf,2+$ndata) == 2+$ndata || die("$WBRcfn: $!");
       
   742 		($id,@dta) = unpack("vC$ndata",$buf);
       
   743 
       
   744 		$id == 0x0200 ||
       
   745 			die(sprintf($FmtErr,$WBRcfn,"Correlation Data",$id,$ens));
       
   746 		
       
   747 		for ($i=0,$bin=0; $bin<$nbins; $bin++) {
       
   748 			for ($beam=0; $beam<4; $beam++,$i++) {
       
   749 				${$E}[$ens]->{CORRELATION}[$bin][$beam] = $dta[$i]
       
   750 					if ($dta[$i]);
       
   751 			}
       
   752 		}
       
   753 
       
   754 		#--------------------
       
   755 		# Echo Intensity Data
       
   756 		#--------------------
       
   757 
       
   758 		sysseek(WBRF,$start_ens+$WBRofs[4],0) || die("$WBRcfn: $!");
       
   759 		sysread(WBRF,$buf,2+$ndata) == 2+$ndata || die("$WBRcfn: $!");
       
   760 		($id,@dta) = unpack("vC$ndata",$buf);
       
   761 
       
   762 		$id == 0x0300 ||
       
   763 			die(sprintf($FmtErr,$WBRcfn,"Echo Intensity",$id,$ens));
       
   764 
       
   765 		for ($i=0,$bin=0; $bin<$nbins; $bin++) {
       
   766 			for ($beam=0; $beam<4; $beam++,$i++) {
       
   767 				${$E}[$ens]->{ECHO_AMPLITUDE}[$bin][$beam] = $dta[$i];
       
   768 			}
       
   769 		}
       
   770 
       
   771 		#--------------------
       
   772 		# Percent Good Data
       
   773 		#--------------------
       
   774 
       
   775 		sysseek(WBRF,$start_ens+$WBRofs[5],0) || die("$WBRcfn: $!");
       
   776 		sysread(WBRF,$buf,2+$ndata) == 2+$ndata || die("$WBRcfn: $!");
       
   777 		($id,@dta) = unpack("vC$ndata",$buf);
       
   778 
       
   779 		$id == 0x0400 ||
       
   780 			die(sprintf($FmtErr,$WBRcfn,"Percent-Good Data",$id,$ens));
       
   781 
       
   782 		for ($i=0,$bin=0; $bin<$nbins; $bin++) {
       
   783 			for ($beam=0; $beam<4; $beam++,$i++) {
       
   784 				${$E}[$ens]->{PERCENT_GOOD}[$bin][$beam] = $dta[$i];
       
   785 			}
       
   786 		}
       
   787 
       
   788 		#--------------------
       
   789 		# Bottom-Track Data
       
   790 		#--------------------
       
   791 
       
   792 		if ($BT_present) {
       
   793 			sysseek(WBRF,$start_ens+$WBRofs[6],0) || die("$WBRcfn: $!");
       
   794 			sysread(WBRF,$buf,2) == 2 || die("$WBRcfn: $!");
       
   795 			$id = unpack('v',$buf);
       
   796 	
       
   797 			$id == 0x0600 ||
       
   798 				die(sprintf($FmtErr,$WBRcfn,"Bottom Track",$id,$ens));
       
   799 	
       
   800 			sysseek(WBRF,14,1) || die("$WBRcfn: $!");		# BT config
       
   801 	
       
   802 			sysread(WBRF,$buf,28) == 28 || die("$WBRcfn: $!");
       
   803 			@dta = unpack('v4v4C4C4C4',$buf);
       
   804 		    
       
   805 			for ($beam=0; $beam<4; $beam++) {
       
   806 				${$E}[$ens]->{BT_RANGE}[$beam] = $dta[$beam] / 100
       
   807 						if ($dta[$beam]);
       
   808 			}
       
   809 			for ($beam=0; $beam<4; $beam++) {
       
   810 				${$E}[$ens]->{BT_VELOCITY}[$beam] =
       
   811 					unpack('s',pack('S',$dta[4+$beam])) / 1000
       
   812 						if ($dta[4+$beam] != 0x8000);
       
   813 			}
       
   814 			for ($beam=0; $beam<4; $beam++) {
       
   815 				${$E}[$ens]->{BT_CORRELATION}[$beam] = $dta[8+$beam]
       
   816 					if ($dta[8+$beam]);
       
   817 			}
       
   818 			for ($beam=0; $beam<4; $beam++) {
       
   819 				${$E}[$ens]->{BT_EVAL_AMPLITUDE}[$beam] = $dta[12+$beam];
       
   820 			}
       
   821 			for ($beam=0; $beam<4; $beam++) {
       
   822 				${$E}[$ens]->{BT_PERCENT_GOOD}[$beam] = $dta[16+$beam];
       
   823 			}
       
   824 	
       
   825 			sysseek(WBRF,6,1) || die("$WBRcfn: $!");		# BT config
       
   826 	
       
   827 			sysread(WBRF,$buf,20) == 20 || die("$WBRcfn: $!");
       
   828 			@dta = unpack('v4C4C4C4',$buf);
       
   829 	
       
   830 			for ($beam=0; $beam<4; $beam++) {
       
   831 				${$E}[$ens]->{BT_RL_VELOCITY}[$beam] =
       
   832 					unpack('s',pack('S',$dta[$beam])) / 1000
       
   833 						if ($dta[$beam] != 0x8000);
       
   834 			}
       
   835 			for ($beam=0; $beam<4; $beam++) {
       
   836 				${$E}[$ens]->{BT_RL_CORRELATION}[$beam] = $dta[4+$beam]
       
   837 					if ($dta[4+$beam]);
       
   838 			}
       
   839 			for ($beam=0; $beam<4; $beam++) {
       
   840 				${$E}[$ens]->{BT_RL_ECHO_AMPLITUDE}[$beam] = $dta[8+$beam];
       
   841 			}
       
   842 			for ($beam=0; $beam<4; $beam++) {
       
   843 				${$E}[$ens]->{BT_RL_PERCENT_GOOD}[$beam] = $dta[12+$beam];
       
   844 			}
       
   845 	
       
   846 			sysseek(WBRF,2,1) || die("$WBRcfn: $!");		# BT config
       
   847 	
       
   848 			sysread(WBRF,$buf,9) == 9 || die("$WBRcfn: $!");
       
   849 			@dta = unpack('C4CC4',$buf);
       
   850 	
       
   851 			for ($beam=0; $beam<4; $beam++) {
       
   852 				${$E}[$ens]->{BT_SIGNAL_STRENGTH}[$beam] = $dta[$beam];
       
   853 			}
       
   854 			${$E}[$ens]->{HIGH_GAIN} if	   ($dta[4]);
       
   855 			${$E}[$ens]->{LOW_GAIN}	unless ($dta[4]);
       
   856 			for ($beam=0; $beam<4; $beam++) {
       
   857 				${$E}[$ens]->{BT_RANGE}[$beam] += $dta[5+$beam] * 655.36
       
   858 					if ($dta[5+$beam]);
       
   859 	        }
       
   860 	    } # BT present
       
   861 	} # ens loop
       
   862 }
       
   863 
       
   864 #----------------------------------------------------------------------
       
   865 # WBPens(nbins,fixed_leader_bytes,^data)
       
   866 # 	- patch PD0 file with new data
       
   867 #	- file must already exist and have correct structure
       
   868 #	- currently only does part of variable leader data, esp. attitude
       
   869 #----------------------------------------------------------------------
       
   870 
       
   871 sub writeData(@)
       
   872 {
       
   873 	my($fn,$dta) = @_;
       
   874 
       
   875 	die("writeData() needs \$WBRcfn from previous readData()")
       
   876 		unless (-r $WBRcfn);
       
   877 	$WBPcfn = $fn;
       
   878 	system("cp $WBRcfn $WBPcfn");
       
   879 
       
   880 	open(WBPF,"+<$WBPcfn") || die("$WBPcfn: $!");
       
   881     WBPens($dta->{N_BINS},$dta->{FIXED_LEADER_BYTES},
       
   882 	                   \@{$dta->{ENSEMBLE}});
       
   883 }
       
   884 
       
   885 sub WBPens($$$)
       
   886 {
       
   887 	my($nbins,$fixed_leader_bytes,$E) = @_;
       
   888 	my($start_ens,$B1,$B2,$B3,$B4,$I,$id,$bin,$beam,$buf,$dummy,@dta,$i,$cs,@WBPofs);
       
   889 	my($ens,$ensNo,$dayStart,$ens_length,$BT_present,$hid,$did,$ndt);
       
   890 
       
   891 	for ($ens=$start_ens=0; 1; $ens++,$start_ens+=$ens_length+2) {
       
   892 
       
   893 		#----------------------------------------
       
   894 		# Get ensemble length and # of data types 
       
   895 		#----------------------------------------
       
   896 
       
   897 		sysseek(WBPF,$start_ens,0) || die("$WBPcfn: $!");
       
   898 		sysread(WBPF,$buf,6) == 6 || last;
       
   899 		($hid,$did,$ens_length,$dummy,$ndt) = unpack('CCvCC',$buf);
       
   900 		$hid == 0x7f || die(sprintf($FmtErr,$WBPcfn,"Header",$hid,0));
       
   901 		$did == 0x7f || die(sprintf($FmtErr,$WBPcfn,"Data Source",$did,0));
       
   902 		printf(STDERR "\n$WBPcfn: WARNING: unexpected number of data types (%d, ens=$ens)\n",$ndt),last
       
   903 				unless ($ndt == 6 || $ndt == 7);
       
   904 		$BT_present = ($ndt == 7);
       
   905 		sysread(WBPF,$buf,2*$ndt) == 2*$ndt || die("$WBPcfn: $!");
       
   906 		@WBPofs = unpack("v$ndt",$buf);
       
   907 		$fixed_leader_bytes = $WBPofs[1] - $WBPofs[0];
       
   908 	
       
   909 		#------------------------------
       
   910 		# Variable Leader
       
   911 		#------------------------------
       
   912 	
       
   913 		sysseek(WBPF,$start_ens+$WBPofs[1]+12,0) || die("$WBPcfn: $!");
       
   914 		
       
   915 		${$E}[$ens]->{XDUCER_DEPTH} *= 10;
       
   916 		${$E}[$ens]->{HEADING} *= 100;
       
   917 		
       
   918 		${$E}[$ens]->{PITCH} = unpack('S',pack('s',${$E}[$ens]->{PITCH}*100));
       
   919 		${$E}[$ens]->{ROLL}  = unpack('S',pack('s',${$E}[$ens]->{ROLL} *100));
       
   920 		${$E}[$ens]->{TEMPERATURE} =
       
   921 			unpack('S',pack('s',${$E}[$ens]->{TEMPERATURE}*100));
       
   922 
       
   923 		sysseek(WBPF,2,1);			# skip built-in test which reads as 0 but is usually undef		
       
   924 									# this was found not to matter, but there is no reason to edit
       
   925 #		my($b1);					# this field
       
   926 #		sysread(WBPF,$b1,14);
       
   927 #		sysseek(WBPF,-14,1);
       
   928 #		my($sos,$xd,$hdg,$pit,$rol,$sal,$tem) = unpack('vvvvvvv',$b1);
       
   929 
       
   930 		$buf = pack('vvvvvvv',
       
   931 			 ${$E}[$ens]->{SPEED_OF_SOUND},
       
   932 			 ${$E}[$ens]->{XDUCER_DEPTH},${$E}[$ens]->{HEADING},
       
   933 			 ${$E}[$ens]->{PITCH},${$E}[$ens]->{ROLL},
       
   934 			 ${$E}[$ens]->{SALINITY},${$E}[$ens]->{TEMPERATURE});
       
   935 
       
   936 #		unless ($b1 eq $buf) {
       
   937 #			printf(STDERR "ens = $ens\n");
       
   938 #			printf(STDERR "hdg: $hdg, ${$E}[$ens]->{HEADING}\n");
       
   939 #			printf(STDERR "xd: $xd, ${$E}[$ens]->{XDUCER_DEPTH}\n");
       
   940 #			printf(STDERR "pit: $pit, ${$E}[$ens]->{PITCH}\n");
       
   941 #			printf(STDERR "rol: $rol, ${$E}[$ens]->{ROLL}\n");
       
   942 #			printf(STDERR "sal: $sal, ${$E}[$ens]->{SALINITY}\n");
       
   943 #			printf(STDERR "tem: $tem, ${$E}[$ens]->{TEMPERATURE}\n");
       
   944 #
       
   945 #			printf(STDERR "read: %04X %04X %04X %04X %04X %04X %04X written: %04X %04X %04X %04X %04X %04X %04X\n",unpack('v7',$b1),unpack('v7',$buf));
       
   946 #
       
   947 #			die;
       
   948 #		}
       
   949 
       
   950 		my($nw) = syswrite(WBPF,$buf,14);
       
   951 		$nw == 14 || die("$WBPcfn: $nw bytes written ($!)");
       
   952 
       
   953 		#----------------
       
   954 		# Update Checksum
       
   955 		#----------------
       
   956 
       
   957 		sysseek(WBPF,$start_ens,0) || die("$WBPcfn: $!");
       
   958 		sysread(WBPF,$buf,$ens_length) == $ens_length || die("$WBPcfn: $!");
       
   959 		$cs = unpack('%16C*',$buf);
       
   960 		$buf = pack('v',$cs);
       
   961 		$nw = syswrite(WBPF,$buf,2);
       
   962 		$nw == 2 || die("$WBPcfn: $nw bytes written, ens=$ens ($!)");
       
   963 
       
   964 	} # ens loop
       
   965 }
       
   966 
       
   967 1;      # return true for all the world to see