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