Example #1
0
    def __init__(self,
                 universe,
                 uid,
                 broadcast_write_delay,
                 inter_test_delay,
                 pid_store,
                 wrapper,
                 timestamp=False):
        """Create a new TestRunner.

    Args:
      universe: The universe number to use
      uid: The UID object to test
      broadcast_write_delay: the delay to use after sending broadcast sets
      inter_test_delay: the delay to use between tests
      pid_store: A PidStore object
      wrapper: A ClientWrapper object
      timestamp: true to print timestamps with each test
    """
        self._universe = universe
        self._uid = uid
        self._broadcast_write_delay = broadcast_write_delay
        self._inter_test_delay = inter_test_delay
        self._timestamp = timestamp
        self._pid_store = pid_store
        self._api = RDMAPI(wrapper.Client(), pid_store, strict_checks=False)
        self._wrapper = wrapper

        # maps device properties to the tests that provide them
        self._property_map = {}
        self._all_tests = set()  # set of all test classes

        # Used to flush the queued message queue
        self._message_fetcher = QueuedMessageFetcher(universe, uid, self._api,
                                                     wrapper)
    def __init__(self, universe, uid, sub_device, pid_file):
        """Create a new InteractiveModeController.

    Args:
      universe:
      uid:
      sub_device:
      pid_file:
    """
        cmd.Cmd.__init__(self)
        self._universe = universe
        self._uid = uid
        self._sub_device = sub_device

        self.pid_store = PidStore.GetStore(pid_file)
        self.wrapper = ClientWrapper()
        self.client = self.wrapper.Client()
        self.rdm_api = RDMAPI(self.client, self.pid_store)
        self._uids = []
        self._response_printer = ResponsePrinter()

        # tuple of (sub_device, command_class, pid)
        self._outstanding_request = None

        self.prompt = '> '
Example #3
0
 def __init__(self, wrapper, pid_store):
     self.wrapper = wrapper
     self.pid_store = pid_store
     self.client = self.wrapper.Client()
     self.rdm_api = RDMAPI(self.client, self.pid_store)
Example #4
0
class ModelCollector(object):
    """A controller that fetches data for responders."""

    (EMPTYING_QUEUE, DEVICE_INFO, MODEL_DESCRIPTION, SUPPORTED_PARAMS,
     SOFTWARE_VERSION_LABEL, PERSONALITIES, SENSORS, MANUFACTURER_PIDS,
     LANGUAGE, LANGUAGES, SLOT_INFO, SLOT_DESCRIPTION,
     SLOT_DEFAULT_VALUE) = xrange(13)

    def __init__(self, wrapper, pid_store):
        self.wrapper = wrapper
        self.pid_store = pid_store
        self.client = self.wrapper.Client()
        self.rdm_api = RDMAPI(self.client, self.pid_store)

    def Run(self, universe, skip_queued_messages):
        """Run the collector.

    Args:
      universe: The universe to collect
      skip_queued_messages:
    """
        self.universe = universe
        self.skip_queued_messages = skip_queued_messages
        self._ResetData()

        self.client.RunRDMDiscovery(self.universe, True, self._HandleUIDList)
        self.wrapper.Run()

        # strip various info that is redundant
        for model_list in self.data.values():
            for model in model_list:
                for key in [
                        'language', 'current_personality', 'personality_count',
                        'sensor_count'
                ]:
                    if key in model:
                        del model[key]
        return self.data

    def _ResetData(self):
        """Reset the internal data structures."""
        self.uids = []
        self.uid = None  # the current uid we're fetching from
        self.outstanding_pid = None
        self.work_state = None
        self.manufacturer_pids = []
        self.slots = []
        self.personalities = []
        self.sensors = []
        # keyed by manufacturer id
        self.data = {}

    def _GetDevice(self):
        return self.data[self.uid.manufacturer_id][-1]

    def _GetVersion(self):
        this_device = self._GetDevice()
        software_versions = this_device['software_versions']
        return software_versions[software_versions.keys()[0]]

    def _GetCurrentPersonality(self):
        this_device = self._GetDevice()
        this_version = self._GetVersion()
        personalities = this_version['personalities']
        return personalities[(this_device['current_personality'] - 1)]

    def _GetSlotData(self, slot):
        this_personality = self._GetCurrentPersonality()
        return this_personality.setdefault('slots', {}).setdefault(slot, {})

    def _GetLanguage(self):
        this_device = self._GetDevice()
        return this_device.get('language', DEFAULT_LANGUAGE)

    def _CheckPidSupported(self, pid):
        this_version = self._GetVersion()
        if pid.value in this_version['supported_parameters']:
            return True
        else:
            return False

    def _GetPid(self, pid):
        self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE, pid,
                         self._RDMRequestComplete)
        logging.debug('Sent %s request' % pid)
        self.outstanding_pid = pid

    def _HandleUIDList(self, state, uids):
        """Called when the UID list arrives."""
        if not state.Succeeded():
            raise DiscoveryException(state.message)

        found_uids = set()
        for uid in uids:
            found_uids.add(uid)
            logging.debug(uid)

        self.uids = list(found_uids)
        self._FetchNextUID()

    def _HandleResponse(self, unpacked_data):
        logging.debug(unpacked_data)
        if self.work_state == self.DEVICE_INFO:
            self._HandleDeviceInfo(unpacked_data)
        elif self.work_state == self.MODEL_DESCRIPTION:
            self._HandleDeviceModelDescription(unpacked_data)
        elif self.work_state == self.SUPPORTED_PARAMS:
            self._HandleSupportedParams(unpacked_data)
        elif self.work_state == self.SOFTWARE_VERSION_LABEL:
            self._HandleSoftwareVersionLabel(unpacked_data)
        elif self.work_state == self.PERSONALITIES:
            self._HandlePersonalityData(unpacked_data)
        elif self.work_state == self.SENSORS:
            self._HandleSensorData(unpacked_data)
        elif self.work_state == self.MANUFACTURER_PIDS:
            self._HandleManufacturerPids(unpacked_data)
        elif self.work_state == self.LANGUAGE:
            self._HandleLanguage(unpacked_data)
        elif self.work_state == self.LANGUAGES:
            self._HandleLanguages(unpacked_data)
        elif self.work_state == self.SLOT_INFO:
            self._HandleSlotInfo(unpacked_data)
        elif self.work_state == self.SLOT_DESCRIPTION:
            self._HandleSlotDescription(unpacked_data)
        elif self.work_state == self.SLOT_DEFAULT_VALUE:
            self._HandleSlotDefaultValue(unpacked_data)

    def _HandleDeviceInfo(self, data):
        """Called when we get a DEVICE_INFO response."""
        this_device = self._GetDevice()
        fields = [
            'device_model', 'product_category', 'current_personality',
            'personality_count', 'sensor_count', 'sub_device_count'
        ]
        for field in fields:
            this_device[field] = data[field]

        this_device['software_versions'][data['software_version']] = {
            'languages': [],
            'personalities': [],
            'manufacturer_pids': [],
            'supported_parameters': [],
            'sensors': [],
        }

        self.personalities = list(xrange(1, data['personality_count'] + 1))
        self.sensors = list(xrange(0, data['sensor_count']))
        self._NextState()

    def _HandleDeviceModelDescription(self, data):
        """Called when we get a DEVICE_MODEL_DESCRIPTION response."""
        this_device = self._GetDevice()
        this_device['model_description'] = data['description']
        self._NextState()

    def _HandleSupportedParams(self, data):
        """Called when we get a SUPPORTED_PARAMETERS response."""
        this_version = self._GetVersion()
        for param_info in data['params']:
            this_version['supported_parameters'].append(param_info['param_id'])
            if (param_info['param_id'] >=
                    ola.RDMConstants.RDM_MANUFACTURER_PID_MIN
                    and param_info['param_id'] <=
                    ola.RDMConstants.RDM_MANUFACTURER_PID_MAX):
                self.manufacturer_pids.append(param_info['param_id'])
        self._NextState()

    def _HandleSoftwareVersionLabel(self, data):
        """Called when we get a SOFTWARE_VERSION_LABEL response."""
        this_version = self._GetVersion()
        this_version['label'] = data['label']
        self._NextState()

    def _HandlePersonalityData(self, data):
        """Called when we get a DMX_PERSONALITY_DESCRIPTION response."""
        this_version = self._GetVersion()
        this_version['personalities'].append({
            'description':
            data['name'],
            'index':
            data['personality'],
            'slot_count':
            data['slots_required'],
        })
        self._FetchNextPersonality()

    def _HandleSensorData(self, data):
        """Called when we get a SENSOR_DEFINITION response."""
        this_version = self._GetVersion()
        this_version['sensors'].append({
            'index':
            data['sensor_number'],
            'description':
            data['name'],
            'type':
            data['type'],
            'supports_recording':
            data['supports_recording'],
        })
        self._FetchNextSensor()

    def _HandleManufacturerPids(self, data):
        """Called when we get a PARAMETER_DESCRIPTION response."""
        this_version = self._GetVersion()
        this_version['manufacturer_pids'].append({
            'pid':
            data['pid'],
            'description':
            data['description'],
            'command_class':
            data['command_class'],
            'data_type':
            data['data_type'],
            'unit':
            data['unit'],
            'prefix':
            data['prefix'],
            'min_value':
            data['min_value'],
            'default_value':
            data['default_value'],
            'max_value':
            data['max_value'],
        })
        self._FetchNextManufacturerPid()

    def _HandleLanguage(self, data):
        """Called when we get a LANGUAGE response."""
        this_device = self._GetDevice()
        this_device['language'] = data['language']
        self._NextState()

    def _HandleLanguages(self, data):
        """Called when we get a LANGUAGE_CAPABILITIES response."""
        this_version = self._GetVersion()
        for language in data['languages']:
            this_version['languages'].append(language['language'])
        self._NextState()

    def _HandleSlotInfo(self, data):
        """Called when we get a SLOT_INFO response."""
        for slot in data['slots']:
            this_slot_data = self._GetSlotData(slot['slot_offset'])
            this_slot_data['label_id'] = slot['slot_label_id']
            this_slot_data['type'] = slot['slot_type']
            self.slots.append(slot['slot_offset'])
        self._NextState()

    def _HandleSlotDescription(self, data):
        """Called when we get a SLOT_DESCRIPTION response."""
        if data is not None:
            # Got valid data, not a nack
            this_slot_data = self._GetSlotData(data['slot_number'])
            this_slot_data.setdefault('name',
                                      {})[self._GetLanguage()] = data['name']
        self._FetchNextSlotDescription()

    def _HandleSlotDefaultValue(self, data):
        """Called when we get a DEFAULT_SLOT_VALUE response."""
        for slot in data['slot_values']:
            this_slot_data = self._GetSlotData(slot['slot_offset'])
            this_slot_data['default_value'] = slot['default_slot_value']
        self._NextState()

    def _NextState(self):
        """Move to the next state of information fetching."""
        if self.work_state == self.EMPTYING_QUEUE:
            # fetch device info
            pid = self.pid_store.GetName('DEVICE_INFO')
            self._GetPid(pid)
            self.work_state = self.DEVICE_INFO
        elif self.work_state == self.DEVICE_INFO:
            # fetch device model description
            pid = self.pid_store.GetName('DEVICE_MODEL_DESCRIPTION')
            self._GetPid(pid)
            self.work_state = self.MODEL_DESCRIPTION
        elif self.work_state == self.MODEL_DESCRIPTION:
            # fetch supported params
            pid = self.pid_store.GetName('SUPPORTED_PARAMETERS')
            self._GetPid(pid)
            self.work_state = self.SUPPORTED_PARAMS
        elif self.work_state == self.SUPPORTED_PARAMS:
            # fetch software version label
            pid = self.pid_store.GetName('SOFTWARE_VERSION_LABEL')
            self._GetPid(pid)
            self.work_state = self.SOFTWARE_VERSION_LABEL
        elif self.work_state == self.SOFTWARE_VERSION_LABEL:
            self.work_state = self.PERSONALITIES
            self._FetchNextPersonality()
        elif self.work_state == self.PERSONALITIES:
            self.work_state = self.SENSORS
            self._FetchNextSensor()
        elif self.work_state == self.SENSORS:
            self.work_state = self.MANUFACTURER_PIDS
            self._FetchNextManufacturerPid()
        elif self.work_state == self.MANUFACTURER_PIDS:
            self.work_state = self.LANGUAGE
            pid = self.pid_store.GetName('LANGUAGE')
            if self._CheckPidSupported(pid):
                self._GetPid(pid)
            else:
                logging.debug(
                    "Skipping pid %s as it's not supported on this device" %
                    pid)
                self._NextState()
        elif self.work_state == self.LANGUAGE:
            # fetch language capabilities
            self.work_state = self.LANGUAGES
            pid = self.pid_store.GetName('LANGUAGE_CAPABILITIES')
            if self._CheckPidSupported(pid):
                self._GetPid(pid)
            else:
                logging.debug(
                    "Skipping pid %s as it's not supported on this device" %
                    pid)
                self._NextState()
        elif self.work_state == self.LANGUAGES:
            # fetch slot info
            self.work_state = self.SLOT_INFO
            pid = self.pid_store.GetName('SLOT_INFO')
            if self._CheckPidSupported(pid):
                self._GetPid(pid)
            else:
                logging.debug(
                    "Skipping pid %s as it's not supported on this device" %
                    pid)
                self._NextState()
        elif self.work_state == self.SLOT_INFO:
            self.work_state = self.SLOT_DESCRIPTION
            self._FetchNextSlotDescription()
        elif self.work_state == self.SLOT_DESCRIPTION:
            # fetch slot default value
            self.work_state = self.SLOT_DEFAULT_VALUE
            pid = self.pid_store.GetName('DEFAULT_SLOT_VALUE')
            if self._CheckPidSupported(pid):
                self._GetPid(pid)
            else:
                logging.debug(
                    "Skipping pid %s as it's not supported on this device" %
                    pid)
                self._NextState()
        else:
            # this one is done, onto the next UID
            self._FetchNextUID()

    def _FetchNextUID(self):
        """Start fetching the info for the next UID."""
        if not self.uids:
            self.wrapper.Stop()
            return

        self.uid = self.uids.pop()
        self.personalities = []
        self.sensors = []
        logging.debug('Fetching data for %s' % self.uid)
        devices = self.data.setdefault(self.uid.manufacturer_id, [])
        devices.append({
            'software_versions': {},
        })

        self.work_state = self.EMPTYING_QUEUE
        if self.skip_queued_messages:
            # proceed to the fetch now
            self._NextState()
        else:
            self.queued_message_failures = 0
            self._FetchQueuedMessages()

    def _FetchNextPersonality(self):
        """Fetch the info for the next personality, or proceed to the next state if
       there are none left.
    """
        pid = self.pid_store.GetName('DMX_PERSONALITY_DESCRIPTION')
        if self._CheckPidSupported(pid):
            if self.personalities:
                personality_index = self.personalities.pop(0)
                self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE,
                                 pid, self._RDMRequestComplete,
                                 [personality_index])
                logging.debug('Sent DMX_PERSONALITY_DESCRIPTION request')
                self.outstanding_pid = pid
            else:
                self._NextState()
        else:
            if self.personalities:
                # If we have personalities but no description, we still need the basic
                # data structure to add the other info to
                this_version = self._GetVersion()
                for personality_index in self.personalities:
                    this_version['personalities'].append({
                        'index':
                        personality_index,
                    })
            logging.debug(
                "Skipping pid %s as it's not supported on this device" % pid)
            self._NextState()

    def _FetchNextSensor(self):
        """Fetch the info for the next sensor, or proceed to the next state if
       there are none left.
    """
        if self.sensors:
            sensor_index = self.sensors.pop(0)
            pid = self.pid_store.GetName('SENSOR_DEFINITION')
            self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE,
                             pid, self._RDMRequestComplete, [sensor_index])
            logging.debug('Sent SENSOR_DEFINITION request')
            self.outstanding_pid = pid
        else:
            self._NextState()

    def _FetchNextManufacturerPid(self):
        """Fetch the info for the next manufacturer PID, or proceed to the next
       state if there are none left.
    """
        if self.manufacturer_pids:
            manufacturer_pid = self.manufacturer_pids.pop(0)
            pid = self.pid_store.GetName('PARAMETER_DESCRIPTION')
            self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE,
                             pid, self._RDMRequestComplete, [manufacturer_pid])
            logging.debug('Sent PARAMETER_DESCRIPTION request')
            self.outstanding_pid = pid
        else:
            self._NextState()

    def _FetchNextSlotDescription(self):
        """Fetch the description for the next slot, or proceed to the next state if
       there are none left.
    """
        pid = self.pid_store.GetName('SLOT_DESCRIPTION')
        if self._CheckPidSupported(pid):
            if self.slots:
                slot = self.slots.pop(0)
                self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE,
                                 pid, self._RDMRequestComplete, [slot])
                logging.debug('Sent SLOT_DESCRIPTION request for slot %d' %
                              slot)
                self.outstanding_pid = pid
            else:
                self._NextState()
        else:
            logging.debug(
                "Skipping pid %s as it's not supported on this device" % pid)
            self._NextState()

    def _FetchQueuedMessages(self):
        """Fetch messages until the queue is empty."""
        pid = self.pid_store.GetName('QUEUED_MESSAGE')
        self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE, pid,
                         self._QueuedMessageComplete, ['advisory'])
        logging.debug('Sent GET QUEUED_MESSAGE')

    def _RDMRequestComplete(self, response, unpacked_data, unpack_exception):
        if not self._CheckForAckOrNack(response):
            return

        # at this stage the response is either a ack or nack
        # We have to allow nacks from SLOT_DESCRIPTION, as it may not have a
        # description for every slot
        if (response.response_type == OlaClient.RDM_NACK_REASON
                and response.pid !=
                self.pid_store.GetName('SLOT_DESCRIPTION').value):
            print('Got nack with reason for pid %s: %s' %
                  (response.pid, response.nack_reason))
            self._NextState()
        elif unpack_exception:
            print unpack_exception
            self.wrapper.Stop()
        else:
            self._HandleResponse(unpacked_data)

    def _QueuedMessageComplete(self, response, unpacked_data,
                               unpack_exception):
        """Called when a queued message is returned."""
        if not self._CheckForAckOrNack(response):
            return

        if response.response_type == OlaClient.RDM_NACK_REASON:
            if (self.outstanding_pid
                    and response.command_class == OlaClient.RDM_GET_RESPONSE
                    and response.pid == self.outstanding_pid.value):
                # we found what we were looking for
                logging.debug(
                    'Received matching queued message, response was NACK')
                self.outstanding_pid = None
                self._NextState()

            elif (response.nack_reason == RDMNack.NR_UNKNOWN_PID
                  and response.pid
                  == self.pid_store.GetName('QUEUED_MESSAGE').value):
                logging.debug('Device doesn\'t support queued messages')
                self._NextState()
            else:
                print 'Got nack for 0x%04hx with reason: %s' % (
                    response.pid, response.nack_reason)

        elif unpack_exception:
            print 'Invalid Param data: %s' % unpack_exception
            self.queued_message_failures += 1
            if self.queued_message_failures >= 10:
                # declare this bad and move on
                self._NextState()
            else:
                self._FetchQueuedMessages()
        else:
            status_messages_pid = self.pid_store.GetName('STATUS_MESSAGES')
            queued_message_pid = self.pid_store.GetName('QUEUED_MESSAGE')
            if (response.pid == status_messages_pid.value
                    and unpacked_data.get('messages', []) == []):
                logging.debug('Got back empty list of STATUS_MESSAGES')
                self._NextState()
                return
            elif response.pid == queued_message_pid.value:
                logging.debug(
                    'Got back QUEUED_MESSAGE, this is a bad responder')
                self._NextState()
                return

            logging.debug('Got pid 0x%hx' % response.pid)
            if self.outstanding_pid and response.pid == self.outstanding_pid.value:
                self._HandleResponse(unpacked_data)
            else:
                self._FetchQueuedMessages()

    def _CheckForAckOrNack(self, response):
        """Check for all the different error conditions.

    Returns:
      True if this response was an ACK or NACK, False for all other cases.
    """
        if not response.status.Succeeded():
            print response.status.message
            self.wrapper.Stop()
            return False

        if response.response_code != OlaClient.RDM_COMPLETED_OK:
            print response.ResponseCodeAsString()
            self.wrapper.Stop()
            return False

        if response.response_type == OlaClient.RDM_ACK_TIMER:
            # schedule the fetch
            logging.debug('Got ack timer for %d ms' % response.ack_timer)
            self.wrapper.AddEvent(response.ack_timer,
                                  self._FetchQueuedMessages)
            return False
        return True
Example #5
0
class ConfigReader(object):
    """A controller that fetches data for responders."""

    (EMPTYING_QUEUE, DMX_START_ADDRESS, DEVICE_LABEL, PERSONALITY) = range(4)

    def __init__(self, wrapper, pid_store):
        self.wrapper = wrapper
        self.pid_store = pid_store
        self.client = self.wrapper.Client()
        self.rdm_api = RDMAPI(self.client, self.pid_store)

    def Run(self, universe, skip_queued_messages):
        """Run the collector.

    Args:
      universe: The universe to collect
      skip_queued_messages:
    """
        self.universe = universe
        self.skip_queued_messages = skip_queued_messages
        self._ResetData()

        self.client.RunRDMDiscovery(self.universe, True, self._HandleUIDList)
        self.wrapper.Run()
        return self.data

    def _ResetData(self):
        """Reset the internal data structures."""
        self.uids = []
        self.uid = None  # the current uid we're fetching from
        self.outstanding_pid = None
        self.work_state = None
        # uid: {'start_address': '', 'personality': '', 'label': ''}
        self.data = {}

    def _GetPid(self, pid):
        self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE, pid,
                         self._RDMRequestComplete)
        logging.debug('Sent %s request' % pid)
        self.outstanding_pid = pid

    def _HandleUIDList(self, state, uids):
        """Called when the UID list arrives."""
        if not state.Succeeded():
            raise DiscoveryException(state.message)

        found_uids = set()
        for uid in uids:
            found_uids.add(uid)
            logging.debug(uid)

        self.uids = list(found_uids)
        self._FetchNextUID()

    def _HandleResponse(self, unpacked_data):
        logging.debug(unpacked_data)
        if self.work_state == self.DMX_START_ADDRESS:
            self._HandleStartAddress(unpacked_data)
        elif self.work_state == self.DEVICE_LABEL:
            self._HandleDeviceLabel(unpacked_data)
        elif self.work_state == self.PERSONALITY:
            self._HandlePersonality(unpacked_data)

    def _HandleStartAddress(self, data):
        """Called when we get a DMX_START_ADDRESS response."""
        this_device = self.data.setdefault(str(self.uid), {})
        this_device['dmx_start_address'] = data['dmx_address']
        self._NextState()

    def _HandleDeviceLabel(self, data):
        """Called when we get a DEVICE_LABEL response."""
        this_device = self.data.setdefault(str(self.uid), {})
        this_device['label'] = data['label']
        self._NextState()

    def _HandlePersonality(self, data):
        """Called when we get a DMX_PERSONALITY response."""
        this_device = self.data.setdefault(str(self.uid), {})
        this_device['personality'] = data['current_personality']
        self._NextState()

    def _NextState(self):
        """Move to the next state of information fetching."""
        if self.work_state == self.EMPTYING_QUEUE:
            # fetch start address
            pid = self.pid_store.GetName('DMX_START_ADDRESS')
            self._GetPid(pid)
            self.work_state = self.DMX_START_ADDRESS
        elif self.work_state == self.DMX_START_ADDRESS:
            # fetch device label
            pid = self.pid_store.GetName('DEVICE_LABEL')
            self._GetPid(pid)
            self.work_state = self.DEVICE_LABEL
        elif self.work_state == self.DEVICE_LABEL:
            # fetch personality
            pid = self.pid_store.GetName('DMX_PERSONALITY')
            self._GetPid(pid)
            self.work_state = self.PERSONALITY
        else:
            # this one is done, onto the next UID
            self._FetchNextUID()

    def _FetchNextUID(self):
        """Start fetching the info for the next UID."""
        if not self.uids:
            self.wrapper.Stop()
            return

        self.uid = self.uids.pop()
        self.personalities = []
        self.sensors = []
        logging.debug('Fetching data for %s' % self.uid)

        self.work_state = self.EMPTYING_QUEUE
        if self.skip_queued_messages:
            # proceed to the fetch now
            self._NextState()
        else:
            self._FetchQueuedMessages()

    def _FetchNextPersonality(self):
        """Fetch the info for the next personality, or proceed to the next state if
       there are none left.
    """
        if self.personalities:
            personality_index = self.personalities.pop(0)
            pid = self.pid_store.GetName('DMX_PERSONALITY_DESCRIPTION')
            self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE,
                             pid, self._RDMRequestComplete,
                             [personality_index])
            logging.debug('Sent DMX_PERSONALITY_DESCRIPTION request')
            self.outstanding_pid = pid
        else:
            self._NextState()

    def _FetchNextSensor(self):
        """Fetch the info for the next sensor, or proceed to the next state if
       there are none left.
    """
        if self.sensors:
            sensor_index = self.sensors.pop(0)
            pid = self.pid_store.GetName('SENSOR_DEFINITION')
            self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE,
                             pid, self._RDMRequestComplete, [sensor_index])
            logging.debug('Sent SENSOR_DEFINITION request')
            self.outstanding_pid = pid
        else:
            self._NextState()

    def _FetchQueuedMessages(self):
        """Fetch messages until the queue is empty."""
        pid = self.pid_store.GetName('QUEUED_MESSAGE')
        self.rdm_api.Get(self.universe, self.uid, PidStore.ROOT_DEVICE, pid,
                         self._QueuedMessageComplete, ['advisory'])
        logging.debug('Sent GET QUEUED_MESSAGE')

    def _QueuedMessageFound(self):
        if self.work_state == self.EMPTYING_QUEUE:
            self._FetchQueuedMessages()

    def _RDMRequestComplete(self, response, unpacked_data, unpack_exception):
        if not self._CheckForAckOrNack(response):
            return

        # at this stage the response is either a ack or nack
        if response.response_type == OlaClient.RDM_NACK_REASON:
            print('Got nack with reason for PID %d: %s' %
                  (response.pid, response.nack_reason))
            self._NextState()
        elif unpack_exception:
            print(unpack_exception)
            self.wrapper.Stop()
        else:
            self._HandleResponse(unpacked_data)

    def _QueuedMessageComplete(self, response, unpacked_data,
                               unpack_exception):
        """Called when a queued message is returned."""
        if not self._CheckForAckOrNack(response):
            return

        if response.response_type == OlaClient.RDM_NACK_REASON:
            if (self.outstanding_pid
                    and response.command_class == OlaClient.RDM_GET_RESPONSE
                    and response.pid == self.outstanding_pid.value):
                # we found what we were looking for
                print("found, but nacked")
                self.outstanding_pid = None
                self._NextState()

            elif (response.nack_reason == RDMNack.NR_UNKNOWN_PID
                  and response.pid
                  == self.pid_store.GetName('QUEUED_MESSAGE').value):
                logging.debug('Device doesn\'t support queued messages')
                self._NextState()
            else:
                print('Got nack for 0x%04hx with reason: %s' %
                      (response.pid, response.nack_reason))

        elif unpack_exception:
            print('Invalid Param data: %s' % unpack_exception)
        else:
            status_messages_pid = self.pid_store.GetName('STATUS_MESSAGES')
            queued_message_pid = self.pid_store.GetName('QUEUED_MESSAGE')
            if (response.pid == status_messages_pid.value
                    and unpacked_data.get('messages', []) == []):
                logging.debug('Got back empty list of STATUS_MESSAGES')
                self._NextState()
                return
            elif response.pid == queued_message_pid.value:
                logging.debug(
                    'Got back QUEUED_MESSAGE, this is a bad responder')
                self._NextState()
                return

            logging.debug('Got pid 0x%hx' % response.pid)
            if self.outstanding_pid and response.pid == self.outstanding_pid.value:
                self._HandleResponse(unpacked_data)
            else:
                self._FetchQueuedMessages()

    def _CheckForAckOrNack(self, response):
        """Check for all the different error conditions.

    Returns:
      True if this response was an ACK or NACK, False for all other cases.
    """
        if not response.status.Succeeded():
            print(response.status.message)
            self.wrapper.Stop()
            return False

        if response.response_code != OlaClient.RDM_COMPLETED_OK:
            print(response.ResponseCodeAsString())
            self.wrapper.Stop()
            return False

        if response.response_type == OlaClient.RDM_ACK_TIMER:
            # schedule the fetch
            logging.debug('Got ack timer for %d ms' % response.ack_timer)
            self.wrapper.AddEvent(response.ack_timer,
                                  self._FetchQueuedMessages)
            return False
        return True
Example #6
0
class ConfigWriter(object):
    """A controller that applies configuration to a universe."""
    (DMX_START_ADDRESS, DEVICE_LABEL, PERSONALITY, COMPLETE) = range(4)

    def __init__(self, wrapper, pid_store):
        self.wrapper = wrapper
        self.pid_store = pid_store
        self.client = self.wrapper.Client()
        self.rdm_api = RDMAPI(self.client, self.pid_store)

    def Run(self, universe, configuration):
        """Run the collector.

    Args:
      universe: The universe to collect
      configuration: The config to apply
    """
        self.universe = universe
        self.configuration = configuration
        self.uids = list(configuration.keys())

        self.client.RunRDMDiscovery(self.universe, True, self._HandleUIDList)
        self.wrapper.Run()

    def _HandleUIDList(self, state, uids):
        """Called when the UID list arrives."""
        if not state.Succeeded():
            raise DiscoveryException(state.message)

        found_uids = set()
        for uid in uids:
            found_uids.add(uid)
            logging.debug(uid)

        for uid in self.configuration.keys():
            if uid not in found_uids:
                print('Device %s has been removed' % uid)
        self._SetNextUID()

    def _SetNextUID(self):
        """Start setting the info for the next UID."""
        if not self.uids:
            self.wrapper.Stop()
            return

        self.uid = self.uids.pop()
        print('Doing %s' % self.uid)
        self.work_state = self.DMX_START_ADDRESS
        self._NextState()

    def _NextState(self):
        """Move to the next state of information fetching."""
        if self.work_state == self.DMX_START_ADDRESS:
            address = self.configuration[self.uid].get('dmx_start_address')
            self.work_state = self.DEVICE_LABEL
            if address is not None:
                pid = self.pid_store.GetName('DMX_START_ADDRESS')
                self._SetPid(pid, [address])
                return

        if self.work_state == self.DEVICE_LABEL:
            label = self.configuration[self.uid].get('label')
            self.work_state = self.PERSONALITY
            if label is not None:
                pid = self.pid_store.GetName('DEVICE_LABEL')
                self._SetPid(pid, [label])
                return

        if self.work_state == self.PERSONALITY:
            personality = self.configuration[self.uid].get('personality')
            self.work_state = self.COMPLETE
            if personality is not None:
                pid = self.pid_store.GetName('DMX_PERSONALITY')
                self._SetPid(pid, [personality])
                return

        # this one is done, onto the next UID
        self._SetNextUID()

    def _SetPid(self, pid, values):
        self.rdm_api.Set(self.universe, self.uid, PidStore.ROOT_DEVICE, pid,
                         self._RDMRequestComplete, values)
        logging.debug('Sent %s request' % pid)
        self.outstanding_pid = pid

    def _RDMRequestComplete(self, response, unpacked_data, unpack_exception):
        if not response.status.Succeeded():
            print(response.status.message)
            self.wrapper.Stop()
            return

        if response.response_code != OlaClient.RDM_COMPLETED_OK:
            print(response.ResponseCodeAsString())
            self.wrapper.Stop()
            return

        if response.response_type == OlaClient.RDM_ACK_TIMER:
            # schedule the fetch
            logging.debug('Got ack timer for %d ms' % response.ack_timer)
            self.wrapper.AddEvent(response.ack_timer,
                                  self._FetchQueuedMessages)
            return

        # at this stage the response is either a ack or nack
        if response.response_type == OlaClient.RDM_NACK_REASON:
            print('Got nack with reason: %s' % response.nack_reason)
        self._NextState()
Example #7
0
class InteractiveModeController(cmd.Cmd):
  """Interactive mode!"""
  def __init__(self, universe, uid, sub_device, pid_location):
    """Create a new InteractiveModeController.

    Args:
      universe:
      uid:
      sub_device:
      pid_location:
    """
    cmd.Cmd.__init__(self)
    self._universe = universe
    self._uid = uid
    self._sub_device = sub_device
    self.pid_store = PidStore.GetStore(pid_location)
    self.wrapper = ClientWrapper()
    self.client = self.wrapper.Client()
    self.rdm_api = RDMAPI(self.client, self.pid_store)
    self._uids = []
    self._response_printer = ResponsePrinter()

    # tuple of (sub_device, command_class, pid)
    self._outstanding_request = None

    self.prompt = '> '

  def emptyline(self):
    pass

  def do_exit(self, s):
    """Exit the interpreter."""
    return True

  def do_EOF(self, s):
    print('')
    return self.do_exit('')

  def do_uid(self, line):
    """Sets the active UID."""
    args = line.split()
    if len(args) != 1:
      print('*** Requires a single UID argument')
      return

    uid = UID.FromString(args[0])
    if uid is None:
      print('*** Invalid UID')
      return

    if uid not in self._uids:
      print('*** UID not found')
      return

    self._uid = uid
    print('Fetching queued messages...')
    self._FetchQueuedMessages()
    self.wrapper.Run()

  def complete_uid(self, text, line, start_index, end_index):
    tokens = line.split()
    if len(tokens) > 1 and text == '':
      return []

    uids = [str(uid) for uid in self._uids if str(uid).startswith(text)]
    return uids

  def do_subdevice(self, line):
    """Sets the sub device."""
    args = line.split()
    if len(args) != 1:
      print('*** Requires a single int argument')
      return

    try:
      sub_device = int(args[0])
    except ValueError:
      print('*** Requires a single int argument')
      return

    if sub_device < 0 or sub_device > PidStore.ALL_SUB_DEVICES:
      print('*** Argument must be between 0 and 0x%hx' %
            PidStore.ALL_SUB_DEVICES)
      return
    self._sub_device = sub_device

  def do_print(self, line):
    """Prints the current universe, UID and sub device."""
    print(textwrap.dedent("""\
      Universe: %d
      UID: %s
      Sub Device: %d""" % (
        self._universe,
        self._uid,
        self._sub_device)))

  def do_uids(self, line):
    """List the UIDs for this universe."""
    self.client.FetchUIDList(self._universe, self._DisplayUids)
    self.wrapper.Run()

  def _DisplayUids(self, state, uids):
    self._uids = []
    if state.Succeeded():
      self._UpdateUids(uids)
      for uid in uids:
        print(str(uid))
    self.wrapper.Stop()

  def do_full_discovery(self, line):
    """Run full RDM discovery for this universe."""
    self.client.RunRDMDiscovery(self._universe, True, self._DiscoveryDone)
    self.wrapper.Run()

  def do_incremental_discovery(self, line):
    """Run incremental RDM discovery for this universe."""
    self.client.RunRDMDiscovery(self._universe, False, self._DiscoveryDone)
    self.wrapper.Run()

  def _DiscoveryDone(self, state, uids):
    if state.Succeeded():
      self._UpdateUids(uids)
    self.wrapper.Stop()

  def _UpdateUids(self, uids):
    self._uids = []
    for uid in uids:
      self._uids.append(uid)

  def do_list(self, line):
    """List the pids available."""
    names = []
    for pid in self.pid_store.Pids():
      names.append('%s (0x%04hx)' % (pid.name.lower(), pid.value))
    if self._uid:
      for pid in self.pid_store.ManufacturerPids(self._uid.manufacturer_id):
        names.append('%s (0x%04hx)' % (pid.name.lower(), pid.value))
    names.sort()
    print('\n'.join(names))

  def do_queued(self, line):
    """Fetch all the queued messages."""
    self._FetchQueuedMessages()
    self.wrapper.Run()

  def do_get(self, line):
    """Send a GET command."""
    self.GetOrSet(PidStore.RDM_GET, line)

  def complete_get(self, text, line, start_index, end_index):
    return self.CompleteGetOrSet(PidStore.RDM_GET, text, line)

  def do_set(self, line):
    """Send a SET command."""
    self.GetOrSet(PidStore.RDM_SET, line)

  def complete_set(self, text, line, start_index, end_index):
    return self.CompleteGetOrSet(PidStore.RDM_SET, text, line)

  def CompleteGetOrSet(self, request_type, text, line):
    if len(line.split(' ')) > 2:
      return []
    pids = [pid for pid in self.pid_store.Pids()
            if pid.name.lower().startswith(text)]
    if self._uid:
      for pid in self.pid_store.ManufacturerPids(self._uid.manufacturer_id):
        if pid.name.lower().startswith(text):
          pids.append(pid)

    # now check if this type of request is supported
    pid_names = sorted([pid.name.lower() for pid in pids
                        if pid.RequestSupported(request_type)])

    return pid_names

  def GetOrSet(self, request_type, line):
    if self._uid is None:
      print('*** No UID selected, use the uid command')
      return

    args = line.split()
    command = 'get'
    if request_type == PidStore.RDM_SET:
      command = 'set'
    if len(args) < 1:
      print('%s <pid> [args]' % command)
      return

    pid = None
    try:
      pid = self.pid_store.GetPid(int(args[0], 0), self._uid.manufacturer_id)
    except ValueError:
      pid = self.pid_store.GetName(args[0].upper(), self._uid.manufacturer_id)
    if pid is None:
      print('*** Unknown pid %s' % args[0])
      return

    if not pid.RequestSupported(request_type):
      print('*** PID does not support command')
      return

    if request_type == PidStore.RDM_SET:
      method = self.rdm_api.Set
    else:
      method = self.rdm_api.Get

    try:
      if method(self._universe,
                self._uid,
                self._sub_device,
                pid,
                self._RDMRequestComplete,
                args[1:]):
        self._outstanding_request = (self._sub_device, request_type, pid)
        self.wrapper.Run()
    except PidStore.ArgsValidationError as e:
      args, help_string = pid.GetRequestDescription(request_type)
      print('Usage: %s %s %s' % (command, pid.name.lower(), args))
      print(help_string)
      print('')
      print('*** %s' % e)
      return

  def _FetchQueuedMessages(self):
    """Fetch messages until the queue is empty."""
    pid = self.pid_store.GetName('QUEUED_MESSAGE', self._uid.manufacturer_id)
    self.rdm_api.Get(self._universe,
                     self._uid,
                     PidStore.ROOT_DEVICE,
                     pid,
                     self._QueuedMessageComplete,
                     ['error'])

  def _RDMRequestComplete(self, response, unpacked_data, unpack_exception):
    if not self._CheckForAckOrNack(response):
      return

    # at this stage the response is either a ack or nack
    self._outstanding_request = None
    self.wrapper.Stop()

    if response.response_type == OlaClient.RDM_NACK_REASON:
      print('Got nack with reason: %s' % response.nack_reason)
    elif unpack_exception:
      print(unpack_exception)
    else:
      self._response_printer.PrintResponse(self._uid, response.pid,
                                           unpacked_data)

    if response.queued_messages:
      print('%d queued messages remain' % response.queued_messages)

  def _QueuedMessageComplete(self, response, unpacked_data, unpack_exception):
    if not self._CheckForAckOrNack(response):
      return

    if response.response_type == OlaClient.RDM_NACK_REASON:
      if (self._outstanding_request and
          response.sub_device == self._outstanding_request[0] and
          response.command_class == self._outstanding_request[1] and
          response.pid == self._outstanding_request[2].value):
        # we found what we were looking for
        self._outstanding_request = None
        self.wrapper.StopIfNoEvents()

      elif (response.nack_reason == RDMNack.NR_UNKNOWN_PID and
            response.pid == self.pid_store.GetName('QUEUED_MESSAGE').value):
        print('Device doesn\'t support queued messages')
        self.wrapper.StopIfNoEvents()
      else:
        print('Got nack for 0x%04hx with reason: %s' % (
            response.pid, response.nack_reason))

    elif unpack_exception:
      print('Invalid Param data: %s' % unpack_exception)
    else:
      status_messages_pid = self.pid_store.GetName('STATUS_MESSAGES')
      if (response.pid == status_messages_pid.value and
          unpacked_data.get('messages', []) == []):
        self.wrapper.StopIfNoEvents()
        return

      self._response_printer.PrintResponse(self._uid,
                                           response.pid,
                                           unpacked_data)

    if response.queued_messages:
      self._FetchQueuedMessages()
    else:
      self.wrapper.StopIfNoEvents()

  def _CheckForAckOrNack(self, response):
    """Check for all the different error conditions.

    Returns:
      True if this response was an ACK or NACK, False for all other cases.
    """
    if not response.status.Succeeded():
      print(response.status.message)
      self.wrapper.Stop()
      return False

    if response.response_code != OlaClient.RDM_COMPLETED_OK:
      print(response.ResponseCodeAsString())
      self.wrapper.Stop()
      return False

    if response.response_type == OlaClient.RDM_ACK_TIMER:
      # schedule the fetch
      self.wrapper.AddEvent(response.ack_timer, self._FetchQueuedMessages)
      return False

    return True
Example #8
0
    def testGetWithResponse(self):
        """uses client to send an RDM get with mocked olad.
    Regression test that confirms sent message is correct and
    sends fixed response message."""
        sockets = socket.socketpair()
        wrapper = ClientWrapper(sockets[0])
        pid_store = PidStore.GetStore(pid_store_path)
        client = wrapper.Client()
        rdm_api = RDMAPI(client, pid_store)

        class results:
            got_request = False
            got_response = False

        def DataCallback(self):
            # request and response for
            # ola_rdm_get.py -u 1 --uid 7a70:ffffff00 device_info
            # against olad dummy plugin
            # enable logging in rpc/StreamRpcChannel.py
            data = sockets[1].recv(4096)
            expected = binascii.unhexlify(
                "29000010080110001a0a52444d436f6d6d616e6422170801120908f0f4011500"
                "ffffff180020602a0030003800")
            self.assertEqual(data,
                             expected,
                             msg="Regression check failed. If protocol change "
                             "was intended set expected to: " +
                             str(binascii.hexlify(data)))
            results.got_request = True
            response = binascii.unhexlify(
                "3f0000100802100022390800100018002213010000017fff0000000300050204"
                "00010000032860300038004a0908f0f4011500ffffff520908f0f40115ac1100"
                "02580a")
            sent_bytes = sockets[1].send(response)
            self.assertEqual(sent_bytes, len(response))

        def ResponseCallback(self, response, data, unpack_exception):
            results.got_response = True
            self.assertEqual(response.response_type, client.RDM_ACK)
            self.assertEqual(response.pid, 0x60)
            self.assertEqual(data["dmx_footprint"], 5)
            self.assertEqual(data["software_version"], 3)
            self.assertEqual(data["personality_count"], 4)
            self.assertEqual(data["device_model"], 1)
            self.assertEqual(data["current_personality"], 2)
            self.assertEqual(data["protocol_major"], 1)
            self.assertEqual(data["protocol_minor"], 0)
            self.assertEqual(data["product_category"], 32767)
            self.assertEqual(data["dmx_start_address"], 1)
            self.assertEqual(data["sub_device_count"], 0)
            self.assertEqual(data["sensor_count"], 3)
            wrapper.AddEvent(0, wrapper.Stop)

        wrapper._ss.AddReadDescriptor(sockets[1], lambda: DataCallback(self))

        uid = UID.FromString("7a70:ffffff00")
        pid = pid_store.GetName("DEVICE_INFO")
        rdm_api.Get(1, uid, 0, pid,
                    lambda x, y, z: ResponseCallback(self, x, y, z))

        wrapper.Run()

        sockets[0].close()
        sockets[1].close()

        self.assertTrue(results.got_request)
        self.assertTrue(results.got_response)
Example #9
0
    def testSetParamsWithNack(self):
        """uses client to send an RDM set with mocked olad.
    Regression test that confirms sent message is correct and
    sends fixed response message."""
        sockets = socket.socketpair()
        wrapper = ClientWrapper(sockets[0])
        pid_store = PidStore.GetStore(pid_store_path)
        client = wrapper.Client()
        rdm_api = RDMAPI(client, pid_store)

        class results:
            got_request = False
            got_response = False

        def DataCallback(self):
            # request and response for
            # ola_rdm_set.py -u 1 --uid 7a70:ffffff00 DMX_PERSONALITY 10
            # against olad dummy plugin
            # enable logging in rpc/StreamRpcChannel.py
            data = sockets[1].recv(4096)
            expected = binascii.unhexlify(
                "2b000010080110001a0a52444d436f6d6d616e6422190801120908f0f401150"
                "0ffffff180020e0012a010a30013800")
            self.assertEqual(data,
                             expected,
                             msg="Regression check failed. If protocol change "
                             "was intended set expected to: " +
                             str(binascii.hexlify(data)))
            results.got_request = True
            response = binascii.unhexlify(
                "2f0000100802100022290800100218002202000628e001300138004a0908f0f"
                "4011500ffffff520908f0f40115ac107de05831")
            sent_bytes = sockets[1].send(response)
            self.assertEqual(sent_bytes, len(response))

        def ResponseCallback(self, response, data, unpack_exception):
            results.got_response = True
            self.assertEqual(response.response_type, client.RDM_NACK_REASON)
            self.assertEqual(response.pid, 0xe0)
            self.assertEqual(response.nack_reason,
                             RDMNack.NR_DATA_OUT_OF_RANGE)
            wrapper.AddEvent(0, wrapper.Stop)

        wrapper._ss.AddReadDescriptor(sockets[1], lambda: DataCallback(self))

        uid = UID.FromString("7a70:ffffff00")
        pid = pid_store.GetName("DMX_PERSONALITY")
        rdm_api.Set(1,
                    uid,
                    0,
                    pid,
                    lambda x, y, z: ResponseCallback(self, x, y, z),
                    args=["10"])

        wrapper.Run()

        sockets[0].close()
        sockets[1].close()

        self.assertTrue(results.got_request)
        self.assertTrue(results.got_response)
Example #10
0
    def testGetParamsWithResponse(self):
        """uses client to send an RDM get with mocked olad.
    Regression test that confirms sent message is correct and
    sends fixed response message."""
        sockets = socket.socketpair()
        wrapper = ClientWrapper(sockets[0])
        pid_store = PidStore.GetStore(pid_store_path)
        client = wrapper.Client()
        rdm_api = RDMAPI(client, pid_store)

        class results:
            got_request = False
            got_response = False

        def DataCallback(self):
            # request and response for
            # ola_rdm_get.py -u 1 --uid 7a70:ffffff00 DMX_PERSONALITY_DESCRIPTION 2
            # against olad dummy plugin
            # enable logging in rpc/StreamRpcChannel.py
            data = sockets[1].recv(4096)
            expected = binascii.unhexlify(
                "2b000010080110001a0a52444d436f6d6d616e6422190801120908f0f4011500"
                "ffffff180020e1012a010230003800")
            self.assertEqual(data,
                             expected,
                             msg="Regression check failed. If protocol change "
                             "was intended set expected to: " +
                             str(binascii.hexlify(data)))
            results.got_request = True
            response = binascii.unhexlify(
                "3d0000100802100022370800100018002210020005506572736f6e616c697479"
                "203228e101300038004a0908f0f4011500ffffff520908f0f40115ac107de058"
                "29")
            sent_bytes = sockets[1].send(response)
            self.assertEqual(sent_bytes, len(response))

        def ResponseCallback(self, response, data, unpack_exception):
            results.got_response = True
            self.assertEqual(response.response_type, client.RDM_ACK)
            self.assertEqual(response.pid, 0xe1)
            self.assertEqual(data['personality'], 2)
            self.assertEqual(data['slots_required'], 5)
            self.assertEqual(data['name'], "Personality 2")
            wrapper.AddEvent(0, wrapper.Stop)

        wrapper._ss.AddReadDescriptor(sockets[1], lambda: DataCallback(self))

        uid = UID.FromString("7a70:ffffff00")
        pid = pid_store.GetName("DMX_PERSONALITY_DESCRIPTION")
        rdm_api.Get(1,
                    uid,
                    0,
                    pid,
                    lambda x, y, z: ResponseCallback(self, x, y, z),
                    args=["2"])

        wrapper.Run()

        sockets[0].close()
        sockets[1].close()

        self.assertTrue(results.got_request)
        self.assertTrue(results.got_response)