docs: def _parse_message(self, data):
          """Interpret each message datagram from device and do the needful.
  
          This function receives datagrams from _assemble_buffer and inerprets
          what they mean.  It's responsible for maintaining the internal state
          table for each device attribute and also for firing the update_callback
          function (if one was supplied)
          """
          recognized = False
          newdata = False
  
          if data.startswith('!I'):
              self.log.warning('Invalid command: %s', data[2:])
              recognized = True
          elif data.startswith('!R'):
              self.log.warning('Out-of-range command: %s', data[2:])
              recognized = True
          elif data.startswith('!E'):
              self.log.warning('Cannot execute recognized command: %s', data[2:])
              recognized = True
          elif data.startswith('!Z'):
              self.log.warning('Ignoring command for powered-off zone: %s', data[2:])
              recognized = True
          else:
  
              for key in LOOKUP:
                  if data.startswith(key):
                      recognized = True
  
                      value = data[len(key):]
                      oldvalue = getattr(self, '_'+key)
                      if oldvalue != value:
                          changeindicator = 'New Value'
                          newdata = True
                      else:
                          changeindicator = 'Unchanged'
  
                      if key in LOOKUP:
                          if 'description' in LOOKUP[key]:
                              if value in LOOKUP[key]:
                                  self.log.info('%s: %s (%s) -> %s (%s)',
                                                changeindicator,
                                                LOOKUP[key]['description'], key,
                                                LOOKUP[key][value], value)
                              else:
                                  self.log.info('%s: %s (%s) -> %s',
                                                changeindicator,
                                                LOOKUP[key]['description'], key,
                                                value)
                      else:
                          self.log.info('%s: %s -> %s', changeindicator, key, value)
  
                      setattr(self, '_'+key, value)
  
                      if key == 'Z1POW' and value == '1' and oldvalue == '0':
                          self.log.info('Power on detected, refreshing all attributes')
                          self._poweron_refresh_successful = False
                          self._loop.call_later(1, self.poweron_refresh)
  
                      if key == 'Z1POW' and value == '0' and oldvalue == '1':
                          self._poweron_refresh_successful = False
  
                      break
  
          if data.startswith('ICN'):
              self.log.warning('ICN update received')
              recognized = True
              self._populate_inputs(int(value))
  
          if data.startswith('ISN'):
              recognized = True
              self._poweron_refresh_successful = True
  
              input_number = int(data[3:5])
              value = data[5:]
  
              oldname = self._input_names.get(input_number, '')
  
              if oldname != value:
                  self._input_numbers[value] = input_number
                  self._input_names[input_number] = value
                  self.log.info('New Value: Input %d is called %s', input_number, value)
                  newdata = True
  
          if newdata:
              if self._update_callback:
                  self._loop.call_soon(self._update_callback, data)
          else:
              self.log.debug('no new data encountered')
  
          if not recognized:
              self.log.warning('Unrecognized response: %s', data)