text: import textwrap
  from typing import Optional, Union
  from collections import defaultdict
  
  from qcodes import VisaInstrument
  from qcodes.utils.helpers import create_on_off_val_mapping
  from .KeysightB1530A import B1530A
  from .KeysightB1520A import B1520A
  from .KeysightB1517A import B1517A
  from .KeysightB1500_module import B1500Module, parse_module_query_response, \
      parse_spot_measurement_response
  from . import constants
  from .constants import ChannelList
  from .message_builder import MessageBuilder
  
  
  class KeysightB1500(VisaInstrument):
      """Driver for Keysight B1500 Semiconductor Parameter Analyzer.
  
      For the list of supported modules, refer to :meth:`from_model_name`.
      """
  
      def __init__(self, name, address, **kwargs):
          super().__init__(name, address, terminator="\r\n", **kwargs)
  
          self.by_slot = {}
          self.by_channel = {}
          self.by_kind = defaultdict(list)
  
          self._find_modules()
  
          self.add_parameter('autozero_enabled',
                             unit='',
                             label='Autozero enabled of the high-resolution ADC',
                             set_cmd=self._set_autozero,
                             get_cmd=None,
                             val_mapping=create_on_off_val_mapping(
                                 on_val=True, off_val=False),
                             docstring=textwrap.dedent("""
              Enable or disable cancelling of the offset of the 
              high-resolution A/D converter (ADC).
      
              Set the function to OFF in cases that the measurement speed is 
              more important than the measurement accuracy. This roughly halves
              the integration time."""))
          # Instrument is initialized with this setting having value of
          # `False`, hence let's set the parameter to this value since it is
          # not possible to request this value from the instrument.
          self.autozero_enabled.raw_value = False
          self.autozero_enabled._save_val(False)
          self.connect_message()
  
      def add_module(self, name: str, module: B1500Module):
          super().add_submodule(name, module)
  
          self.by_kind[module.MODULE_KIND].append(module)
          self.by_slot[module.slot_nr] = module
          for ch in module.channels:
              self.by_channel[ch] = module
  
      def reset(self):
          """Performs an instrument reset.
  
          This does not reset error queue!
          """
          self.write("*RST")
  
      def get_status(self) -> int:
          return int(self.ask("*STB?"))
  
      # TODO: Data Output parser: At least for Format FMT1,0 and maybe for a
      # second (binary) format. 8 byte binary format would be nice because it
      # comes with time stamp
      # FMT1,0: ASCII (12 digits data with header) <CR/LF^EOI>
  
      def _find_modules(self):
          from .constants import UNT
  
          r = self.ask(MessageBuilder()
                       .unt_query(mode=UNT.Mode.MODULE_INFO_ONLY)
                       .message
                       )
  
          slot_population = parse_module_query_response(r)
  
          for slot_nr, model in slot_population.items():
              module = self.from_model_name(model, slot_nr, self)
  
              self.add_module(name=module.short_name, module=module)
  
      @staticmethod
      def from_model_name(model: str, slot_nr: int, parent: 'KeysightB1500',
                          name: Optional[str] = None) -> 'B1500Module':
          """Creates the correct instance of instrument module by model name.
  
          Args:
              model: Model name such as 'B1517A'
              slot_nr: Slot number of this module (not channel number)
              parent: Reference to B1500 mainframe instance
              name: Name of the instrument instance to create. If `None`
                  (Default), then the name is autogenerated from the instrument
                  class.
  
          Retuns:
              A specific instance of :class:`.B1500Module`
          """
          if model == "B1517A":
              return B1517A(slot_nr=slot_nr, parent=parent, name=name)
          elif model == "B1520A":
              return B1520A(slot_nr=slot_nr, parent=parent, name=name)
          elif model == "B1530A":
              return B1530A(slot_nr=slot_nr, parent=parent, name=name)
          else:
              raise NotImplementedError("Module type not yet supported.")
  
      def enable_channels(self, channels: ChannelList = None):
          """Enable specified channels.
  
          If channels is omitted or `None`, then all channels are enabled.
          """
          msg = MessageBuilder().cn(channels)
  
          self.write(msg.message)
  
      def disable_channels(self, channels: ChannelList = None):
          """Disable specified channels.
  
          If channels is omitted or `None`, then all channels are disabled.
          """
          msg = MessageBuilder().cl(channels)
  
          self.write(msg.message)
  
      # Response parsing functions as static methods for user convenience
      parse_spot_measurement_response = parse_spot_measurement_response
      parse_module_query_response = parse_module_query_response
  
      def _setup_integration_time(self,
                                  adc_type: constants.AIT.Type,
                                  mode: Union[constants.AIT.Mode, int],
                                  coeff: Optional[Union[int, float]] = None
                                  ) -> None:
          """See :meth:`MessageBuilder.ait` for information"""
          self.write(MessageBuilder()
                     .ait(adc_type=adc_type, mode=mode, coeff=coeff)
                     .message
                     )
  
      def use_nplc_for_high_speed_adc(
              self, n: Optional[Union[int, float]] = None) -> None:
          """
          Set the high-speed ADC to NPLC mode, with optionally defining number
          of averaging samples via argument `n`.
  
          Args:
              n: Value that defines the number of averaging samples given by
                  the following formula:
  
                  ``Number of averaging samples = n / 128``.
  
                  n=1 to 100. Default setting is 1 (if `None` is passed).
  
                  The Keysight B1500 gets 128 samples in a power line cycle,
                  repeats this for the times you specify, and performs
                  averaging to get the measurement data. (For more info see
                  Table 4-21.)
          """
          self._setup_integration_time(
              adc_type=constants.AIT.Type.HIGH_SPEED,
              mode=constants.AIT.Mode.NPLC,
              coeff=n
          )
  
      def use_nplc_for_high_resolution_adc(
              self, n: Optional[Union[int, float]] = None) -> None:
          """
          Set the high-resolution ADC to NPLC mode, with optionally defining
          the number of PLCs per sample via argument `n`.
  
          Args:
              n: Value that defines the integration time given by the
                  following formula:
  
                  ``Integration time = n / power line frequency``.
  
                  n=1 to 100. Default setting is 1 (if `None` is passed).
                  (For more info see Table 4-21.)
          """
          self._setup_integration_time(
              adc_type=constants.AIT.Type.HIGH_RESOLUTION,
              mode=constants.AIT.Mode.NPLC,
              coeff=n
          )
  
      def use_manual_mode_for_high_speed_adc(
              self, n: Optional[Union[int, float]] = None) -> None:
          """
          Set the high-speed ADC to manual mode, with optionally defining number
          of averaging samples via argument `n`.
  
          Use ``n=1`` to disable averaging (``n=None`` uses the default
          setting from the instrument which is also ``n=1``).
  
          Args:
              n: Number of averaging samples, between 1 and 1023. Default
                  setting is 1. (For more info see Table 4-21.)
          """
          self._setup_integration_time(
              adc_type=constants.AIT.Type.HIGH_SPEED,
              mode=constants.AIT.Mode.MANUAL,
              coeff=n
          )
  
      def _set_autozero(self, do_autozero: bool) -> None:
          self.write(MessageBuilder().az(do_autozero=do_autozero).message)
  