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)