Exemple #1
0
    def add_point_value(self, point_value, current_array=None):
        """Add a received PointValue as a Step in the current Function. Return the Step."""
        step_def = self.definition[point_value.point_def]
        step_number = step_def.step_number
        if self.last_step is None:
            self.add_step(step_def, self, point_value)
        else:
            last_received_step_number = self.last_step.definition.step_number
            if step_number == last_received_step_number:
                if not point_value.point_def.is_array_point:
                    raise DNP3Exception('Duplicate step number {} received'.format(step_number))
                # An array point was received for an existing step. Update the step's value.
                self.last_step.value = current_array
            else:
                if step_number < last_received_step_number:
                    # The Function's steps must be received in step-number order
                    if not self.complete:
                        raise DNP3Exception('Step {} received after {}'.format(step_number, last_received_step_number))
                    # Since the old function was complete, treat this as the first step of a new function.
                    self.complete = False
                    self.steps = []

                self.check_for_missing_steps(step_def)
                self.add_step(step_def, self, point_value)

        if not self.missing_step_numbers():
            self.complete = True

        return self.last_step
Exemple #2
0
    def _apply_point_update(point_def, point_index, value):
        """
            Set an input point in the outstation database. This may send its PointValue to the Master.

        :param point_def: A PointDefinition.
        :param point_index: A numeric index for the point.
        :param value: A value to send (unwrapped, simple data type).
        """
        data_type = point_def.data_type
        if data_type == DATA_TYPE_ANALOG_INPUT:
            wrapped_val = opendnp3.Analog(float(value))
            if isinstance(value,
                          bool) or not isinstance(value, numbers.Number):
                # Invalid data type
                raise DNP3Exception('Received {} value for {}.'.format(
                    type(value), point_def))
        elif data_type == DATA_TYPE_BINARY_INPUT:
            wrapped_val = opendnp3.Binary(value)
            if not isinstance(value, bool):
                # Invalid data type
                raise DNP3Exception('Received {} value for {}.'.format(
                    type(value), point_def))
        else:
            # The agent supports only DNP3's Analog and Binary point types at this time.
            raise DNP3Exception('Unsupported point type {}'.format(data_type))
        if wrapped_val is not None:
            DNP3Outstation.apply_update(wrapped_val, point_index)
        _log.debug('Sent DNP3 point {}, value={}'.format(
            point_def, wrapped_val.value))
Exemple #3
0
 def step_definition_for_point(self, point_definition):
     """Return a StepDefinition for a given point. If there isn't exactly one matching StepDefinition, complain."""
     name = point_definition.name
     step_list = self._step_definitions_by_name().get(name, [])
     if not step_list:
         raise DNP3Exception('No StepDefinition named {}'.format(name))
     if len(step_list) > 1:
         raise DNP3Exception('Found multiple StepDefinitions named {}'.format(name))
     return step_list[0]
Exemple #4
0
 def update_array_for_point(self, point_value):
     """A received point belongs to a PointArray. Update it."""
     if point_value.point_def.is_array_head_point:
         self._current_array = PointArray(point_value.point_def)
     elif self._current_array is None:
         raise DNP3Exception(
             'Array point received, but there is no current Array.')
     elif not self._current_array.contains_index(point_value.index):
         raise DNP3Exception(
             'Received Array point outside of current Array.')
     self._current_array.add_point_value(point_value)
Exemple #5
0
    def get_configured_points(self):
        """
            Look up the most-recently-received value of each configured point.

        @return: A dictionary of point values, indexed by their point names.
        """
        if self.volttron_points is None:
            raise DNP3Exception('DNP3 points have not been configured')

        _log.info('Getting all DNP3 configured point values')
        try:
            return {
                name: self.get_point(name)
                for name in self.volttron_points
            }
        except Exception as e:
            raise DNP3Exception(e)
Exemple #6
0
 def set_current_function(self, func):
     """Set the Function being accumulated by the Outstation to the supplied value, which might be None."""
     if func:
         if self.all_functions_supported_by_default != "True":
             if not func.definition.supported:
                 raise DNP3Exception('Received a point for unsupported {}'.format(func))
     self._current_func = func
     return func
Exemple #7
0
    def add_point_value(self,
                        point_value,
                        current_array=None,
                        function_validation=False):
        """
            Add a received PointValue as a Step in the current Function. Return the Step.

        :param point_value: point value
        :param current_array: current array
        :param function_validation: defaults to False. If function_validation is True,
            raise DNP3Exception when getting an error while adding a new step to the current function.
            If function_validation is False, reset current function to None if missing mandatory step,
            set the adding step as the first step of the current function if step is not in order,
            or replace the last step by the adding step if step is duplicated.
        """
        step_def = self.definition[point_value.point_def]
        step_number = step_def.step_number
        if not self.last_step:
            self.add_step(step_def, point_value, function_validation)
        else:
            last_received_step_number = self.last_step.definition.step_number
            if step_number != last_received_step_number:
                if step_number < last_received_step_number:
                    if self.next_remaining_mandatory_step_number:
                        if function_validation:
                            raise DNP3Exception(
                                'Step {} received after {}'.format(
                                    step_number, last_received_step_number))
                    # Since the old function was complete, treat this as the first step of a new function.
                    self.steps = []
                self.add_step(step_def, point_value, function_validation)
            else:
                if not point_value.point_def.is_array_point:
                    if function_validation:
                        raise DNP3Exception(
                            'Duplicate step number {} received'.format(
                                step_number))
                    self.steps.pop()
                    self.add_step(step_def, point_value, function_validation)
                else:
                    # An array point was received for an existing step. Update the step's value.
                    self.last_step.value = current_array

        return self.last_step
Exemple #8
0
 def update_function_for_point_value(self, point_value):
     """Add point_value to the current Function if appropriate."""
     try:
         current_function = self.current_function_for(point_value.point_def)
         if current_function is None:
             return None
         if point_value.point_def.is_array_point:
             self.update_array_for_point(point_value)
         current_function.add_point_value(point_value, current_array=self._current_array)
     except DNP3Exception as err:
         raise DNP3Exception('Error updating function: {}'.format(err))
Exemple #9
0
    def dnp3_point_name(self, point_name):
        """
            Return a point's DNP3 point name, mapped from its VOLTTRON point name if necessary.

            If VOLTTRON point names were configured (by the DNP device driver), map them to DNP3 point names.
        """
        dnp3_point_name = self.volttron_points.get(
            point_name, '') if self.volttron_points else point_name
        if not dnp3_point_name:
            raise DNP3Exception(
                'No configured point for {}'.format(point_name))
        return dnp3_point_name
Exemple #10
0
    def get_points(self, point_list):
        """
            Look up the most-recently-received value of each configured output point.

        @param point_list: A list of point names.
        @return: A dictionary of point values, indexed by their point names.
        """
        _log.info(
            'Getting values for the following points: {}'.format(point_list))
        try:
            return {name: self.get_point(name) for name in point_list}
        except Exception as e:
            raise DNP3Exception(e)
Exemple #11
0
 def current_function_for(self, new_point_def):
     """A point was received. Return the current Function, updating it if necessary."""
     new_point_function_def = self.function_definitions.get(new_point_def, None)
     if new_point_function_def is None:
         return None
     if self.current_function and new_point_function_def != self.current_function.definition:
         if not self.current_function.complete:
             raise DNP3Exception('Mismatch: {} does not belong to {}'.format(new_point_def, self.current_function))
         # The current Function is done, and a new Function is arriving. Discard the old one.
         self.set_current_function(None)
     if not self.current_function:
         self.set_current_function(Function(new_point_function_def))
     return self.current_function
Exemple #12
0
    def set_point(self, point_name, value):
        """
            Set the value of a given input point.

        @param point_name: The point name of a DNP3 PointDefinition.
        @param value: The value to set. The value's data type must match the one in the DNP3 PointDefinition.
        """
        _log.info('Setting DNP3 {} point value = {}'.format(point_name, value))
        try:
            self.update_input_point(
                self.get_point_named(self.dnp3_point_name(point_name)), value)

        except Exception as e:
            raise DNP3Exception(e)
Exemple #13
0
    def set_points(self, point_dict):
        """
            Set point values for a dictionary of points.

        @param point_dict: A dictionary of {point_name: value} for a list of DNP3 points to set.
        """
        _log.info('Setting DNP3 point values: {}'.format(point_dict))
        try:
            for point_name, value in point_dict.items():
                self.update_input_point(
                    self.get_point_named(self.dnp3_point_name(point_name)),
                    value)
        except Exception as e:
            raise DNP3Exception(e)
Exemple #14
0
    def get_point_by_index(self, data_type, index):
        """
            Look up the most-recently-received value for a given point.

        @param data_type: The data_type of a DNP3 point.
        @param index: The index of a DNP3 point.
        @return: The (unwrapped) value of a received point.
        """
        _log.info('Getting point value for data_type {} and index {}'.format(
            data_type, index))
        try:
            point_value = self.get_current_point_value(data_type, index)
            return point_value.unwrapped_value() if point_value else None
        except Exception as e:
            raise DNP3Exception(e)
Exemple #15
0
    def get_point(self, point_name):
        """
            Look up the most-recently-received value for a given output point.

        @param point_name: The point name of a DNP3 PointDefinition.
        @return: The (unwrapped) value of a received point.
        """
        _log.info('Getting point value for {}'.format(point_name))
        try:
            point_name = self.dnp3_point_name(point_name)
            point_def = self.point_definitions.get_point_named(point_name)
            point_value = self.get_current_point_value(point_def.data_type,
                                                       point_def.index)
            return point_value.unwrapped_value() if point_value else None
        except Exception as e:
            raise DNP3Exception(e)
Exemple #16
0
 def load_function_definitions(self):
     """Populate the FunctionDefinitions repository from JSON in the config store."""
     _log.debug('Loading MESA function definitions')
     try:
         if type(self.functions) == str:
             function_defs = self.get_from_config_store(self.functions)
         else:
             function_defs = self.functions
         self.function_definitions = FunctionDefinitions(self.point_definitions)
         self.function_definitions.load_functions(function_defs['functions'])
     except (AttributeError, TypeError) as err:
         if self._local_function_definitions_path:
             _log.warning("Attempting to load Function Definitions from local path.")
             self.function_definitions = FunctionDefinitions(
                 self.point_definitions,
                 function_definitions_path=self._local_function_definitions_path)
         else:
             raise DNP3Exception("Failed to load Function Definitions from config store: {}".format(err))
Exemple #17
0
 def publish_data(self, topic, msg):
     """Publish a payload to the message bus."""
     try:
         self.vip.pubsub.publish(peer='pubsub',
                                 topic=topic,
                                 headers={
                                     headers.TIMESTAMP:
                                     utils.format_timestamp(
                                         utils.get_aware_utc_now())
                                 },
                                 message=msg)
     except Exception as err:
         if os.environ.get('UNITTEST', False):
             _log.debug(
                 'Disregarding publish_data exception during unit test')
         else:
             raise DNP3Exception(
                 'Error publishing topic {}, message {}: {}'.format(
                     topic, msg, err))
Exemple #18
0
    def load_point_definitions(self):
        """
            Load and cache a dictionary of PointDefinitions from a json list.

            Index the dictionary by point_type and point index.
        """
        _log.debug('Loading DNP3 point definitions.')
        try:
            self.point_definitions = PointDefinitions()
            self.point_definitions.load_points(self.points)
        except (AttributeError, TypeError) as err:
            if self._local_point_definitions_path:
                _log.warning(
                    "Attempting to load point definitions from local path.")
                self.point_definitions = PointDefinitions(
                    point_definitions_path=self._local_point_definitions_path)
            else:
                raise DNP3Exception(
                    "Failed to load point definitions from config store: {}".
                    format(err))
Exemple #19
0
 def current_function_for(self, new_point_def):
     """A point was received. Return the current Function, updating it if necessary."""
     new_point_function_def = self.function_definitions.get_fdef_for_pdef(
         new_point_def)
     if new_point_function_def is None:
         return None
     if self._current_functions:
         current_funcs = dict()
         for func_def in new_point_function_def:
             val = self._current_functions.pop(func_def.function_id, None)
             if val:
                 current_funcs.update({func_def.function_id: val})
         self._current_functions = current_funcs
     else:
         for func_def in new_point_function_def:
             if not self.all_functions_supported_by_default and not func_def.supported:
                 raise DNP3Exception(
                     'Received a point for unsupported {}'.format(func_def))
             self._current_functions[func_def.function_id] = Function(
                 func_def)
     return self._current_functions
Exemple #20
0
 def update_function_for_point_value(self, point_value):
     """Add point_value to the current Function if appropriate."""
     error_msg = None
     current_functions = self.current_function_for(point_value.point_def)
     if not current_functions:
         return None
     for function_id, current_function in current_functions.items():
         try:
             if point_value.point_def.is_array_point:
                 self.update_array_for_point(point_value)
             current_function.add_point_value(
                 point_value,
                 current_array=self._current_array,
                 function_validation=self.function_validation)
         except (DNP3Exception, FunctionException) as err:
             current_functions.pop(function_id)
             if type(err) == DNP3Exception:
                 error_msg = err
     if error_msg and not current_functions:
         raise DNP3Exception(
             'Error updating function: {}'.format(error_msg))
Exemple #21
0
    def _set_point(self, point_name, value):
        """
            (Internal) Set the value of a given input point (no debug trace).

        @param point_name: The VOLTTRON point name of a DNP3 PointDefinition.
        @param value: The value to set. The value's data type must match the one in the DNP3 PointDefinition.
        """
        point_properties = self.volttron_points.get(point_name, {})
        data_type = point_properties.get('data_type', None)
        index = point_properties.get('index', None)
        try:
            if data_type == DATA_TYPE_ANALOG_INPUT:
                wrapped_value = opendnp3.Analog(value)
            elif data_type == DATA_TYPE_BINARY_INPUT:
                wrapped_value = opendnp3.Binary(value)
            else:
                raise Exception(
                    'Unexpected data type for DNP3 point named {0}'.format(
                        point_name))
            DNP3Outstation.apply_update(wrapped_value, index)
        except Exception as e:
            raise DNP3Exception(e)
Exemple #22
0
    def _process_point_value(self, point_value):
        """
            A PointValue was received from the Master. Process its payload.

        :param point_value: A PointValue.
        """
        try:
            point_val = super(MesaAgent, self)._process_point_value(point_value)
            if point_val:
                self.update_function_for_point_value(point_val)
                # If we don't have a function, we don't care.
                if self.current_function:
                    if self.current_function.has_input_point():
                        self.update_input_point(
                            self.get_point_named(self.current_function.input_point_name()),
                            point_val.unwrapped_value()
                        )
                    if self.current_function.publish_now():
                        self.publish_function_step(self.current_function.last_step)

        except Exception as err:
            self.set_current_function(None)             # Discard the current function
            raise DNP3Exception('Error processing point value: {}'.format(err))
Exemple #23
0
    def add_step(self, step_def, value, function_validation=False):
        """
            Add a step to function if no mandatory step missing and return the step, raise exception otherwise.

        :param step_def: step definition to add to function
        :param value: value of the point in step_def
        :param function_validation: defaults to False.
            When there is mandatory step missing, raise DNP3Exception if function_validation is True,
            raise FunctionException otherwise.
            FunctionException is used for _process_point_value in Mesa agent, if the FunctionException is raised,
            reset current function to None and process the next point as the first step of a new function.
        """
        # Check for missing mandatory steps up to the current step
        if self.next_remaining_mandatory_step_number \
                and step_def.step_number > self.next_remaining_mandatory_step_number:
            exception_message = '{} is missing Mandatory step number {}'.format(
                self, self.next_remaining_mandatory_step_number)
            if function_validation:
                raise DNP3Exception(exception_message)
            raise FunctionException(exception_message)
        # add current step to self.steps
        step_value = Step(step_def, self, value)
        self.steps.append(step_value)
        return step_value
Exemple #24
0
    def get_point_definitions(self, point_name_list):
        """
            For each DNP3 point name in point_name_list, return a dictionary with each of the point definitions.

            The returned dictionary looks like this:

            {
                "point_name1": {
                    "property1": "property1_value",
                    "property2": "property2_value",
                    ...
                },
                "point_name2": {
                    "property1": "property1_value",
                    "property2": "property2_value",
                    ...
                }
            }

            If a definition cannot be found for a point name, it is omitted from the returned dictionary.

        :param point_name_list: A list of point names.
        :return: A dictionary of point definitions.
        """
        _log.info('Fetching a list of DNP3 point definitions for {}'.format(
            point_name_list))
        try:
            response = {}
            for name in point_name_list:
                point_def = self.point_definitions.get_point_named(
                    self.dnp3_point_name(name))
                if point_def is not None:
                    response[name] = point_def.as_json()
            return response
        except Exception as e:
            raise DNP3Exception(e)
Exemple #25
0
    def _process_point_value(self, point_value):
        """
            A PointValue was received from the Master. Process its payload.

        :param point_value: A PointValue.
        """
        try:
            point_val = super(MesaAgent,
                              self)._process_point_value(point_value)

            if point_val:
                if point_val.point_def.is_selector_block:
                    self._current_block = {
                        'name': point_val.point_def.name,
                        'index': float(point_val.value)
                    }
                    _log.debug(
                        'Starting to receive Selector Block {name} at Edit Selector {index}'
                        .format(**self._current_block))

                # Publish mesa/point if the point action is PUBLISH or PUBLISH_AND_RESPOND
                if point_val.point_def.action in (PUBLISH,
                                                  PUBLISH_AND_RESPOND):
                    self.publish_point_value(point_value)

                self.update_function_for_point_value(point_val)

                if self._current_functions:
                    for current_func_id, current_func in self._current_functions.items(
                    ):
                        # if step action is ACTION_ECHO or ACTION_ECHO_AND_PUBLISH
                        if current_func.has_input_point():
                            self.update_input_point(
                                self.get_point_named(
                                    current_func.input_point_name()),
                                point_val.unwrapped_value())

                        # if step is the last curve or schedule step
                        if self._current_block and point_val.point_def == current_func.definition.last_step.point_def:
                            current_block_name = self._current_block['name']
                            self._selector_block.setdefault(
                                current_block_name, dict())
                            self._selector_block[current_block_name][
                                self.
                                _current_block['index']] = current_func.steps

                            _log.debug(
                                'Saved Selector Block {} at Edit Selector {}: {}'
                                .format(
                                    self._current_block['name'],
                                    self._current_block['index'],
                                    self.get_selector_block(
                                        self._current_block['name'],
                                        self._current_block['index'])))

                            self._current_block = dict()

                        # if step reference to a curve or schedule function
                        func_ref = current_func.last_step.definition.func_ref
                        if func_ref:
                            block_name = self.function_definitions[
                                func_ref].first_step.name
                            block_index = float(point_val.value)
                            if not self._selector_block.get(
                                    block_name, dict()).get(block_index, None):
                                error_msg = 'Have not received data for Selector Block {} at Edit Selector {}'
                                raise DNP3Exception(
                                    error_msg.format(block_name, block_index))
                            current_edit_selector = {
                                'name': block_name,
                                'index': block_index
                            }
                            if current_edit_selector not in self._edit_selectors:
                                self._edit_selectors.append(
                                    current_edit_selector)

                        # if step action is ACTION_PUBLISH, ACTION_ECHO_AND_PUBLISH, or ACTION_PUBLISH_AND_RESPOND
                        if current_func.publish_now():
                            self.publish_function_step(current_func.last_step)

                        # if current function is completed
                        if current_func.complete:
                            self._current_functions.pop(current_func_id)
                            self._edit_selectors = list()

        except (DNP3Exception, FunctionException) as err:
            self._current_functions = dict()
            self._edit_selectors = list()
            if type(err) == DNP3Exception:
                raise DNP3Exception(
                    'Error processing point value: {}'.format(err))
Exemple #26
0
 def check_for_missing_steps(self, step_def):
     """All Mandatory steps with smaller step numbers must be received prior to the current step."""
     for n in self.missing_step_numbers():
         if step_def.step_number > n:
             raise DNP3Exception('Function {} is missing Mandatory step number {}'.format(self, n))