def test_increment_modification_number(): now = datetime.now(timezone.utc) event = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, created_date_time=now, event_status='far', priority=1, market_context='http://context01'), active_period=objects.ActivePeriod(dtstart=now - timedelta(minutes=5), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=now, duration=timedelta(minutes=10), signal_payload=1) ], signal_name='simple', signal_type='level', signal_id='signal001') ], targets=[{ 'ven_id': 'ven001' }]) utils.increment_event_modification_number(event) assert utils.getmember(event, 'event_descriptor.modification_number') == 1 utils.increment_event_modification_number(event) assert utils.getmember(event, 'event_descriptor.modification_number') == 2
async def on_poll(ven_id, future=None): if future and not future.done(): future.set_result(True) return objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, event_status='far', market_context='http://marketcontext01'), event_signals=[ objects.EventSignal( signal_id='signal001', signal_type='level', signal_name='simple', intervals=[ objects.Interval( dtstart=now, duration=datetime.timedelta(minutes=10), signal_payload=1) ]), objects.EventSignal( signal_id='signal002', signal_type='price', signal_name='ELECTRICITY_PRICE', intervals=[ objects.Interval( dtstart=now, duration=datetime.timedelta(minutes=10), signal_payload=1) ]) ], targets=[objects.Target(ven_id=ven_id)]) else: print("Returning None") return None
def test_oadr_event(): event = objects.Event( event_descriptor=objects.EventDescriptor( event_id=1, modification_number=0, market_context='MarketContext1', event_status=enums.EVENT_STATUS.NEAR), active_period=objects.ActivePeriod(dtstart=datetime.now(), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=0, signal_payload=1), objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=1, signal_payload=2) ], targets=[objects.Target(ven_id='1234')], signal_name=enums.SIGNAL_NAME.LOAD_CONTROL, signal_type=enums.SIGNAL_TYPE.LEVEL, signal_id=1, current_value=0) ], targets=[objects.Target(ven_id='1234')]) response = objects.Response(response_code=200, response_description='OK', request_id='1234') msg = create_message('oadrDistributeEvent', response=response, events=[event]) message_type, message_payload = parse_message(msg)
def test_oadr_event_targets_and_targets_by_type(): event = objects.Event( event_descriptor=objects.EventDescriptor( event_id=1, modification_number=0, market_context='MarketContext1', event_status=enums.EVENT_STATUS.NEAR), active_period=objects.ActivePeriod(dtstart=datetime.now(), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=0, signal_payload=1), objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=1, signal_payload=2) ], targets=[objects.Target(ven_id='1234')], signal_name=enums.SIGNAL_NAME.LOAD_CONTROL, signal_type=enums.SIGNAL_TYPE.LEVEL, signal_id=1, current_value=0) ], targets=[{ 'ven_id': 'ven123' }], targets_by_type={'ven_id': ['ven123']}) msg = create_message('oadrDistributeEvent', events=[event]) validate_xml_schema(ensure_bytes(msg)) message_type, message_payload = parse_message(msg)
def test_oadr_event_no_targets(): with pytest.raises(ValueError): event = objects.Event( event_descriptor=objects.EventDescriptor( event_id=1, modification_number=0, market_context='MarketContext1', event_status=enums.EVENT_STATUS.NEAR), active_period=objects.ActivePeriod(dtstart=datetime.now(), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=0, signal_payload=1), objects.Interval(dtstart=datetime.now(), duration=timedelta(minutes=5), uid=1, signal_payload=2) ], targets=[objects.Target(ven_id='1234')], signal_name=enums.SIGNAL_NAME.LOAD_CONTROL, signal_type=enums.SIGNAL_TYPE.LEVEL, signal_id=1, current_value=0) ])
def test_event_descriptor_modification_number(): event_descriptor = objects.EventDescriptor( event_id='event123', modification_number=None, market_context='http://marketcontext01', event_status='near') assert event_descriptor.modification_number == 0
async def test_raw_event(): now = datetime.datetime.now(datetime.timezone.utc) server = OpenADRServer(vtn_id='myvtn') server.add_handler('on_create_party_registration', on_create_party_registration) event = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, event_status='far', market_context='http://marketcontext01'), event_signals=[ objects.EventSignal( signal_id='signal001', signal_type='level', signal_name='simple', intervals=[ objects.Interval(dtstart=now, duration=datetime.timedelta(minutes=10), signal_payload=1) ]), objects.EventSignal( signal_id='signal002', signal_type='price', signal_name='ELECTRICITY_PRICE', intervals=[ objects.Interval(dtstart=now, duration=datetime.timedelta(minutes=10), signal_payload=1) ]) ], targets=[objects.Target(ven_id='ven123')]) loop = asyncio.get_event_loop() event_callback_future = loop.create_future() server.add_raw_event(ven_id='ven123', event=event, callback=partial(event_callback, future=event_callback_future)) on_event_future = loop.create_future() client = OpenADRClient( ven_name='myven', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b') client.add_handler('on_event', partial(on_event_opt_in, future=on_event_future)) await server.run_async() await client.run() event = await on_event_future assert len(event['event_signals']) == 2 result = await event_callback_future assert result == 'optIn' await client.stop() await server.stop()
async def add_event(self, ven_id, signal_name, signal_type, intervals, target, callback): """ 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). """ 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 event_id = generate_id() if not isinstance(target, list): target = [target] event_descriptor = objects.EventDescriptor( event_id=event_id, modification_number=0, market_context="None", 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=generate_id(), targets=target) event = objects.Event(event_descriptor=event_descriptor, event_signals=[event_signal], targets=target) if ven_id not in self.message_queues: self.message_queues[ven_id] = asyncio.Queue() await self.message_queues[ven_id].put(event) self.services['event_service'].pending_events[event_id] = callback
from dataclasses import asdict from datetime import datetime, timezone, timedelta import pytest def on_create_party_registration(registration_info): return 'ven123', 'reg123' def poll_responder(ven_id, message_type, message_payload): return message_type, message_payload event = objects.Event(event_descriptor=objects.EventDescriptor( event_id='event123', event_status='far', modification_number='1', market_context='http://marketcontext01'), event_signals=[ objects.EventSignal( signal_name='simple', signal_type='level', signal_id=utils.generate_id(), intervals=[ objects.Interval( dtstart=datetime.now(timezone.utc), duration=timedelta(minutes=10), signal_payload=1) ]) ], targets=[{
def test_order_events(): now = datetime.now(timezone.utc) event_1_active_high_prio = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, created_date_time=now, event_status='far', priority=1, market_context='http://context01'), active_period=objects.ActivePeriod(dtstart=now - timedelta(minutes=5), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=now, duration=timedelta(minutes=10), signal_payload=1) ], signal_name='simple', signal_type='level', signal_id='signal001') ], targets=[{ 'ven_id': 'ven001' }]) event_2_active_low_prio = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, created_date_time=now, event_status='far', priority=2, market_context='http://context01'), active_period=objects.ActivePeriod(dtstart=now - timedelta(minutes=5), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=now, duration=timedelta(minutes=10), signal_payload=1) ], signal_name='simple', signal_type='level', signal_id='signal001') ], targets=[{ 'ven_id': 'ven001' }]) event_3_active_no_prio = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, created_date_time=now, event_status='far', market_context='http://context01'), active_period=objects.ActivePeriod(dtstart=now - timedelta(minutes=5), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=now, duration=timedelta(minutes=10), signal_payload=1) ], signal_name='simple', signal_type='level', signal_id='signal001') ], targets=[{ 'ven_id': 'ven001' }]) event_4_far_early = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, created_date_time=now, event_status='far', market_context='http://context01'), active_period=objects.ActivePeriod(dtstart=now + timedelta(minutes=5), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=now, duration=timedelta(minutes=10), signal_payload=1) ], signal_name='simple', signal_type='level', signal_id='signal001') ], targets=[{ 'ven_id': 'ven001' }]) event_5_far_later = objects.Event( event_descriptor=objects.EventDescriptor( event_id='event001', modification_number=0, created_date_time=now, event_status='far', market_context='http://context01'), active_period=objects.ActivePeriod(dtstart=now + timedelta(minutes=10), duration=timedelta(minutes=10)), event_signals=[ objects.EventSignal(intervals=[ objects.Interval(dtstart=now, duration=timedelta(minutes=10), signal_payload=1) ], signal_name='simple', signal_type='level', signal_id='signal001') ], targets=[{ 'ven_id': 'ven001' }]) events = [ event_5_far_later, event_4_far_early, event_3_active_no_prio, event_2_active_low_prio, event_1_active_high_prio ] ordered_events = utils.order_events(events) assert ordered_events == [ event_1_active_high_prio, event_2_active_low_prio, event_3_active_no_prio, event_4_far_early, event_5_far_later ] ordered_events = utils.order_events(event_1_active_high_prio) assert ordered_events == [event_1_active_high_prio] event_1_as_dict = asdict(event_1_active_high_prio) ordered_events = utils.order_events(event_1_as_dict) assert ordered_events == [event_1_as_dict]
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
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