Exemple #1
0
 def __post_init__(self):
     if self.targets is None and self.targets_by_type is None:
         return
     elif self.targets_by_type is None:
         list_of_targets = [asdict(target) if is_dataclass(target) else target for target in self.targets]
         self.targets_by_type = group_targets_by_type(list_of_targets)
     elif self.targets is None:
         self.targets = [Target(**target) for target in ungroup_targets_by_type(self.targets_by_type)]
     elif self.targets is not None and self.targets_by_type is not None:
         list_of_targets = [asdict(target) if is_dataclass(target) else target for target in self.targets]
         if group_targets_by_type(list_of_targets) != self.targets_by_type:
             raise ValueError("You assigned both 'targets' and 'targets_by_type' in your event, "
                              "but the two were not consistent with each other. "
                              f"You supplied 'targets' = {self.targets} and "
                              f"'targets_by_type' = {self.targets_by_type}")
Exemple #2
0
 def __post_init__(self):
     if self.active_period is None:
         dtstart = min([
             i['dtstart'] if isinstance(i, dict) else i.dtstart
             for s in self.event_signals for i in s.intervals
         ])
         duration = max([
             i['dtstart'] + i['duration']
             if isinstance(i, dict) else i.dtstart + i.duration
             for s in self.event_signals for i in s.intervals
         ]) - dtstart
         self.active_period = ActivePeriod(dtstart=dtstart,
                                           duration=duration)
     if self.targets is None and self.targets_by_type is None:
         raise ValueError(
             "You must supply either 'targets' or 'targets_by_type'.")
     elif self.targets_by_type is None:
         list_of_targets = [
             asdict(target) if is_dataclass(target) else target
             for target in self.targets
         ]
         self.targets_by_type = utils.group_targets_by_type(list_of_targets)
     elif self.targets is None:
         self.targets = [
             Target(**target) for target in utils.ungroup_targets_by_type(
                 self.targets_by_type)
         ]
     elif self.targets is not None and self.targets_by_type is not None:
         list_of_targets = [
             asdict(target) if is_dataclass(target) else target
             for target in self.targets
         ]
         if utils.group_targets_by_type(
                 list_of_targets) != self.targets_by_type:
             raise ValueError(
                 "You assigned both 'targets' and 'targets_by_type' in your event, "
                 "but the two were not consistent with each other. "
                 f"You supplied 'targets' = {self.targets} and "
                 f"'targets_by_type' = {self.targets_by_type}")
     # Set the event status
     self.event_descriptor.event_status = utils.determine_event_status(
         self.active_period)
Exemple #3
0
 def __post_init__(self):
     if self.signal_type not in enums.SIGNAL_TYPE.values:
         raise ValueError(
             f"""The signal_type must be one of '{"', '".join(enums.SIGNAL_TYPE.values)}', """
             f"""you specified: '{self.signal_type}'.""")
     if self.signal_name not in enums.SIGNAL_NAME.values and not self.signal_name.startswith(
             'x-'):
         raise ValueError(
             f"""The signal_name must be one of '{"', '".join(enums.SIGNAL_TYPE.values)}', """
             f"""or it must begin with 'x-'. You specified: '{self.signal_name}'"""
         )
     if self.targets is None and self.targets_by_type is None:
         return
     elif self.targets_by_type is None:
         list_of_targets = [
             asdict(target) if is_dataclass(target) else target
             for target in self.targets
         ]
         targets_by_type = utils.group_targets_by_type(list_of_targets)
         if len(targets_by_type) > 1:
             raise ValueError(
                 "In OpenADR, the EventSignal target may only be of type endDeviceAsset. "
                 f"You provided types: '{', '.join(targets_by_type)}'")
     elif self.targets is None:
         self.targets = [
             Target(**target) for target in utils.ungroup_targets_by_type(
                 self.targets_by_type)
         ]
     elif self.targets is not None and self.targets_by_type is not None:
         list_of_targets = [
             asdict(target) if is_dataclass(target) else target
             for target in self.targets
         ]
         if utils.group_targets_by_type(
                 list_of_targets) != self.targets_by_type:
             raise ValueError(
                 "You assigned both 'targets' and 'targets_by_type' in your event, "
                 "but the two were not consistent with each other. "
                 f"You supplied 'targets' = {self.targets} and "
                 f"'targets_by_type' = {self.targets_by_type}")
Exemple #4
0
def _preflight_oadrDistributeEvent(message_payload):
    if 'parse_duration' not in globals():
        from .utils import parse_duration
    # Check that the total event_duration matches the sum of the interval durations (rule 8)
    for event in message_payload['events']:
        active_period_duration = event['active_period']['duration']
        signal_durations = []
        for signal in event['event_signals']:
            signal_durations.append(
                sum([
                    parse_duration(i['duration']) for i in signal['intervals']
                ], timedelta(seconds=0)))

        if not all([d == active_period_duration for d in signal_durations]):
            if not all([d == signal_durations[0] for d in signal_durations]):
                raise ValueError(
                    "The different EventSignals have different total durations. "
                    "Please correct this.")
            else:
                logger.warning(
                    f"The active_period duration for event "
                    f"{event['event_descriptor']['event_id']} ({active_period_duration})"
                    f" differs from the sum of the interval's durations "
                    f"({signal_durations[0]}). The active_period duration has been "
                    f"adjusted to ({signal_durations[0]}).")
                event['active_period']['duration'] = signal_durations[0]

    # Check that payload values with signal name SIMPLE are constricted (rule 9)
    for event in message_payload['events']:
        for event_signal in event['event_signals']:
            if event_signal['signal_name'] == "SIMPLE":
                for interval in event_signal['intervals']:
                    if interval['signal_payload'] not in (0, 1, 2, 3):
                        raise ValueError(
                            "Payload Values used with Signal Name SIMPLE "
                            "must be one of 0, 1, 2 or 3")

    # Check that the current_value is 0 for SIMPLE events that are not yet active (rule 14)
    for event in message_payload['events']:
        for event_signal in event['event_signals']:
            if 'current_value' in event_signal and event_signal[
                    'current_value'] != 0:
                if event_signal['signal_name'] == "SIMPLE" \
                        and event['event_descriptor']['event_status'] != "ACTIVE":
                    logger.warning("The current_value for a SIMPLE event "
                                   "that is not yet active must be 0. "
                                   "This will be corrected.")
                    event_signal['current_value'] = 0

    # Check that there is a valid oadrResponseRequired value for each Event
    for event in message_payload['events']:
        if 'response_required' not in event:
            event['response_required'] = 'always'
        elif event['response_required'] not in ('never', 'always'):
            logger.warning(
                f"The response_required property in an Event "
                f"should be 'never' or 'always', not "
                f"{event['response_required']}. Changing to 'always'.")
            event['response_required'] = 'always'

    # Check that there is a valid oadrResponseRequired value for each Event
    for event in message_payload['events']:
        if 'created_date_time' not in event['event_descriptor'] \
                or not event['event_descriptor']['created_date_time']:
            event['event_descriptor']['created_date_time'] = datetime.now(
                timezone.utc)

    # Check that the target designations are correct and consistent
    for event in message_payload['events']:
        if 'targets' in event and 'targets_by_type' in event:
            if utils.group_targets_by_type(
                    event['targets']) != event['targets_by_type']:
                raise ValueError(
                    "You assigned both 'targets' and 'targets_by_type' in your event, "
                    "but the two were not consistent with each other. "
                    f"You supplied 'targets' = {event['targets']} and "
                    f"'targets_by_type' = {event['targets_by_type']}")
        elif 'targets_by_type' in event and 'targets' not in event:
            event['targets'] = utils.ungroup_targets_by_type(
                event['targets_by_type'])
def test_ungroup_target_by_type_with_single_str():
    targets_by_type = {'ven_id': 'ven123'}
    targets = utils.ungroup_targets_by_type(targets_by_type)
    assert targets == [{'ven_id': 'ven123'}]
Exemple #6
0
    def add_event(self,
                  ven_id,
                  signal_name,
                  signal_type,
                  intervals,
                  callback,
                  targets=None,
                  targets_by_type=None,
                  target=None,
                  market_context="oadr://unknown.context"):
        """
        Convenience method to add an event with a single signal.
        :param str ven_id: The ven_id to whom this event must be delivered.
        :param str signal_name: The OpenADR name of the signal; one of openleadr.objects.SIGNAL_NAME
        :param str signal_type: The OpenADR type of the signal; one of openleadr.objects.SIGNAL_TYPE
        :param str intervals: A list of intervals with a dtstart, duration and payload member.
        :param str callback: A callback function for when your event has been accepted (optIn) or refused (optOut).
        :param list targets: A list of Targets that this Event applies to.
        :param target: A single target for this event.
        :param dict targets_by_type: A dict of targets, grouped by type.
        :param str market_context: A URI for the DR program that this event belongs to.

        If you don't provide a target using any of the three arguments, the target will be set to the given ven_id.
        """
        if self.services['event_service'].polling_method == 'external':
            logger.error(
                "You cannot use the add_event method after you assign your own on_poll "
                "handler. If you use your own on_poll handler, you are responsible for "
                "delivering events from that handler. If you want to use OpenLEADRs "
                "message queuing system, you should not assign an on_poll handler. "
                "Your Event will NOT be added.")
            return
        if not re.match(
                r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?",
                market_context):
            raise ValueError("The Market Context must be a valid URI.")
        event_id = utils.generate_id()

        # Figure out the target for this Event
        if target is None and targets is None and targets_by_type is None:
            targets = [{'ven_id': ven_id}]
        elif target is not None:
            targets = [target]
        elif targets_by_type is not None:
            targets = utils.ungroup_targets_by_type(targets_by_type)
        if not isinstance(targets, list):
            targets = [targets]

        event_descriptor = objects.EventDescriptor(
            event_id=event_id,
            modification_number=0,
            market_context=market_context,
            event_status="near",
            created_date_time=datetime.now(timezone.utc))
        event_signal = objects.EventSignal(intervals=intervals,
                                           signal_name=signal_name,
                                           signal_type=signal_type,
                                           signal_id=utils.generate_id(),
                                           targets=targets)
        event = objects.Event(event_descriptor=event_descriptor,
                              event_signals=[event_signal],
                              targets=targets)
        if ven_id not in self.message_queues:
            self.message_queues[ven_id] = asyncio.Queue()
        self.message_queues[ven_id].put_nowait(event)
        self.services['event_service'].pending_events[event_id] = callback
Exemple #7
0
    def add_event(self, ven_id, signal_name, signal_type, intervals, callback=None, event_id=None,
                  targets=None, targets_by_type=None, target=None, response_required='always',
                  market_context="oadr://unknown.context", notification_period=None,
                  ramp_up_period=None, recovery_period=None, signal_target_mrid=None):
        """
        Convenience method to add an event with a single signal.

        :param str ven_id: The ven_id to whom this event must be delivered.
        :param str signal_name: The OpenADR name of the signal; one of openleadr.objects.SIGNAL_NAME
        :param str signal_type: The OpenADR type of the signal; one of openleadr.objects.SIGNAL_TYPE
        :param str intervals: A list of intervals with a dtstart, duration and payload member.
        :param str callback: A callback function for when your event has been accepted (optIn) or refused (optOut).
        :param list targets: A list of Targets that this Event applies to.
        :param target: A single target for this event.
        :param dict targets_by_type: A dict of targets, grouped by type.
        :param str market_context: A URI for the DR program that this event belongs to.
        :param timedelta notification_period: The Notification period for the Event's Active Period.
        :param timedelta ramp_up_period: The Ramp Up period for the Event's Active Period.
        :param timedelta recovery_period: The Recovery period for the Event's Active Period.

        If you don't provide a target using any of the three arguments, the target will be set to the given ven_id.
        """
        if self.services['event_service'].polling_method == 'external':
            logger.error("You cannot use the add_event method after you assign your own on_poll "
                         "handler. If you use your own on_poll handler, you are responsible for "
                         "delivering events from that handler. If you want to use OpenLEADRs "
                         "message queuing system, you should not assign an on_poll handler. "
                         "Your Event will NOT be added.")
            return
        if not re.match(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?", market_context):
            raise ValueError("The Market Context must be a valid URI.")
        event_id = event_id or utils.generate_id()

        if response_required not in ('always', 'never'):
            raise ValueError("'response_required' should be either 'always' or 'never'; "
                             f"you provided '{response_required}'.")

        # Figure out the target for this Event
        if target is None and targets is None and targets_by_type is None:
            targets = [{'ven_id': ven_id}]
        elif target is not None:
            targets = [target]
        elif targets_by_type is not None:
            targets = utils.ungroup_targets_by_type(targets_by_type)
        if not isinstance(targets, list):
            targets = [targets]
        if signal_type not in enums.SIGNAL_TYPE.values:
            raise ValueError(f"""The signal_type must be one of '{"', '".join(enums.SIGNAL_TYPE.values)}', """
                             f"""you specified: '{signal_type}'.""")
        if signal_name not in enums.SIGNAL_NAME.values and not signal_name.startswith('x-'):
            raise ValueError(f"""The signal_name must be one of '{"', '".join(enums.SIGNAL_TYPE.values)}', """
                             f"""or it must begin with 'x-'. You specified: '{signal_name}'""")
        if not intervals or not isinstance(intervals, (list, tuple)) or len(intervals) == 0:
            raise ValueError(f"The intervals must be a list of intervals, you specified: {intervals}")

        event_descriptor = objects.EventDescriptor(event_id=event_id,
                                                   modification_number=0,
                                                   market_context=market_context,
                                                   event_status="far",
                                                   created_date_time=datetime.now(timezone.utc))
        event_signal = objects.EventSignal(intervals=intervals,
                                           signal_name=signal_name,
                                           signal_type=signal_type,
                                           signal_id=utils.generate_id())

        # Make sure the intervals carry timezone-aware timestamps
        for interval in intervals:
            if utils.getmember(interval, 'dtstart').tzinfo is None:
                utils.setmember(interval, 'dtstart',
                                utils.getmember(interval, 'dtstart').astimezone(timezone.utc))
                logger.warning("You supplied a naive datetime object to your interval's dtstart. "
                               "This will be interpreted as a timestamp in your local timezone "
                               "and then converted to UTC before sending. Please supply timezone-"
                               "aware timestamps like datetime.datetime.new(timezone.utc) or "
                               "datetime.datetime(..., tzinfo=datetime.timezone.utc)")
        active_period = utils.get_active_period_from_intervals(intervals, False)
        active_period.ramp_up_period = ramp_up_period
        active_period.notification_period = notification_period
        active_period.recovery_period = recovery_period
        event = objects.Event(active_period=active_period,
                              event_descriptor=event_descriptor,
                              event_signals=[event_signal],
                              targets=targets,
                              response_required=response_required)
        self.add_raw_event(ven_id=ven_id, event=event, callback=callback)
        return event_id