Esempio n. 1
0
    def _control_event_loop(self):
        '''
        This is the threading loop to perform control based on current oadr events
        Note the current implementation simply loops based on CONTROL_LOOP_INTERVAL
        except when an updated event is received by a VTN.
        '''
        while not self._exit.is_set():
            try:
                logger.debug("Updating control states...")
                events = self.event_handler.get_active_events()

                new_signal_level = self._update_control(events)
                logger.debug("Highest signal level is: %f", new_signal_level)

                changed = self._update_signal_level(new_signal_level)
                if changed:
                    logger.debug("Updated current signal level!")

            except Exception as ex:
                logger.exception("Control loop error: %s", ex)

            self._control_loop_signal.wait(self.control_loop_interval)
            self._control_loop_signal.clear(
            )  # in case it was triggered by a poll update

        logger.info("Control loop exiting.")
Esempio n. 2
0
    def __init__(self, event_config, control_opts={}, client_id=None):
        '''
        base class initializer, creates an `event.EventHandler` as
        `self.event_handler` and a `control.EventController` as
        `self.event_controller

        event_config -- A dictionary containing keyword arugments for the
                        EventHandler
        control_opts -- a dict of opts for `control.EventController` init
        '''

        # Get an EventHandler and an EventController
        self.event_handler = event.EventHandler(**event_config)
        self.event_controller = controller.EventController(
            self.event_handler, **control_opts)

        # Add an exit thread for the module
        self._exit = threading.Event()
        self._exit.clear()

        logger.addFilter(IdFilter(client_id=client_id))

        logger.info('Created base handler.')

        pass
Esempio n. 3
0
    def exit(self):
        '''
        Shutdown the base handler and its threads.
        '''

        self.event_controller.exit()  # Stop the event controller
        self._exit.set()

        logger.info('Shutdown base handler.')
Esempio n. 4
0
    def __init__(
        self,
        event_config,
        vtn_base_uri,
        control_opts={},
        username=None,
        password=None,
        ven_client_cert_key=None,
        ven_client_cert_pem=None,
        vtn_ca_certs=False,
        vtn_poll_interval=DEFAULT_VTN_POLL_INTERVAL,
        start_thread=True,
        client_id=None,
    ):
        '''
        Sets up the class and intializes the HTTP client.

        event_config -- A dictionary containing key-word arugments for the
                        EventHandller
        ven_client_cert_key -- Certification Key for the HTTP Client
        ven_client_cert_pem -- PEM file/string for the HTTP Client
        vtn_base_uri -- Base URI of the VTN's location
        vtn_poll_interval -- How often we should poll the VTN
        vtn_ca_certs -- CA Certs for the VTN
        start_thread -- start the thread for the poll loop or not? left as a legacy option
        '''

        # Call the parent's methods
        super(OpenADR2, self).__init__(event_config,
                                       control_opts,
                                       client_id=client_id)

        # Get the VTN's base uri set
        self.vtn_base_uri = vtn_base_uri
        if self.vtn_base_uri:  # append path
            join_char = '/' if self.vtn_base_uri[-1] != '/' else ''
            self.vtn_base_uri = join_char.join(
                (self.vtn_base_uri, OADR2_URI_PATH))
        try:
            self.vtn_poll_interval = int(vtn_poll_interval)
            assert self.vtn_poll_interval >= MINIMUM_POLL_INTERVAL
        except ValueError:
            logger.warning('Invalid poll interval: %s', self.vtn_poll_interval)
            self.vtn_poll_interval = DEFAULT_VTN_POLL_INTERVAL

        # Security & Authentication related
        self.ven_certs = (ven_client_cert_pem, ven_client_cert_key)\
            if ven_client_cert_pem and ven_client_cert_key else None
        self.vtn_ca_certs = vtn_ca_certs
        self.__username = username
        self.__password = password

        self.poll_thread = None
        if start_thread:  # this is left for backward compatibility
            self.start()

        logger.info("+++++++++++++++ OADR2 module started ++++++++++++++")
Esempio n. 5
0
    def stop(self):
        '''
        Stops polling without stopping event controller
        :return:
        '''
        if self.poll_thread is not None:
            self.poll_thread.join(2)  # they are daemons.

        self._exit.set()

        logger.info("Polling thread stopped")
Esempio n. 6
0
    def start(self):
        '''
        Initialize the HTTP client.

        start_thread -- To start the polling thread or not.
        '''

        if self.poll_thread and self.poll_thread.is_alive():
            logger.warning("Thread is already running")
            return

        self.poll_thread = threading.Thread(name='oadr2.poll',
                                            target=self.poll_vtn_loop)
        self.poll_thread.daemon = True
        self._exit.clear()

        self.poll_thread.start()
        logger.info("Polling thread started")
Esempio n. 7
0
    def poll_vtn_loop(self):
        '''
        The threading loop which polls the VTN on an interval
        '''

        while not self._exit.is_set():
            try:
                self.query_vtn()

            except urllib.error.HTTPError as ex:  # 4xx or 5xx HTTP response:
                logger.warning("HTTP error: %s\n%s", ex, ex.read())

            except urllib.error.URLError as ex:  # network error.
                logger.debug("Network error: %s", ex)

            except Exception as ex:
                logger.exception("Error in OADR2 poll thread: %s", ex)

            self._exit.wait(
                uniform(self.vtn_poll_interval * (1 - POLLING_JITTER),
                        self.vtn_poll_interval * (1 + POLLING_JITTER)))
        logger.info("+++++++++++++++ OADR2 polling thread has exited.")
Esempio n. 8
0
    def handle_payload(self, payload):
        '''
        Handle a payload.  Puts Events into the handler's event list.

        payload -- An lxml.etree.Element object of oadr:oadrDistributeEvent as root node

        Returns: An lxml.etree.Element object; which should be used as a response payload
        '''

        reply_events = []
        all_events = []

        requestID = payload.findtext('pyld:requestID', namespaces=self.ns_map)
        vtnID = payload.findtext('ei:vtnID', namespaces=self.ns_map)

        # If we got a payload from an VTN that is not in our list,
        # send it a 400 message and return
        if self.vtn_ids and (vtnID not in self.vtn_ids):
            logger.warning("Unexpected VTN ID: %s, expected one of %r", vtnID,
                           self.vtn_ids)
            return self.build_error_response(requestID, '400',
                                             'Unknown vtnID: %s' % vtnID)

        # Loop through all of the oadr:oadrEvent 's in the payload
        for evt in payload.iterfind('oadr:oadrEvent', namespaces=self.ns_map):
            response_required = evt.findtext("oadr:oadrResponseRequired",
                                             namespaces=self.ns_map)
            evt = evt.find('ei:eiEvent',
                           namespaces=self.ns_map)  # go to nested eiEvent
            new_event = EventSchema.from_xml(evt)
            current_signal_val = get_current_signal_value(evt, self.ns_map)

            logger.debug(
                f'------ EVENT ID: {new_event.id}({new_event.mod_number}); '
                f'Status: {new_event.status}; Current Signal: {current_signal_val}'
            )

            all_events.append(new_event.id)
            old_event = self.db.get_event(new_event.id)

            # For the events we need to reply to, make our "opts," and check the status of the event

            # By default, we optIn and have an "OK," status (200)
            opt = 'optIn'
            status = '200'

            if old_event and (old_event.mod_number > new_event.mod_number):
                logger.warning(
                    f"Got a smaller modification number "
                    f"({new_event.mod_number} < {old_event.mod_number}) for event {new_event.id}"
                )
                status = '403'
                opt = 'optOut'

            if not self.check_target_info(new_event):
                logger.info(
                    f"Opting out of event {new_event.id} - no target match")
                status = '403'
                opt = 'optOut'

            if new_event.id in self.optouts:
                logger.info(
                    f"Opting out of event {new_event.id} - user opted out")
                status = '200'
                opt = 'optOut'

            if not new_event.signals:
                logger.info(
                    f"Opting out of event {new_event.id} - no simple signal")
                opt = 'optOut'
                status = '403'

            if self.market_contexts and (new_event.market_context
                                         not in self.market_contexts):
                logger.info(
                    f"Opting out of event {new_event.id}:"
                    f"market context {new_event.market_context} does not match"
                )
                opt = 'optOut'
                status = '405'

            if response_required == 'always':
                reply_events.append((new_event.id, new_event.mod_number,
                                     requestID, opt, status))

            # We have a new event or an updated old one
            # if (old_event is None) or (e_mod_num > old_mod_num):
            if opt == "optIn":
                if old_event and (old_event.mod_number < new_event.mod_number):
                    # Add/update the event to our list
                    # updated_events[e_id] = evt
                    if new_event.status == "cancelled":
                        if new_event.status != old_event.status:
                            new_event.cancel(random_end=True)
                        else:
                            new_event.cancel()
                    self.db.update_event(new_event)

                if not old_event:
                    if new_event.status == "cancelled":
                        new_event.cancel()
                    self.db.add_event(new_event)

        # Find implicitly cancelled events and get rid of them
        for evt in self.get_active_events():
            if evt.id not in all_events:
                logger.debug(f'Mark event {evt.id} as cancelled')
                evt.cancel()
                self.db.update_event(evt)

        # If we have any in the reply_events list, build some payloads
        logger.debug("Replying for events %r", reply_events)
        reply = None
        if reply_events:
            reply = self.build_created_payload(reply_events)

        return reply