1919import logging
2020import ssl
2121from datetime import datetime , timedelta , timezone
22+ from dataclasses import asdict
2223from functools import partial
2324from http import HTTPStatus
2425
@@ -42,6 +43,7 @@ class OpenADRClient:
4243 Main client class. Most of these methods will be called automatically, but
4344 you can always choose to call them manually.
4445 """
46+
4547 def __init__ (self , ven_name , vtn_url , debug = False , cert = None , key = None ,
4648 passphrase = None , vtn_fingerprint = None , show_fingerprint = True , ca_file = None ,
4749 allow_jitter = True , ven_id = None , disable_signature = False , check_hostname = True ):
@@ -85,6 +87,7 @@ def __init__(self, ven_name, vtn_url, debug=False, cert=None, key=None,
8587 self .client_session = None
8688 self .report_queue_task = None
8789
90+ self .opts = []
8891 self .received_events = [] # Holds the events that we received.
8992 self .responded_events = {} # Holds the events that we already saw.
9093
@@ -558,6 +561,104 @@ async def sync_events(self):
558561 if 'events' in response_payload and len (response_payload ['events' ]) > 0 :
559562 await self ._on_event (response_payload )
560563
564+ ###########################################################################
565+ # #
566+ # OPT METHODS #
567+ # #
568+ ###########################################################################
569+
570+ async def create_opt (self , opt_type , opt_reason , targets , vavailability = None , event_id = None ,
571+ modification_number = None , opt_id = None , request_id = None , market_context = None ,
572+ signal_target_mrid = None ):
573+ """
574+ Send a new opt to the VTN, either to communicate a temporary availability
575+ schedule or to qualify the resources participating in an event.
576+
577+ :param str opt_type: An OpenADR opt type. (found in openleadr.enums.OPT)
578+ :param str opt_reason: An OpenADR opt reason. (found in openleadr.enums.OPT_REASON)
579+ :param targets: A list of target(s) that this opt is related to.
580+ :param vavailability: The availability schedule to send
581+ :param event_id: The id of the event this opt is referencing.
582+ :param modification_number: The modification number of the event this opt is referencing.
583+ :param str opt_id: A unique identifier for this opt message. Leave this blank for a
584+ random generated id, or fill it in if your VTN depends on
585+ this being a known value, or if it needs to be constant
586+ between restarts of the client.
587+ :param str request_id: A unique identifier for this request. The same remarks apply
588+ as for the opt_id.
589+ :param str market_context: The Market Context that this opt belongs to.
590+ """
591+
592+ # Verify input
593+ if opt_type not in enums .OPT .values :
594+ raise ValueError (f"{ opt_type } is not a valid opt type. Valid options are "
595+ f"{ ', ' .join (enums .REPORT_NAME .values )} " )
596+ if opt_reason not in enums .OPT_REASON .values :
597+ raise ValueError (f"{ opt_reason } is not a valid opt reason. Valid options are "
598+ f"{ ', ' .join (enums .REPORT_NAME .values )} " )
599+
600+ # Save opt
601+ opt_id = opt_id or utils .generate_id ()
602+ opt = objects .Opt (
603+ opt_id = opt_id ,
604+ opt_type = opt_type ,
605+ opt_reason = opt_reason ,
606+ vavailability = vavailability ,
607+ event_id = event_id ,
608+ modification_number = modification_number ,
609+ targets = targets ,
610+ market_context = market_context ,
611+ signal_target_mrid = signal_target_mrid
612+ )
613+ self .opts .append (opt )
614+
615+ # Send opt
616+ request_id = request_id or utils .generate_id ()
617+ payload = {
618+ 'request_id' : request_id ,
619+ 'ven_id' : self .ven_id ,
620+ ** asdict (opt )
621+ }
622+
623+ service = 'EiOpt'
624+ message = self ._create_message ('oadrCreateOpt' , ** payload )
625+ response_type , response_payload = await self ._perform_request (service , message )
626+
627+ if 'opt_id' in response_payload :
628+ # VTN acknowledged the opt message
629+ return response_payload ['opt_id' ]
630+
631+ # TODO: what to do if the VTN sends an error or does not acknowledge the opt?
632+
633+ async def cancel_opt (self , opt_id ):
634+ """
635+ Tell the VTN to cancel a previously acknowledged opt message
636+
637+ :param str opt_id: The id of the opt to cancel
638+ """
639+
640+ # Check if this opt exists
641+ opt = utils .find_by (
642+ self .opts , 'opt_id' , opt_id )
643+ if not opt :
644+ logger .error (f"A non-existant opt with opt_id "
645+ f"{ opt_id } was requested for cancellation." )
646+ return False
647+
648+ payload = {
649+ 'opt_id' : opt_id ,
650+ 'ven_id' : self .ven_id
651+ }
652+
653+ service = 'EiOpt'
654+ message = self ._create_message ('oadrCancelOpt' , ** payload )
655+ response_type , response_payload = await self ._perform_request (service , message )
656+
657+ if 'opt_id' in response_payload :
658+ # VTN acknowledged the opt cancelation
659+ self .opts .remove (opt )
660+ return True
661+
561662 ###########################################################################
562663 # #
563664 # REPORTING METHODS #
0 commit comments