text: import logging
  
  from collections import OrderedDict
  
  from .ophydobj import Kind
  from .signal import (EpicsSignal, EpicsSignalRO)
  from .device import Device
  from .device import (Component as Cpt, DynamicDeviceComponent as DDCpt,
                       FormattedComponent as FCpt)
  
  logger = logging.getLogger(__name__)
  
  
  def _scaler_fields(cls, attr_base, field_base, range_, **kwargs):
      defn = OrderedDict()
      for i in range_:
          attr = '{attr}{i}'.format(attr=attr_base, i=i)
          suffix = '{field}{i}'.format(field=field_base, i=i)
          defn[attr] = (cls, suffix, kwargs)
  
      return defn
  
  
  class EpicsScaler(Device):
      '''SynApps Scaler Record interface'''
      # tigger + trigger mode
      count = Cpt(EpicsSignal, '.CNT', trigger_value=1, kind=Kind.omitted)
      count_mode = Cpt(EpicsSignal, '.CONT', string=True, kind=Kind.config)
  
      # delay from triggering to starting counting
      delay = Cpt(EpicsSignal, '.DLY', kind=Kind.config)
      auto_count_delay = Cpt(EpicsSignal, '.DLY1', kind=Kind.config)
  
      # the data
      channels = DDCpt(_scaler_fields(EpicsSignalRO, 'chan', '.S', range(1, 33),
                                      kind=Kind.hinted))
      names = DDCpt(_scaler_fields(EpicsSignal, 'name', '.NM', range(1, 33),
                                   kind=Kind.config))
  
      time = Cpt(EpicsSignal, '.T', kind=Kind.config)
      freq = Cpt(EpicsSignal, '.FREQ', kind=Kind.config)
  
      preset_time = Cpt(EpicsSignal, '.TP', kind=Kind.config)
      auto_count_time = Cpt(EpicsSignal, '.TP1', kind=Kind.config)
  
      presets = DDCpt(_scaler_fields(EpicsSignal, 'preset', '.PR', range(1, 33),
                                     kind=Kind.omitted))
      gates = DDCpt(_scaler_fields(EpicsSignal, 'gate', '.G', range(1, 33),
                                   kind=Kind.omitted))
  
      update_rate = Cpt(EpicsSignal, '.RATE', kind=Kind.omitted)
      auto_count_update_rate = Cpt(EpicsSignal, '.RAT1', kind=Kind.omitted)
  
      egu = Cpt(EpicsSignal, '.EGU', kind=Kind.config)
  
      def __init__(self, *args, **kwargs):
  
          super().__init__(*args, **kwargs)
  
          self.stage_sigs.update([('count_mode', 0)])
  
  
  class ScalerChannel(Device):
  
      # TODO set up monitor on this to automatically change the name
      chname = FCpt(EpicsSignal, '{self.prefix}.NM{self._ch_num}',
                    kind=Kind.config)
      s = FCpt(EpicsSignalRO, '{self.prefix}.S{self._ch_num}',
               kind=Kind.hinted, auto_monitor=False)
      preset = FCpt(EpicsSignal, '{self.prefix}.PR{self._ch_num}',
                    kind=Kind.config)
      gate = FCpt(EpicsSignal, '{self.prefix}.G{self._ch_num}', string=True,
                  kind=Kind.config)
  
      def __init__(self, prefix, ch_num,
                   **kwargs):
          self._ch_num = ch_num
  
          super().__init__(prefix, **kwargs)
          self.match_name()
  
      def match_name(self):
          self.s.name = self.chname.get()
  
  
  def _sc_chans(attr_fix, id_range):
      defn = OrderedDict()
      for k in id_range:
          defn['{}{:02d}'.format(attr_fix, k)] = (ScalerChannel,
                                                  '', {'ch_num': k,
                                                       'kind': Kind.normal})
      return defn
  
  
  class ScalerCH(Device):
  
      # The data
      channels = DDCpt(_sc_chans('chan', range(1, 33)))
  
      # tigger + trigger mode
      count = Cpt(EpicsSignal, '.CNT', trigger_value=1, kind=Kind.omitted)
      count_mode = Cpt(EpicsSignal, '.CONT', string=True, kind=Kind.config)
  
      # delay from triggering to starting counting
      delay = Cpt(EpicsSignal, '.DLY', kind=Kind.config)
      auto_count_delay = Cpt(EpicsSignal, '.DLY1', kind=Kind.config)
  
      time = Cpt(EpicsSignal, '.T')
      freq = Cpt(EpicsSignal, '.FREQ', kind=Kind.config)
  
      preset_time = Cpt(EpicsSignal, '.TP', kind=Kind.config)
      auto_count_time = Cpt(EpicsSignal, '.TP1', kind=Kind.config)
  
      update_rate = Cpt(EpicsSignal, '.RATE', kind=Kind.omitted)
      auto_count_update_rate = Cpt(EpicsSignal, '.RAT1', kind=Kind.omitted)
  
      egu = Cpt(EpicsSignal, '.EGU', kind=Kind.config)
  
      def match_names(self):
          for s in self.channels.component_names:
              getattr(self.channels, s).match_name()
  
      def select_channels(self, chan_names=None):
          '''Select channels based on the EPICS name PV
  
          Parameters
          ----------
          chan_names : Iterable[str] or None
  
              The names (as reported by the ``channel.chname`` signal)
              of the channels to select.
              If ``None``, select **all** channels named in the EPICS scaler.
          '''
          self.match_names()
          name_map = {}
          for s in self.channels.component_names:
              scaler_channel = getattr(self.channels, s)
              nm = scaler_channel.s.name  # as defined in self.match_names()
              if len(nm) > 0:
                  name_map[nm] = s
  
          if chan_names is None:
              chan_names = name_map.keys()
  
          read_attrs = []
          for ch in chan_names:
              try:
                  read_attrs.append(name_map[ch])
              except KeyError:
                  raise RuntimeError("The channel {} is not configured "
                                     "on the scaler.  The named channels are "
                                     "{}".format(ch, tuple(name_map)))
  
          self.channels.kind = Kind.normal
          self.channels.read_attrs = list(read_attrs)
          self.channels.configuration_attrs = list(read_attrs)
          for ch in read_attrs[1:]:
              getattr(self.channels, ch).s.kind = Kind.hinted
  