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))
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)
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(self.dnp3_point_name(name)) for name in self.volttron_points} except Exception as e: raise DNP3Exception(e.message)
def get_selector_block(self, point_name, edit_selector): """ Return a dictionary of point values for a given selector block. :param point_name: Name of the first point in the selector block. :param edit_selector: The index (edit selector) of the block. :return: A dictionary of point values. """ _log.info('Get point values for selector block {}, index {}'.format(point_name, edit_selector)) point_values = {} try: # Create a dictionary of all point values in the block, indexed by name. Expand any arrays. for pt in self._get_selector_block_points(point_name, edit_selector): point_def = pt.point_def point_value = pt.unwrapped_value() if point_def.is_array_point: head_point_def = self.point_definitions.point_named(pt.name) # Construct array JSON row = 0 if point_def.is_array_head_point else point_def.row col = 0 if point_def.is_array_head_point else point_def.column col_name = head_point_def.array_points[col]['name'] # Expand the JSON structure as needed if pt.name not in point_values: point_values[pt.name] = [] if row + 1 > len(point_values[pt.name]): point_values[pt.name].append({}) point_values[pt.name][row][col_name] = point_value else: point_values[pt.name] = point_value except Exception as e: raise DNP3Exception(e.message) return point_values
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.message)
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: if type(self.points) == str: # There's something odd here. The point and function definitions are defined in the # config file using a 'config://' entry (previously used only by MasterDriveAgent). # It seems like this should have been resolved to the registry entry at which the # 'config://' entry points, and in that case 'self.points' should already be # a json structure. But instead, it's still the string 'config://mesa_points.config'. # The call to get_from_config_store() below works around the issue by fetching the linked # registry entry. point_defs = self.get_from_config_store(self.points) else: point_defs = self.points self.point_definitions = PointDefinitions() self.point_definitions.load_points(point_defs) 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))
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
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))
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(self.dnp3_point_name(name)) for name in point_list} except Exception as e: raise DNP3Exception(e.message)
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.iteritems(): self.update_input_point(self.get_point_named(self.dnp3_point_name(point_name)), value) except Exception as e: raise DNP3Exception(e.message)
def _get_point_by_index(self, group, index): """ (Internal) Look up the most-recently-received value for a given point (no debug trace). @param group: The group number of a DNP3 point. @param index: The index of a DNP3 point. @return: The (unwrapped) value of a received point. """ try: point_value = self.get_current_point_value(PointDefinition.point_type_for_group(group), index) return point_value.unwrapped_value() if point_value else None except Exception as e: raise DNP3Exception(e.message)
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.message)
def _get_point(self, point_name): """ (Internal) Look up the most-recently-received value for a given point (no debug trace). @param point_name: The name of a DNP3 PointDefinition. @return: The (unwrapped) value of a received point. """ try: point_def = self.point_definitions.get_point_named(point_name) point_value = self.get_current_point_value(point_def.point_type, point_def.index) return point_value.unwrapped_value() if point_value else None except Exception as e: raise DNP3Exception(e.message)
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.message)
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.message)
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))
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, {}) group = point_properties.get('group', None) index = point_properties.get('index', None) point_type = PointDefinition.point_type_for_group(group) try: if point_type == POINT_TYPE_ANALOG_INPUT: wrapped_value = opendnp3.Analog(value) elif point_type == POINT_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.message)