docs: def read_line(self, timeout=0.0, purge_buffer=False):
          """
          Reads a line from the device.
  
          :param timeout: read timeout
          :type timeout: float
          :param purge_buffer: Indicates whether to purge the buffer prior to
                               reading.
          :type purge_buffer: bool
  
          :returns: line that was read
          :raises: :py:class:`~alarmdecoder.util.CommError`, :py:class:`~alarmdecoder.util.TimeoutError`
          """
  
          def timeout_event():
              """Handles read timeout event"""
              timeout_event.reading = False
          timeout_event.reading = True
  
          if purge_buffer:
              self._buffer = b''
  
          got_line, data = False, ''
  
          timer = threading.Timer(timeout, timeout_event)
          if timeout > 0:
              timer.start()
  
          leftovers = b''
          try:
              while timeout_event.reading and not got_line:
                  read_ready, _, _ = select.select([self._device.fileno()], [], [], 0.5)
                  if len(read_ready) == 0:
                      continue
  
                  bytes_avail = 0
                  if hasattr(self._device, "in_waiting"):
                      bytes_avail = self._device.in_waiting
                  else:
                      bytes_avail = self._device.inWaiting()
  
                  buf = self._device.read(bytes_avail)
  
                  for idx in range(len(buf)):
                      c = buf[idx]
  
                      ub = bytes_hack(c)
                      if sys.version_info > (3,):
                          ub = bytes([ub])
  
                      # NOTE: AD2SERIAL and AD2PI apparently sends down \xFF on boot.
                      if ub != b'' and ub != b"\xff":
                          self._buffer += ub
  
                          if ub == b"\n":
                              self._buffer = self._buffer.strip(b"\r\n")
  
                              if len(self._buffer) > 0:
                                  got_line = True
                                  leftovers = buf[idx:]
                                  break
  
          except (OSError, serial.SerialException) as err:
              raise CommError('Error reading from device: {0}'.format(str(err)), err)
  
          else:
              if got_line:
                  data, self._buffer = self._buffer, leftovers
  
                  self.on_read(data=data)
  
              else:
                  raise TimeoutError('Timeout while waiting for line terminator.')
  
          finally:
              timer.cancel()
  
          return data.decode('utf-8')