text: """
  @package mi.dataset.parser.adcpa
  @file marine-integrations/mi/dataset/parser/adcpa.py
  @author Christopher Wingard, using Teledyne ADCP instrument particles developed
      by Roger Unwin.
  @brief Dataset code for the Coastal Slocum Adcpa mounted Teledyne ExplorerDVL
      (ADCPA) particles
  
  Release notes:
      There are small differences between the PD0 formatted data files for the
      Teledyne RDI Workhorse series of ADCPs and the ExplorerDVL. The set of
      particles defined herein, will only work with the ExplorerDVL data files
      downloaded from TWR Coastal Slocum Adcpa. If the planned for AUVs also use
      the RDI ExplorerDVL, then these particles should apply to that instrument
      as well.
  
      Note, all binary data produced by the ExplorerDVL (and all other Teledyne
      RDI ADCPs) is little-endian.
  """
  __author__ = 'Christopher Wingard'
  __license__ = 'Apache 2.0'
  
  import copy
  import datetime as dt
  import re
  from calendar import timegm
  from functools import partial
  from struct import unpack
  
  from mi.core.log import get_logger
  from mi.core.common import BaseEnum
  from mi.core.exceptions import SampleException, DatasetParserException
  from mi.core.instrument.chunker import StringChunker
  from mi.core.instrument.data_particle import DataParticle, DataParticleKey
  from mi.dataset.dataset_parser import BufferLoadingParser
  
  # start the logger
  log = get_logger()
  
  # Regex set to find each ensemble in the PD0 packet. It starts by specifying the
  # first 6 bytes of the header data (this is more explicit than just the 0x7f7f
  # marker the manual specifies, helping to avoid cases where the marker could
  # actually be in the data string, thus giving a false positive data record
  # marker), followed by the remaining N bytes in the ensemble. It stops, without
  # consuming bytes, at the beginning of the next ensemble (again using the first
  # 6 bytes of the header) or the end of the file. This dynamic regex is required
  # since the ensemble sizes will vary during the course of a deployment as
  # bottom-tracking is automatically turned on and off. This also allows the
  # glider pilots the flexibility to change the sampling characteristics of the
  # ADCPA without impacting the parser code.
  ADCPA_PD0_PARSED_REGEX = (
      b'(\x7f\x7f[\x00-\xFF]{2}\x00[\x06|\x07]{1}[\x00-\xFF]+?)' +  # header bytes plus rest of ensemble
      '(?=(\x7f\x7f[\x00-\xFF]{2}\x00[\x06|\x07]{1})|\Z)'  # start of the next ensemble, or EOF
  )
  ADCPA_PD0_PARSED_MATCHER = re.compile(ADCPA_PD0_PARSED_REGEX, re.DOTALL)
  
  
  ###############################################################################
  # Data Particles
  ###############################################################################
  class StateKey(BaseEnum):
      POSITION = "position"
  
  
  class DataParticleType(BaseEnum):
      CGLDR_ADCPA_PD0_PARSED = 'cgldr_adcpa_pd0_parsed'
  
  
  class ADCPA_PD0_PARSED_KEY(BaseEnum):
      """
      Data particles for the Teledyne RDI ExplorerDVL PD0 formatted data files
      """
      # Header Data
      HEADER_ID = 'header_id'
      DATA_SOURCE_ID = 'data_source_id'
      NUM_BYTES = 'num_bytes'
      NUM_DATA_TYPES = 'num_data_types'
      OFFSET_DATA_TYPES = 'offset_data_types'
  
      # Fixed Leader Data
      FIXED_LEADER_ID = 'fixed_leader_id'
      FIRMWARE_VERSION = 'firmware_version'
      FIRMWARE_REVISION = 'firmware_revision'
      SYSCONFIG_FREQUENCY = 'sysconfig_frequency'
      SYSCONFIG_BEAM_PATTERN = 'sysconfig_beam_pattern'
      SYSCONFIG_SENSOR_CONFIG = 'sysconfig_sensor_config'
      SYSCONFIG_HEAD_ATTACHED = 'sysconfig_head_attached'
      SYSCONFIG_VERTICAL_ORIENTATION = 'sysconfig_vertical_orientation'
      DATA_FLAG = 'data_flag'
      LAG_LENGTH = 'lag_length'
      NUM_BEAMS = 'num_beams'
      NUM_CELLS = 'num_cells'
      PINGS_PER_ENSEMBLE = 'pings_per_ensemble'
      DEPTH_CELL_LENGTH = 'depth_cell_length'
      BLANK_AFTER_TRANSMIT = 'blank_after_transmit'
      SIGNAL_PROCESSING_MODE = 'signal_processing_mode'
      LOW_CORR_THRESHOLD = 'low_corr_threshold'
      NUM_CODE_REPETITIONS = 'num_code_repetitions'
      PERCENT_GOOD_MIN = 'percent_good_min'
      ERROR_VEL_THRESHOLD = 'error_vel_threshold'
      TIME_PER_PING_MINUTES = 'time_per_ping_minutes'
      TIME_PER_PING_SECONDS = 'time_per_ping_seconds'
      COORD_TRANSFORM_TYPE = 'coord_transform_type'
      COORD_TRANSFORM_TILTS = 'coord_transform_tilts'
      COORD_TRANSFORM_BEAMS = 'coord_transform_beams'
      COORD_TRANSFORM_MAPPING = 'coord_transform_mapping'
      HEADING_ALIGNMENT = 'heading_alignment'
      HEADING_BIAS = 'heading_bias'
      SENSOR_SOURCE_SPEED = 'sensor_source_speed'
      SENSOR_SOURCE_DEPTH = 'sensor_source_depth'
      SENSOR_SOURCE_HEADING = 'sensor_source_heading'
      SENSOR_SOURCE_PITCH = 'sensor_source_pitch'
      SENSOR_SOURCE_ROLL = 'sensor_source_roll'
      SENSOR_SOURCE_CONDUCTIVITY = 'sensor_source_conductivity'
      SENSOR_SOURCE_TEMPERATURE = 'sensor_source_temperature'
      SENSOR_AVAILABLE_DEPTH = 'sensor_available_depth'
      SENSOR_AVAILABLE_HEADING = 'sensor_available_heading'
      SENSOR_AVAILABLE_PITCH = 'sensor_available_pitch'
      SENSOR_AVAILABLE_ROLL = 'sensor_available_roll'
      SENSOR_AVAILABLE_CONDUCTIVITY = 'sensor_available_conductivity'
      SENSOR_AVAILABLE_TEMPERATURE = 'sensor_available_temperature'
      BIN_1_DISTANCE = 'bin_1_distance'
      TRANSMIT_PULSE_LENGTH = 'transmit_pulse_length'
      REFERENCE_LAYER_START = 'reference_layer_start'
      REFERENCE_LAYER_STOP = 'reference_layer_stop'
      FALSE_TARGET_THRESHOLD = 'false_target_threshold'
      LOW_LATENCY_TRIGGER = 'low_latency_trigger'
      TRANSMIT_LAG_DISTANCE = 'transmit_lag_distance'
      SYSTEM_BANDWIDTH = 'system_bandwidth'
      SERIAL_NUMBER = 'serial_number'
  
      # Variable Leader Data
      VARIABLE_LEADER_ID = 'variable_leader_id'
      ENSEMBLE_NUMBER = 'ensemble_number'
      ENSEMBLE_NUMBER_INCREMENT = 'ensemble_number_increment'
      REAL_TIME_CLOCK = 'real_time_clock'
      ENSEMBLE_START_TIME = 'ensemble_start_time'
      BIT_RESULT_DEMOD_1 = 'bit_result_demod_1'
      BIT_RESULT_DEMOD_2 = 'bit_result_demod_2'
      BIT_RESULT_TIMING = 'bit_result_timing'
      SPEED_OF_SOUND = 'speed_of_sound'
      TRANSDUCER_DEPTH = 'transducer_depth'
      HEADING = 'heading'
      PITCH = 'pitch'
      ROLL = 'roll'
      SALINITY = 'salinity'
      TEMPERATURE = 'temperature'
      MPT_MINUTES = 'mpt_minutes'
      MPT_SECONDS = 'mpt_seconds'
      HEADING_STDEV = 'heading_stdev'
      PITCH_STDEV = 'pitch_stdev'
      ROLL_STDEV = 'roll_stdev'
      ADC_TRANSMIT_CURRENT = 'adc_transmit_current'
      ADC_TRANSMIT_VOLTAGE = 'adc_transmit_voltage'
      ADC_AMBIENT_TEMP = 'adc_ambient_temp'
      ADC_PRESSURE_PLUS = 'adc_pressure_plus'
      ADC_PRESSURE_MINUS = 'adc_pressure_minus'
      ADC_ATTITUDE_TEMP = 'adc_attitude_temp'
      ADC_ATTITUDE = 'adc_attitude'
      ADC_CONTAMINATION_SENSOR = 'adc_contamination_sensor'
      BUS_ERROR_EXCEPTION = 'bus_error_exception'
      ADDRESS_ERROR_EXCEPTION = 'address_error_exception'
      ILLEGAL_INSTRUCTION_EXCEPTION = 'illegal_instruction_exception'
      ZERO_DIVIDE_INSTRUCTION = 'zero_divide_instruction'
      EMULATOR_EXCEPTION = 'emulator_exception'
      UNASSIGNED_EXCEPTION = 'unassigned_exception'
      WATCHDOG_RESTART_OCCURRED = 'watchdog_restart_occurred'
      BATTERY_SAVER_POWER = 'battery_saver_power'
      PINGING = 'pinging'
      COLD_WAKEUP_OCCURRED = 'cold_wakeup_occurred'
      UNKNOWN_WAKEUP_OCCURRED = 'unknown_wakeup_occurred'
      CLOCK_READ_ERROR = 'clock_read_error'
      UNEXPECTED_ALARM = 'unexpected_alarm'
      CLOCK_JUMP_FORWARD = 'clock_jump_forward'
      CLOCK_JUMP_BACKWARD = 'clock_jump_backward'
      POWER_FAIL = 'power_fail'
      SPURIOUS_DSP_INTERRUPT = 'spurious_dsp_interrupt'
      SPURIOUS_UART_INTERRUPT = 'spurious_uart_interrupt'
      SPURIOUS_CLOCK_INTERRUPT = 'spurious_clock_interrupt'
      LEVEL_7_INTERRUPT = 'level_7_interrupt'
      PRESSURE = 'pressure'
      PRESSURE_VARIANCE = 'pressure_variance'
  
      # Velocity Data
      VELOCITY_DATA_ID = 'velocity_data_id'
      WATER_VELOCITY_EAST = 'water_velocity_east'
      WATER_VELOCITY_NORTH = 'water_velocity_north'
      WATER_VELOCITY_UP = 'water_velocity_up'
      ERROR_VELOCITY = 'error_velocity'
  
      # Correlation Magnitude Data
      CORRELATION_MAGNITUDE_ID = 'correlation_magnitude_id'
      CORRELATION_MAGNITUDE_BEAM1 = 'correlation_magnitude_beam1'
      CORRELATION_MAGNITUDE_BEAM2 = 'correlation_magnitude_beam2'
      CORRELATION_MAGNITUDE_BEAM3 = 'correlation_magnitude_beam3'
      CORRELATION_MAGNITUDE_BEAM4 = 'correlation_magnitude_beam4'
  
      # Echo Intensity Data
      ECHO_INTENSITY_ID = 'echo_intensity_id'
      ECHO_INTENSITY_BEAM1 = 'echo_intensity_beam1'
      ECHO_INTENSITY_BEAM2 = 'echo_intensity_beam2'
      ECHO_INTENSITY_BEAM3 = 'echo_intensity_beam3'
      ECHO_INTENSITY_BEAM4 = 'echo_intensity_beam4'
  
      # Percent Good Data
      PERCENT_GOOD_ID = 'percent_good_id'
      PERCENT_GOOD_3BEAM = 'percent_good_3beam'
      PERCENT_TRANSFORMS_REJECT = 'percent_transforms_reject'
      PERCENT_BAD_BEAMS = 'percent_bad_beams'
      PERCENT_GOOD_4BEAM = 'percent_good_4beam'
  
      # Bottom Track Data (only produced if Adcpa is in less than 65 m of water)
      BOTTOM_TRACK_ID = 'bottom_track_id'
      BT_PINGS_PER_ENSEMBLE = 'bt_pings_per_ensemble'
      BT_DELAY_BEFORE_REACQUIRE = 'bt_delay_before_reacquire'
      BT_CORR_MAGNITUDE_MIN = 'bt_corr_magnitude_min'
      BT_EVAL_MAGNITUDE_MIN = 'bt_eval_magnitude_min'
      BT_PERCENT_GOOD_MIN = 'bt_percent_good_min'
      BT_MODE = 'bt_mode'
      BT_ERROR_VELOCITY_MAX = 'bt_error_velocity_max'
      BEAM1_BT_RANGE_LSB = 'beam1_bt_range_lsb'
      BEAM2_BT_RANGE_LSB = 'beam2_bt_range_lsb'
      BEAM3_BT_RANGE_LSB = 'beam3_bt_range_lsb'
      BEAM4_BT_RANGE_LSB = 'beam4_bt_range_lsb'
      EASTWARD_BT_VELOCITY = 'eastward_bt_velocity'
      NORTHWARD_BT_VELOCITY = 'northward_bt_velocity'
      UPWARD_BT_VELOCITY = 'upward_bt_velocity'
      ERROR_BT_VELOCITY = 'error_bt_velocity'
      BEAM1_BT_CORRELATION = 'beam1_bt_correlation'
      BEAM2_BT_CORRELATION = 'beam2_bt_correlation'
      BEAM3_BT_CORRELATION = 'beam3_bt_correlation'
      BEAM4_BT_CORRELATION = 'beam4_bt_correlation'
      BEAM1_EVAL_AMP = 'beam1_eval_amp'
      BEAM2_EVAL_AMP = 'beam2_eval_amp'
      BEAM3_EVAL_AMP = 'beam3_eval_amp'
      BEAM4_EVAL_AMP = 'beam4_eval_amp'
      BEAM1_BT_PERCENT_GOOD = 'beam1_bt_percent_good'
      BEAM2_BT_PERCENT_GOOD = 'beam2_bt_percent_good'
      BEAM3_BT_PERCENT_GOOD = 'beam3_bt_percent_good'
      BEAM4_BT_PERCENT_GOOD = 'beam4_bt_percent_good'
      REF_LAYER_MIN = 'ref_layer_min'
      REF_LAYER_NEAR = 'ref_layer_near'
      REF_LAYER_FAR = 'ref_layer_far'
      BEAM1_REF_LAYER_VELOCITY = 'beam1_ref_layer_velocity'
      BEAM2_REF_LAYER_VELOCITY = 'beam2_ref_layer_velocity'
      BEAM3_REF_LAYER_VELOCITY = 'beam3_ref_layer_velocity'
      BEAM4_REF_LAYER_VELOCITY = 'beam4_ref_layer_velocity'
      BEAM1_REF_CORRELATION = 'beam1_ref_correlation'
      BEAM2_REF_CORRELATION = 'beam2_ref_correlation'
      BEAM3_REF_CORRELATION = 'beam3_ref_correlation'
      BEAM4_REF_CORRELATION = 'beam4_ref_correlation'
      BEAM1_REF_INTENSITY = 'beam1_ref_intensity'
      BEAM2_REF_INTENSITY = 'beam2_ref_intensity'
      BEAM3_REF_INTENSITY = 'beam3_ref_intensity'
      BEAM4_REF_INTENSITY = 'beam4_ref_intensity'
      BEAM1_REF_PERCENT_GOOD = 'beam1_ref_percent_good'
      BEAM2_REF_PERCENT_GOOD = 'beam2_ref_percent_good'
      BEAM3_REF_PERCENT_GOOD = 'beam3_ref_percent_good'
      BEAM4_REF_PERCENT_GOOD = 'beam4_ref_percent_good'
      BT_MAX_DEPTH = 'bt_max_depth'
      BEAM1_RSSI_AMPLITUDE = 'beam1_rssi_amplitude'
      BEAM2_RSSI_AMPLITUDE = 'beam2_rssi_amplitude'
      BEAM3_RSSI_AMPLITUDE = 'beam3_rssi_amplitude'
      BEAM4_RSSI_AMPLITUDE = 'beam4_rssi_amplitude'
      BT_GAIN = 'bt_gain'
      BEAM1_BT_RANGE_MSB = 'beam1_bt_range_msb'
      BEAM2_BT_RANGE_MSB = 'beam2_bt_range_msb'
      BEAM3_BT_RANGE_MSB = 'beam3_bt_range_msb'
      BEAM4_BT_RANGE_MSB = 'beam4_bt_range_msb'
  
      # Ensemble checksum
      CHECKSUM = 'checksum'
  
  
  class ADCPA_PD0_PARSED_DataParticle(DataParticle):
      _data_particle_type = DataParticleType.CGLDR_ADCPA_PD0_PARSED
  
      def _build_parsed_values(self):
          """
          Parse the beginning portion of the ensemble (Header Data)
          """
          self.final_result = []
  
          length = unpack("<H", self.raw_data[2:4])[0]
          data = str(self.raw_data)
  
          # Calculate the checksum
          total = int(0)
          for i in range(0, length):
              total += int(ord(data[i]))
  
          checksum = total & 65535    # bitwise and with 65535 or mod vs 65536
  
          if checksum != unpack("<H", self.raw_data[length: length+2])[0]:
              log.debug("Checksum mismatch " + str(checksum) + " != "
                        + str(unpack("<H", self.raw_data[length: length+2])[0]))
              raise SampleException("Checksum mismatch")
  
          # save the checksum and process the remainder of the ensemble
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CHECKSUM,
                                    DataParticleKey.VALUE: checksum})
  
          (header_id, data_source_id, num_bytes, spare, num_data_types) = \
              unpack('<BBHBB', self.raw_data[0:6])
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.HEADER_ID,
                                    DataParticleKey.VALUE: header_id})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.DATA_SOURCE_ID,
                                    DataParticleKey.VALUE: data_source_id})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.NUM_BYTES,
                                    DataParticleKey.VALUE: num_bytes})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.NUM_DATA_TYPES,
                                    DataParticleKey.VALUE: num_data_types})
  
          offsets = []    # create list for offsets
          strt = 6        # offsets start at byte 6 (using 0 indexing)
          nDT = 1         # counter for n data types
          while nDT <= num_data_types:
              value = unpack('<H', self.raw_data[strt:strt+2])[0]
              offsets.append(value)
              strt += 2
              nDT += 1
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.OFFSET_DATA_TYPES,
                                    DataParticleKey.VALUE: offsets})
  
          for offset in offsets:
              # for each offset, using the starting byte, determine the data type
              # and then parse accordingly.
              data_type = unpack('<H', self.raw_data[offset:offset+2])[0]
  
              # fixed leader data (x00x00)
              if data_type == 0:
                  chunk = self.raw_data[offset:offset+58]
                  self.parse_fixed_chunk(chunk)
                  iCells = self.num_depth_cells   # grab the # of depth cells
                                                  # obtained from the fixed leader
                                                  # data type
  
              # variable leader data (x80x00)
              if data_type == 128:
                  chunk = self.raw_data[offset:offset+60]
                  self.parse_variable_chunk(chunk)
  
              # velocity data (x00x01)
              if data_type == 256:
                  # number of bytes is a function of the user selectable number of
                  # depth cells (WN command), calculated above
                  nBytes = 2 + 8 * iCells
                  chunk = self.raw_data[offset:offset+nBytes]
                  self.parse_velocity_chunk(chunk)
  
              # correlation magnitude data (x00x02)
              if data_type == 512:
                  # number of bytes is a function of the user selectable number of
                  # depth cells (WN command), calculated above
                  nBytes = 2 + 4 * iCells
                  chunk = self.raw_data[offset:offset+nBytes]
                  self.parse_corelation_magnitude_chunk(chunk)
  
              # echo intensity data (x00x03)
              if data_type == 768:
                  # number of bytes is a function of the user selectable number of
                  # depth cells (WN command), calculated above
                  nBytes = 2 + 4 * iCells
                  chunk = self.raw_data[offset:offset+nBytes]
                  self.parse_echo_intensity_chunk(chunk)
  
              # percent-good data (x00x04)
              if data_type == 1024:
                  # number of bytes is a function of the user selectable number of
                  # depth cells (WN command), calculated above
                  nBytes = 2 + 4 * iCells
                  chunk = self.raw_data[offset:offset+nBytes]
                  self.parse_percent_good_chunk(chunk)
  
              # bottom track data (x00x06)
              if data_type == 1536:
                  chunk = self.raw_data[offset:offset+81]
                  self.parse_bottom_track_chunk(chunk)
  
          return self.final_result
  
      def parse_fixed_chunk(self, chunk):
          """
          Parse the fixed leader portion of the particle
  
          @throws SampleException If there is a problem with sample creation
          """
          (fixed_leader_id, firmware_version, firmware_revision,
           sysconfig_frequency, data_flag, lag_length, num_beams, num_cells,
           pings_per_ensemble, depth_cell_length, blank_after_transmit,
           signal_processing_mode, low_corr_threshold, num_code_repetitions,
           percent_good_min, error_vel_threshold, time_per_ping_minutes,
           time_per_ping_seconds, time_per_ping_hundredths, coord_transform_type,
           heading_alignment, heading_bias, sensor_source, sensor_available,
           bin_1_distance, transmit_pulse_length, reference_layer_start,
           reference_layer_stop, false_target_threshold, SPARE1,
           transmit_lag_distance, SPARE2, system_bandwidth,
           SPARE3, SPARE4, serial_number) = \
              unpack('<HBBHBBBBHHHBBBBHBBBBhhBBHHBBBBHQHBBI', chunk)
  
          if 0 != fixed_leader_id:
              raise SampleException("fixed_leader_id was not equal to 0")
  
          # store the number of depth cells for use elsewhere
          self.num_depth_cells = num_cells
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.FIXED_LEADER_ID,
                                    DataParticleKey.VALUE: fixed_leader_id})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.FIRMWARE_VERSION,
                                    DataParticleKey.VALUE: firmware_version})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.FIRMWARE_REVISION,
                                    DataParticleKey.VALUE: firmware_revision})
  
          frequencies = [75, 150, 300, 600, 1200, 2400]
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SYSCONFIG_FREQUENCY,
                                    DataParticleKey.VALUE: frequencies[sysconfig_frequency & 0b00000111]})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SYSCONFIG_BEAM_PATTERN,
                                    DataParticleKey.VALUE: 1 if sysconfig_frequency & 0b00001000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SYSCONFIG_SENSOR_CONFIG,
                                    DataParticleKey.VALUE: sysconfig_frequency & 0b00110000 >> 4})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SYSCONFIG_HEAD_ATTACHED,
                                    DataParticleKey.VALUE: 1 if sysconfig_frequency & 0b01000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SYSCONFIG_VERTICAL_ORIENTATION,
                                    DataParticleKey.VALUE: 1 if sysconfig_frequency & 0b10000000 else 0})
  
          if 0 != data_flag:
              raise SampleException("data_flag was not equal to 0")
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.DATA_FLAG,
                                    DataParticleKey.VALUE: data_flag})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.LAG_LENGTH,
                                    DataParticleKey.VALUE: lag_length})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.NUM_BEAMS,
                                    DataParticleKey.VALUE: num_beams})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.NUM_CELLS,
                                    DataParticleKey.VALUE: num_cells})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PINGS_PER_ENSEMBLE,
                                    DataParticleKey.VALUE: pings_per_ensemble})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.DEPTH_CELL_LENGTH,
                                    DataParticleKey.VALUE: depth_cell_length})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BLANK_AFTER_TRANSMIT,
                                    DataParticleKey.VALUE: blank_after_transmit})
  
          if 1 != signal_processing_mode:
              raise SampleException("signal_processing_mode was not equal to 1")
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SIGNAL_PROCESSING_MODE,
                                    DataParticleKey.VALUE: signal_processing_mode})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.LOW_CORR_THRESHOLD,
                                    DataParticleKey.VALUE: low_corr_threshold})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.NUM_CODE_REPETITIONS,
                                    DataParticleKey.VALUE: num_code_repetitions})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PERCENT_GOOD_MIN,
                                    DataParticleKey.VALUE: percent_good_min})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ERROR_VEL_THRESHOLD,
                                    DataParticleKey.VALUE: error_vel_threshold})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.TIME_PER_PING_MINUTES,
                                    DataParticleKey.VALUE: time_per_ping_minutes})
  
          tpp_float_seconds = float(time_per_ping_seconds + (time_per_ping_hundredths/100))
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.TIME_PER_PING_SECONDS,
                                    DataParticleKey.VALUE: tpp_float_seconds})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.COORD_TRANSFORM_TYPE,
                                    DataParticleKey.VALUE: coord_transform_type & 0b00011000 >> 3})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.COORD_TRANSFORM_TILTS,
                                    DataParticleKey.VALUE: 1 if coord_transform_type & 0b00000100 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.COORD_TRANSFORM_BEAMS,
                                    DataParticleKey.VALUE: 1 if coord_transform_type & 0b0000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.COORD_TRANSFORM_MAPPING,
                                    DataParticleKey.VALUE: 1 if coord_transform_type & 0b00000001 else 0})
  
          # lame, but expedient - mask off un-needed bits
          self.coord_transform_type = (coord_transform_type & 0b00011000) >> 3
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.HEADING_ALIGNMENT,
                                    DataParticleKey.VALUE: heading_alignment})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.HEADING_BIAS,
                                    DataParticleKey.VALUE: heading_bias})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_SOURCE_SPEED,
                                    DataParticleKey.VALUE: 1 if sensor_source & 0b01000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_SOURCE_DEPTH,
                                    DataParticleKey.VALUE: 1 if sensor_source & 0b00100000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_SOURCE_HEADING,
                                    DataParticleKey.VALUE: 1 if sensor_source & 0b00010000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_SOURCE_PITCH,
                                    DataParticleKey.VALUE: 1 if sensor_source & 0b00001000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_SOURCE_ROLL,
                                    DataParticleKey.VALUE: 1 if sensor_source & 0b00000100 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_SOURCE_CONDUCTIVITY,
                                    DataParticleKey.VALUE: 1 if sensor_source & 0b00000010 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_SOURCE_TEMPERATURE,
                                    DataParticleKey.VALUE: 1 if sensor_source & 0b00000001 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_AVAILABLE_DEPTH,
                                    DataParticleKey.VALUE: 1 if sensor_available & 0b00100000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_AVAILABLE_HEADING,
                                    DataParticleKey.VALUE: 1 if sensor_available & 0b00010000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_AVAILABLE_PITCH,
                                    DataParticleKey.VALUE: 1 if sensor_available & 0b00001000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_AVAILABLE_ROLL,
                                    DataParticleKey.VALUE: 1 if sensor_available & 0b00000100 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_AVAILABLE_CONDUCTIVITY,
                                    DataParticleKey.VALUE: 1 if sensor_available & 0b00000010 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SENSOR_AVAILABLE_TEMPERATURE,
                                    DataParticleKey.VALUE: 1 if sensor_available & 0b00000001 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BIN_1_DISTANCE,
                                    DataParticleKey.VALUE: bin_1_distance})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.TRANSMIT_PULSE_LENGTH,
                                    DataParticleKey.VALUE: transmit_pulse_length})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.REFERENCE_LAYER_START,
                                    DataParticleKey.VALUE: reference_layer_start})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.REFERENCE_LAYER_STOP,
                                    DataParticleKey.VALUE: reference_layer_stop})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.FALSE_TARGET_THRESHOLD,
                                    DataParticleKey.VALUE: false_target_threshold})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.TRANSMIT_LAG_DISTANCE,
                                    DataParticleKey.VALUE: transmit_lag_distance})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SYSTEM_BANDWIDTH,
                                    DataParticleKey.VALUE: system_bandwidth})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SERIAL_NUMBER,
                                    DataParticleKey.VALUE: serial_number})
  
      def parse_variable_chunk(self, chunk):
          """
          Parse the variable leader portion of the particle
  
          @throws SampleException If there is a problem with sample creation
          """
          rtc = {}
          (variable_leader_id, ensemble_number, rtc['year'], rtc['month'],
           rtc['day'], rtc['hour'], rtc['minute'], rtc['second'],
           rtc['hundredths'], ensemble_number_increment, error_bit_field,
           reserved_error_bit_field, speed_of_sound, transducer_depth, heading,
           pitch, roll, salinity, temperature, mpt_minutes, mpt_seconds_component,
           mpt_hundredths_component, heading_stdev, pitch_stdev, roll_stdev,
           adc_transmit_current, adc_transmit_voltage, adc_ambient_temp,
           adc_pressure_plus, adc_pressure_minus, adc_attitude_temp,
           adc_attitiude, adc_contamination_sensor, error_status_word_1,
           error_status_word_2, error_status_word_3, error_status_word_4,
           SPARE1, pressure, pressure_variance, SPARE2) = \
              unpack('<HHBBBBBBBBBBHHHhhHhBBBBBBBBBBBBBBBBBBHIII', chunk)
  
          if 128 != variable_leader_id:
              raise SampleException("variable_leader_id was not equal to 128")
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.VARIABLE_LEADER_ID,
                                    DataParticleKey.VALUE: variable_leader_id})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ENSEMBLE_NUMBER,
                                    DataParticleKey.VALUE: ensemble_number})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ENSEMBLE_NUMBER_INCREMENT,
                                    DataParticleKey.VALUE: ensemble_number_increment})
  
          # convert individual date and time values to datetime object and
          # calculate the NTP timestamp (seconds since Jan 1, 1900), per OOI
          # convention
          dts = dt.datetime(2000 + rtc['year'], rtc['month'], rtc['day'],
                            rtc['hour'], rtc['minute'], rtc['second'])
          epoch_ts = timegm(dts.timetuple()) + (rtc['hundredths'] / 100.0)  # seconds since 1970-01-01 in UTC
          ntp_ts = epoch_ts + 2208988800
          self.set_internal_timestamp(ntp_ts)
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.REAL_TIME_CLOCK,
                                   DataParticleKey.VALUE: [rtc['year'], rtc['month'], rtc['day'],
                                                           rtc['hour'], rtc['minute'], rtc['second'], rtc['hundredths']]})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ENSEMBLE_START_TIME,
                                   DataParticleKey.VALUE: epoch_ts})
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BIT_RESULT_DEMOD_1,
                                    DataParticleKey.VALUE: 1 if error_bit_field & 0b00001000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BIT_RESULT_DEMOD_2,
                                    DataParticleKey.VALUE: 1 if error_bit_field & 0b00010000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BIT_RESULT_TIMING,
                                    DataParticleKey.VALUE: 1 if error_bit_field & 0b00000010 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SPEED_OF_SOUND,
                                    DataParticleKey.VALUE: speed_of_sound})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.TRANSDUCER_DEPTH,
                                    DataParticleKey.VALUE: transducer_depth})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.HEADING,
                                    DataParticleKey.VALUE: heading})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PITCH,
                                    DataParticleKey.VALUE: pitch})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ROLL,
                                    DataParticleKey.VALUE: roll})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SALINITY,
                                    DataParticleKey.VALUE: salinity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.TEMPERATURE,
                                    DataParticleKey.VALUE: temperature})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.MPT_MINUTES,
                                    DataParticleKey.VALUE: mpt_minutes})
  
          mpt_seconds = float(mpt_seconds_component + (mpt_hundredths_component/100))
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.MPT_SECONDS,
                                    DataParticleKey.VALUE: mpt_seconds})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.HEADING_STDEV,
                                    DataParticleKey.VALUE: heading_stdev})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PITCH_STDEV,
                                    DataParticleKey.VALUE: pitch_stdev})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ROLL_STDEV,
                                    DataParticleKey.VALUE: roll_stdev})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_TRANSMIT_CURRENT,
                                    DataParticleKey.VALUE: adc_transmit_current})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_TRANSMIT_VOLTAGE,
                                    DataParticleKey.VALUE: adc_transmit_voltage})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_AMBIENT_TEMP,
                                    DataParticleKey.VALUE: adc_ambient_temp})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_PRESSURE_PLUS,
                                    DataParticleKey.VALUE: adc_pressure_plus})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_PRESSURE_MINUS,
                                    DataParticleKey.VALUE: adc_pressure_minus})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_ATTITUDE_TEMP,
                                    DataParticleKey.VALUE: adc_attitude_temp})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_ATTITUDE,
                                    DataParticleKey.VALUE: adc_attitiude})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADC_CONTAMINATION_SENSOR,
                                    DataParticleKey.VALUE: adc_contamination_sensor})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BUS_ERROR_EXCEPTION,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b00000001 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ADDRESS_ERROR_EXCEPTION,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b00000010 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ILLEGAL_INSTRUCTION_EXCEPTION,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b00000100 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ZERO_DIVIDE_INSTRUCTION,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b00001000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.EMULATOR_EXCEPTION,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b00010000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.UNASSIGNED_EXCEPTION,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b00100000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.WATCHDOG_RESTART_OCCURRED,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b01000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BATTERY_SAVER_POWER,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b10000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PINGING,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b00000001 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.COLD_WAKEUP_OCCURRED,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b01000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.UNKNOWN_WAKEUP_OCCURRED,
                                    DataParticleKey.VALUE: 1 if error_status_word_1 & 0b10000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CLOCK_READ_ERROR,
                                    DataParticleKey.VALUE: 1 if error_status_word_3 & 0b00000001 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.UNEXPECTED_ALARM,
                                    DataParticleKey.VALUE: 1 if error_status_word_3 & 0b00000010 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CLOCK_JUMP_FORWARD,
                                    DataParticleKey.VALUE: 1 if error_status_word_3 & 0b00000100 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CLOCK_JUMP_BACKWARD,
                                    DataParticleKey.VALUE: 1 if error_status_word_3 & 0b00001000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.POWER_FAIL,
                                    DataParticleKey.VALUE: 1 if error_status_word_4 & 0b00001000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SPURIOUS_DSP_INTERRUPT,
                                    DataParticleKey.VALUE: 1 if error_status_word_4 & 0b00010000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SPURIOUS_UART_INTERRUPT,
                                    DataParticleKey.VALUE: 1 if error_status_word_4 & 0b00100000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.SPURIOUS_CLOCK_INTERRUPT,
                                    DataParticleKey.VALUE: 1 if error_status_word_4 & 0b01000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.LEVEL_7_INTERRUPT,
                                    DataParticleKey.VALUE: 1 if error_status_word_4 & 0b10000000 else 0})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PRESSURE,
                                    DataParticleKey.VALUE: pressure})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PRESSURE_VARIANCE,
                                    DataParticleKey.VALUE: pressure_variance})
  
      def parse_velocity_chunk(self, chunk):
          """
          Parse the velocity portion of the particle
  
          @throws SampleException If there is a problem with sample creation
          """
          N = (len(chunk) - 2) / 2 / 4
          offset = 0
  
          velocity_data_id = unpack("<H", chunk[0:2])[0]
          if 256 != velocity_data_id:
              raise SampleException("velocity_data_id was not equal to 256")
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.VELOCITY_DATA_ID,
                                    DataParticleKey.VALUE: velocity_data_id})
  
          water_velocity_east = []
          water_velocity_north = []
          water_velocity_up = []
          error_velocity = []
          for row in range(0, N):
              (a, b, c, d) = unpack('<hhhh', chunk[offset + 2: offset + 10])
              water_velocity_east.append(a)
              water_velocity_north.append(b)
              water_velocity_up.append(c)
              error_velocity.append(d)
              offset += 4 * 2
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.WATER_VELOCITY_EAST,
                                    DataParticleKey.VALUE: water_velocity_east})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.WATER_VELOCITY_NORTH,
                                    DataParticleKey.VALUE: water_velocity_north})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.WATER_VELOCITY_UP,
                                    DataParticleKey.VALUE: water_velocity_up})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ERROR_VELOCITY,
                                    DataParticleKey.VALUE: error_velocity})
  
      def parse_corelation_magnitude_chunk(self, chunk):
          """
          Parse the corelation magnitude portion of the particle
  
          @throws SampleException If there is a problem with sample creation
          """
          N = (len(chunk) - 2) / 4
          offset = 0
  
          correlation_magnitude_id = unpack("<H", chunk[0:2])[0]
          if 512 != correlation_magnitude_id:
              raise SampleException("correlation_magnitude_id was not equal to 512")
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CORRELATION_MAGNITUDE_ID,
                                    DataParticleKey.VALUE: correlation_magnitude_id})
  
          correlation_magnitude_beam1 = []
          correlation_magnitude_beam2 = []
          correlation_magnitude_beam3 = []
          correlation_magnitude_beam4 = []
          for row in range(0, N):
              (a, b, c, d) = unpack('<BBBB', chunk[offset + 2: offset + 6])
              correlation_magnitude_beam1.append(a)
              correlation_magnitude_beam2.append(b)
              correlation_magnitude_beam3.append(c)
              correlation_magnitude_beam4.append(d)
              offset += 4
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CORRELATION_MAGNITUDE_BEAM1,
                                    DataParticleKey.VALUE: correlation_magnitude_beam1})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CORRELATION_MAGNITUDE_BEAM2,
                                    DataParticleKey.VALUE: correlation_magnitude_beam2})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CORRELATION_MAGNITUDE_BEAM3,
                                    DataParticleKey.VALUE: correlation_magnitude_beam3})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.CORRELATION_MAGNITUDE_BEAM4,
                                    DataParticleKey.VALUE: correlation_magnitude_beam4})
  
      def parse_echo_intensity_chunk(self, chunk):
          """
          Parse the echo intensity portion of the particle
  
          @throws SampleException If there is a problem with sample creation
          """
          N = (len(chunk) - 2) / 4
          offset = 0
  
          echo_intensity_id = unpack("<H", chunk[0:2])[0]
          if 768 != echo_intensity_id:
              raise SampleException("echo_intensity_id was not equal to 768")
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ECHO_INTENSITY_ID,
                                    DataParticleKey.VALUE: echo_intensity_id})
  
          echo_intesity_beam1 = []
          echo_intesity_beam2 = []
          echo_intesity_beam3 = []
          echo_intesity_beam4 = []
          for row in range(0, N):
              (a, b, c, d) = unpack('<BBBB', chunk[offset + 2: offset + 6])
              echo_intesity_beam1.append(a)
              echo_intesity_beam2.append(b)
              echo_intesity_beam3.append(c)
              echo_intesity_beam4.append(d)
              offset += 4
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ECHO_INTENSITY_BEAM1,
                                    DataParticleKey.VALUE: echo_intesity_beam1})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ECHO_INTENSITY_BEAM2,
                                    DataParticleKey.VALUE: echo_intesity_beam2})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ECHO_INTENSITY_BEAM3,
                                    DataParticleKey.VALUE: echo_intesity_beam3})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ECHO_INTENSITY_BEAM4,
                                    DataParticleKey.VALUE: echo_intesity_beam4})
  
      def parse_percent_good_chunk(self, chunk):
          """
          Parse the percent good portion of the particle
  
          @throws SampleException If there is a problem with sample creation
          """
          N = (len(chunk) - 2) / 4
          offset = 0
  
          percent_good_id = unpack("<H", chunk[0:2])[0]
          if 1024 != percent_good_id:
              raise SampleException("percent_good_id was not equal to 1024")
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PERCENT_GOOD_ID,
                                    DataParticleKey.VALUE: percent_good_id})
  
          percent_good_3beam = []
          percent_transforms_reject = []
          percent_bad_beams = []
          percent_good_4beam = []
          for row in range(0, N):
              (a, b, c, d) = unpack('<BBBB', chunk[offset + 2: offset + 6])
              percent_good_3beam.append(a)
              percent_transforms_reject.append(b)
              percent_bad_beams.append(c)
              percent_good_4beam.append(d)
              offset += 4
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PERCENT_GOOD_3BEAM,
                                    DataParticleKey.VALUE: percent_good_3beam})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PERCENT_TRANSFORMS_REJECT,
                                    DataParticleKey.VALUE: percent_transforms_reject})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PERCENT_BAD_BEAMS,
                                    DataParticleKey.VALUE: percent_bad_beams})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.PERCENT_GOOD_4BEAM,
                                    DataParticleKey.VALUE: percent_good_4beam})
  
      def parse_bottom_track_chunk(self, chunk):
          """
          Parse the bottom track portion of the particle
  
          @throws SampleException If there is a problem with sample creation
          """
          (bottom_track_id, bt_pings_per_ensemble, bt_delay_before_reacquire,
           bt_corr_magnitude_min, bt_eval_magnitude_min, bt_percent_good_min,
           bt_mode, bt_error_velocity_max, RESERVED, beam1_bt_range_lsb, beam2_bt_range_lsb,
           beam3_bt_range_lsb, beam4_bt_range_lsb, eastward_bt_velocity,
           northward_bt_velocity, upward_bt_velocity, error_bt_velocity,
           beam1_bt_correlation, beam2_bt_correlation, beam3_bt_correlation,
           beam4_bt_correlation, beam1_eval_amp, beam2_eval_amp, beam3_eval_amp,
           beam4_eval_amp, beam1_bt_percent_good, beam2_bt_percent_good,
           beam3_bt_percent_good, beam4_bt_percent_good, ref_layer_min,
           ref_layer_near, ref_layer_far, beam1_ref_layer_velocity,
           beam2_ref_layer_velocity, beam3_ref_layer_velocity,
           beam4_ref_layer_velocity, beam1_ref_correlation, beam2_ref_correlation,
           beam3_ref_correlation, beam4_ref_correlation, beam1_ref_intensity,
           beam2_ref_intensity, beam3_ref_intensity, beam4_ref_intensity,
           beam1_ref_percent_good, beam2_ref_percent_good, beam3_ref_percent_good,
           beam4_ref_percent_good, bt_max_depth, beam1_rssi_amplitude,
           beam2_rssi_amplitude, beam3_rssi_amplitude, beam4_rssi_amplitude,
           bt_gain, beam1_bt_range_msb, beam2_bt_range_msb, beam3_bt_range_msb,
           beam4_bt_range_msb) = \
              unpack('<HHHBBBBHLHHHHhhhhBBBBBBBBBBBBHHHhhhhBBBBBBBBBBBBHBBBBBBBBB', chunk)
  
          if 1536 != bottom_track_id:
              raise SampleException("bottom_track_id was not equal to 1536")
  
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BOTTOM_TRACK_ID,
                                    DataParticleKey.VALUE: bottom_track_id})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_PINGS_PER_ENSEMBLE,
                                    DataParticleKey.VALUE: bt_pings_per_ensemble})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_DELAY_BEFORE_REACQUIRE,
                                    DataParticleKey.VALUE: bt_delay_before_reacquire})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_CORR_MAGNITUDE_MIN,
                                    DataParticleKey.VALUE: bt_corr_magnitude_min})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_EVAL_MAGNITUDE_MIN,
                                    DataParticleKey.VALUE: bt_eval_magnitude_min})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_PERCENT_GOOD_MIN,
                                    DataParticleKey.VALUE: bt_percent_good_min})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_MODE,
                                    DataParticleKey.VALUE: bt_mode})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_ERROR_VELOCITY_MAX,
                                    DataParticleKey.VALUE: bt_error_velocity_max})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_BT_RANGE_LSB,
                                    DataParticleKey.VALUE: beam1_bt_range_lsb})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_BT_RANGE_LSB,
                                    DataParticleKey.VALUE: beam2_bt_range_lsb})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_BT_RANGE_LSB,
                                    DataParticleKey.VALUE: beam3_bt_range_lsb})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_BT_RANGE_LSB,
                                    DataParticleKey.VALUE: beam4_bt_range_lsb})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.EASTWARD_BT_VELOCITY,
                                    DataParticleKey.VALUE: eastward_bt_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.NORTHWARD_BT_VELOCITY,
                                    DataParticleKey.VALUE: northward_bt_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.UPWARD_BT_VELOCITY,
                                    DataParticleKey.VALUE: upward_bt_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.ERROR_BT_VELOCITY,
                                    DataParticleKey.VALUE: error_bt_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_BT_CORRELATION,
                                    DataParticleKey.VALUE: beam1_bt_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_BT_CORRELATION,
                                    DataParticleKey.VALUE: beam2_bt_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_BT_CORRELATION,
                                    DataParticleKey.VALUE: beam3_bt_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_BT_CORRELATION,
                                    DataParticleKey.VALUE: beam4_bt_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_EVAL_AMP,
                                    DataParticleKey.VALUE: beam1_eval_amp})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_EVAL_AMP,
                                    DataParticleKey.VALUE: beam2_eval_amp})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_EVAL_AMP,
                                    DataParticleKey.VALUE: beam3_eval_amp})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_EVAL_AMP,
                                    DataParticleKey.VALUE: beam4_eval_amp})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_BT_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam1_bt_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_BT_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam2_bt_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_BT_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam3_bt_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_BT_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam4_bt_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.REF_LAYER_MIN,
                                    DataParticleKey.VALUE: ref_layer_min})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.REF_LAYER_NEAR,
                                    DataParticleKey.VALUE: ref_layer_near})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.REF_LAYER_FAR,
                                    DataParticleKey.VALUE: ref_layer_far})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_REF_LAYER_VELOCITY,
                                    DataParticleKey.VALUE: beam1_ref_layer_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_REF_LAYER_VELOCITY,
                                    DataParticleKey.VALUE: beam2_ref_layer_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_REF_LAYER_VELOCITY,
                                    DataParticleKey.VALUE: beam3_ref_layer_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_REF_LAYER_VELOCITY,
                                    DataParticleKey.VALUE: beam4_ref_layer_velocity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_REF_CORRELATION,
                                    DataParticleKey.VALUE: beam1_ref_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_REF_CORRELATION,
                                    DataParticleKey.VALUE: beam2_ref_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_REF_CORRELATION,
                                    DataParticleKey.VALUE: beam3_ref_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_REF_CORRELATION,
                                    DataParticleKey.VALUE: beam4_ref_correlation})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_REF_INTENSITY,
                                    DataParticleKey.VALUE: beam1_ref_intensity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_REF_INTENSITY,
                                    DataParticleKey.VALUE: beam2_ref_intensity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_REF_INTENSITY,
                                    DataParticleKey.VALUE: beam3_ref_intensity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_REF_INTENSITY,
                                    DataParticleKey.VALUE: beam4_ref_intensity})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_REF_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam1_ref_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_REF_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam2_ref_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_REF_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam3_ref_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_REF_PERCENT_GOOD,
                                    DataParticleKey.VALUE: beam4_ref_percent_good})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_MAX_DEPTH,
                                    DataParticleKey.VALUE: bt_max_depth})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_RSSI_AMPLITUDE,
                                    DataParticleKey.VALUE: beam1_rssi_amplitude})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_RSSI_AMPLITUDE,
                                    DataParticleKey.VALUE: beam2_rssi_amplitude})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_RSSI_AMPLITUDE,
                                    DataParticleKey.VALUE: beam3_rssi_amplitude})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_RSSI_AMPLITUDE,
                                    DataParticleKey.VALUE: beam4_rssi_amplitude})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BT_GAIN,
                                    DataParticleKey.VALUE: bt_gain})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM1_BT_RANGE_MSB,
                                    DataParticleKey.VALUE: beam1_bt_range_msb})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM2_BT_RANGE_MSB,
                                    DataParticleKey.VALUE: beam2_bt_range_msb})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM3_BT_RANGE_MSB,
                                    DataParticleKey.VALUE: beam3_bt_range_msb})
          self.final_result.append({DataParticleKey.VALUE_ID: ADCPA_PD0_PARSED_KEY.BEAM4_BT_RANGE_MSB,
                                    DataParticleKey.VALUE: beam4_bt_range_msb})
  
  
  class AdcpaParser(BufferLoadingParser):
      """
      AdcpaParser parses a TRDI ExplorerDVL (ADCPA) PD0 formatted data file that
      has been logged on the TWR Slocum Coastal Electric Glider.
      """
      def __init__(self,
                   config,
                   state,
                   stream_handle,
                   state_callback,
                   publish_callback,
                   *args, **kwargs):
          super(AdcpaParser, self).__init__(config,
                                            stream_handle,
                                            state,
                                            partial(StringChunker.regex_sieve_function,
                                                    regex_list=[ADCPA_PD0_PARSED_MATCHER]),
                                            state_callback,
                                            publish_callback,
                                            *args,
                                            **kwargs)
          self._timestamp = 0.0
          self._record_buffer = []  # holds tuples of (record, state)
          self._read_state = {StateKey.POSITION: 0}
          if state:
              self.set_state(self._state)
  
      def set_state(self, state_obj):
          """
          Set the value of the state object for this parser
          @param state_obj The object to set the state to. Should be a dict with
              a StateKey.POSITION value. The position is the number of bytes into
              the file.
          @throws DatasetParserException if there is a bad state structure
          """
          log.trace("Attempting to set state to: %s", state_obj)
          if not isinstance(state_obj, dict):
              raise DatasetParserException("Invalid state structure")
          if not StateKey.POSITION in state_obj:
              raise DatasetParserException("Invalid state keys")
  
          self._record_buffer = []
          self._state = state_obj
          self._read_state = state_obj
  
          # seek to it
          self._stream_handle.seek(state_obj[StateKey.POSITION])
  
      def _increment_state(self, increment):
          """
          Increment the parser position by a certain amount in bytes. This
          indicates what has been READ from the file, not what has been published.
  
          This is a base implementation, override as needed.
  
          @param increment Number of bytes to increment the parser position.
          """
          log.trace("Incrementing current state: %s with increment: %s",
                    self._read_state, increment)
  
          self._read_state[StateKey.POSITION] += increment
  
      def get_block(self):
          """
          Overwrites get_block method in dataset_parser.py to simply read the
          entire file rather than break it into chunks.
          @retval The length of data retreived
          @throws EOFError when the end of the file is reached
          """
          # read in some more data
          data = self._stream_handle.read()
          if data:
              self._chunker.add_chunk(data, self._timestamp)
              return len(data)
          else:  # EOF
              raise EOFError
  
      def parse_chunks(self):
          """
          @retval a list of tuples with sample particles encountered in this
              parsing, plus the state. An empty list is returned if nothing was
              parsed.
          """
          # set default empty list
          result_particles = []
  
          # now parse the file, ensemble by ensemble, publishing the results as we go.
          (timestamp, chunk, start, end) = self._chunker.get_next_data_with_index(True)
          while chunk is not None:
              # particleize the data block received.
              particle = self._extract_sample(self._particle_class, ADCPA_PD0_PARSED_MATCHER,
                                              chunk, self._timestamp)
  
              # if the particle is good, set the state and append particle
              if particle:
                  log.trace("Particle creation succeeded at position: %d to %d bytes", start, end)
                  self._increment_state(end)
                  result_particles.append((particle, copy.copy(self._read_state)))
              else:
                  log.trace("Particle creation failed at position: %d to %d bytes", start, end)
  
              # keep consuming the file
              (timestamp, chunk, start, end) = self._chunker.get_next_data_with_index()
  
          # save the results
          return result_particles
  