Exemple #1
0
    class Agent(PublishMixin, BaseAgent):
        '''Agent listens to message bus device and runs when data is published.
        '''
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self._update_event = None
            self._update_event_time = None
            self.keys = None
            self._device_states = {}
            self._kwargs = kwargs
            self.commands = {}
            self.current_point = None
            self.current_key = None
            self.received_input_datetime = None
            if output_file != None:
                with open(output_file, 'w') as writer:
                    writer.close()
            self._header_written = False

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **device))
        def on_received_message(self, topic, headers, message, matched):
            '''Subscribe to device data and convert data to correct type for
            the driven application.
            '''
            _log.debug("Message received")
            _log.debug("MESSAGE: " + jsonapi.dumps(message[0]))
            _log.debug("TOPIC: " + topic)
            data = jsonapi.loads(message[0])

            #TODO: grab the time from the header if it's there or use now if not
            self.received_input_datetime = datetime.utcnow()
            results = app_instance.run(self.received_input_datetime, data)
            self._process_results(results)

        def _process_results(self, results):
            '''Run driven application with converted data and write the app
            results to a file or database.
            '''
            _log.debug('Processing Results!')
            for key, value in results.commands.iteritems():
                _log.debug("COMMAND: {}->{}".format(key, value))
            for value in results.log_messages:
                _log.debug("LOG: {}".format(value))
            for key, value in results.table_output.iteritems():
                _log.debug("TABLE: {}->{}".format(key, value))
            # publish to output file if available.
            if output_file != None:
                if len(results.table_output.keys()) > 0:
                    for _, v in results.table_output.items():
                        fname = output_file  # +"-"+k+".csv"
                        for r in v:
                            with open(fname, 'a+') as f:
                                keys = r.keys()
                                fout = csv.DictWriter(f, keys)
                                if not self._header_written:
                                    fout.writeheader()
                                    self._header_written = True
                                # if not header_written:
                                # fout.writerow(keys)
                                fout.writerow(r)
                                f.close()
            # publish to message bus.
            if len(results.table_output.keys()) > 0:
                now = utils.format_timestamp(self.received_input_datetime)
                headers = {
                    headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON,
                    headers_mod.DATE: now,
                    headers_mod.TIMESTAMP: now
                }

                for _, v in results.table_output.items():
                    for r in v:
                        for key, value in r.iteritems():
                            if isinstance(value, bool):
                                value = int(value)
                            topic = topics.ANALYSIS_VALUE(
                                point=key, **
                                config['device'])  #.replace('{analysis}', key)
                            #print "publishing {}->{}".format(topic, value)
                            self.publish_json(topic, headers, value)

            if results.commands and mode:
                self.commands = results.commands
                if self.keys is None:
                    self.keys = self.commands.keys()
                self.schedule_task()

        def schedule_task(self):
            '''Schedule access to modify device controls.'''
            _log.debug('Schedule Device Access')
            headers = {
                'type': 'NEW_SCHEDULE',
                'requesterID': agent_id,
                'taskID': actuator_id,
                'priority': 'LOW'
            }
            start = datetime.now()
            end = start + td(seconds=300)
            start = str(start)
            end = str(end)
            _log.debug("{campus}/{building}/{unit}".format(**device))
            self.publish_json(
                topics.ACTUATOR_SCHEDULE_REQUEST(), headers,
                [["{campus}/{building}/{unit}".format(**device), start, end]])

        def command_equip(self):
            '''Execute commands on configured device.'''
            self.current_key = self.keys[0]
            value = self.commands[self.current_key]
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.publish(topics.ACTUATOR_SET(point=self.current_key, **device),
                         headers, str(value))

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_RESULT())
        def schedule_result(self, topic, headers, message, match):
            '''Actuator response (FAILURE, SUCESS).'''
            print 'Actuator Response'
            msg = jsonapi.loads(message[0])
            msg = msg['result']
            _log.debug('Schedule Device ACCESS')
            if self.keys:
                if msg == "SUCCESS":
                    self.command_equip()
                elif msg == "FAILURE":
                    print 'auto correction failed'
                    _log.debug('Auto-correction of device failed.')

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **device))
        def on_set_result(self, topic, headers, message, match):
            '''Setting of point on device was successful.'''
            print('Set Success:  {point} - {value}'.format(
                point=self.current_key,
                value=str(self.commands[self.current_key])))
            _log.debug('set_point({}, {})'.format(
                self.current_key, self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                print 'Done with Commands - Release device lock.'
                headers = {
                    'type': 'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers,
                                  {})
                self.keys = None

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **device))
        def on_set_error(self, topic, headers, message, match):
            '''Setting of point on device failed, log failure message.'''
            print 'Set ERROR'
            msg = jsonapi.loads(message[0])
            msg = msg['type']
            _log.debug('Actuator Error: ({}, {}, {})'.format(
                msg, self.current_key, self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                headers = {
                    'type': 'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers,
                                  {})
                self.keys = None
Exemple #2
0
    class Agent(PublishMixin, BaseAgent):
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.lock_acquired = False
            self.thread = None
            self.data_queue = multithreading.WaitQueue()
            self.value_queue = multithreading.WaitQueue()

        def setup(self):
            super(Agent, self).setup()
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.publish(topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path), headers)

        def start(self, algo=None):
            if algo is None:
                algo = afdd

            def run():
                sock = messaging.Socket(zmq.PUSH)
                sock.connect(publish_address)
                with contextlib.closing(sock):
                    algo(self, sock)

            self.thread = threading.Thread(target=run)
            self.thread.daemon = True
            self.thread.start()

        @matching.match_exact(topics.ACTUATOR_LOCK_RESULT(**rtu_path))
        def on_lock_result(self, topic, headers, message, match):
            msg = jsonapi.loads(message[0])
            holding_lock = self.lock_acquired
            if headers['requesterID'] == agent_id:
                self.lock_acquired = msg == 'SUCCESS'
            elif msg == 'SUCCESS':
                self.lock_acquired = False
            if self.lock_acquired and not holding_lock:
                self.start()

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **rtu_path))
        def on_new_data(self, topic, headers, message, match):
            data = jsonapi.loads(message[0])
            self.data_queue.notify_all(data)

        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **rtu_path))
        def on_set_result(self, topic, headers, message, match):
            self.value_queue.notify_all((match.group(1), True))

        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **rtu_path))
        def on_set_error(self, topic, headers, message, match):
            self.value_queue.notify_all((match.group(1), False))

        def get_new_data(self, timeout=None):
            _log.debug('get_new_data({})'.format(timeout))
            return self.data_queue.wait(timeout)

        def set_point(self, sock, point_name, value, timeout=None):
            _log.debug('set_point({}, {}, {})'.format(point_name, value,
                                                      timeout))
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            with self.value_queue.condition:
                sock.send_message(topics.ACTUATOR_SET(point=point_name,
                                                      **rtu_path),
                                  headers,
                                  str(value),
                                  flags=zmq.NOBLOCK)
                try:
                    return self.value_queue._wait(timeout)
                except multithreading.Timeout:
                    return None
Exemple #3
0
    class Agent(PublishMixin, BaseAgent):
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.lock_timer = None
            self.lock_acquired = False
            self.tasklet = None
            self.data_queue = green.WaitQueue(self.timer)
            self.value_queue = green.WaitQueue(self.timer)
            self.weather_data_queue = green.WaitQueue(self.timer)

            self.last_run_time = None
            self.is_running = False
            self.remaining_time = None
            self.task_id = agent_id
            self.retry_schedule = None
            self.start = None
            self.end = None

        def setup(self):
            super(Agent, self).setup()
            self.scheduled_task()

        def startrun(self, algo=None):
            _log.debug('start diagnostic')
            if algo is None:
                algo = afdd.AFDD(self, config_path).run_all
            self.tasklet = greenlet.greenlet(algo)
            self.is_running = True
            self.last_run_time = datetime.datetime.now()
            self.tasklet.switch()

        def scheduled_task(self):
            '''
            Schedule re-occuring diagnostics
            '''
            _log.debug('Schedule Dx')
            headers = {
                'type': 'NEW_SCHEDULE',
                'requesterID': agent_id,
                'taskID': agent_id,
                'priority': 'LOW_PREEMPT'
            }

            min_run_hour = math.floor(min_run_window / 3600)
            min_run_minute = int((min_run_window / 3600 - min_run_hour) * 60)

            self.start = datetime.datetime.now().replace(hour=start_hour,
                                                         minute=start_minute)
            self.end = self.start + datetime.timedelta(hours=2, minutes=30)
            run_start = self.end - datetime.datetime.now()
            required_diagnostic_time = datetime.timedelta(
                hours=min_run_hour, minutes=min_run_minute)

            if run_start < required_diagnostic_time:
                self.start = self.start + datetime.timedelta(days=1)
                self.end = self.start + datetime.timedelta(hours=2, minutes=30)
                sched_time = datetime.datetime.now() + datetime.timedelta(
                    days=day_run_interval + 1)
                sched_time = sched_time.replace(hour=0, minute=1)
            else:
                sched_time = datetime.datetime.now() + datetime.timedelta(
                    days=day_run_interval)

            self.start = str(self.start)
            self.end = str(self.end)
            self.task_timer = self.periodic_timer(
                60, self.publish_json, topics.ACTUATOR_SCHEDULE_REQUEST(),
                headers, [[
                    "{campus}/{building}/{unit}".format(**rtu_path),
                    self.start, self.end
                ]])
            event = sched.Event(self.scheduled_task)
            self.next = self.schedule(sched_time, event)

        @matching.match_headers({
            headers_mod.REQUESTER_ID: agent_id,
            'type': 'CANCEL_SCHEDULE'
        })
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_RESULT())
        def preempt(self):
            if self.is_running:
                self.cancel_greenlet()

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_ANNOUNCE(**rtu_path))
        def on_schedule(self, topic, headers, message, match):
            msg = jsonapi.loads(message[0])
            now = datetime.datetime.now()
            self.remaining_time = headers.get('window', 0)
            if self.task_id == headers.get('taskID', ''):
                if self.remaining_time < termination_window:
                    if self.is_running:
                        self.cancel_greenlet()
                elif (self.remaining_time > min_run_window and
                      (self.last_run_time is None or (now - self.last_run_time)
                       > datetime.timedelta(hours=23, minutes=50))):
                    self.startrun()

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_RESULT())
        def schedule_result(self, topic, headers, message, match):
            msg = jsonapi.loads(message[0])
            _log.debug('Actuator response received')
            self.task_timer.cancel()

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **rtu_path))
        def on_new_data(self, topic, headers, message, match):
            data = jsonapi.loads(message[0])
            #Check override status
            if int(data["VoltronPBStatus"]) == 1:
                if self.is_running:
                    _log.debug("User override is initiated...")
                    headers = {
                        'Content-Type': 'text/plain',
                        'requesterID': agent_id,
                    }
                    self.publish(
                        topics.ACTUATOR_SET(point="VoltronFlag", **rtu_path),
                        headers, str(0.0))
                    self.cancel_greenlet()
            else:
                self.data_queue.notify_all(data)

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **rtu_path))
        def on_set_result(self, topic, headers, message, match):
            self.value_queue.notify_all((match.group(1), True))

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **rtu_path))
        def on_set_error(self, topic, headers, message, match):
            self.value_queue.notify_all((match.group(1), False))

        def cancel_greenlet(self):
            #kill all tasks currently in the queue
            self.data_queue.kill_all()
            self.value_queue.kill_all()
            #kill current tasklet
            self.tasklet.throw()
            self.is_running = False

        def sleep(self, timeout):
            _log.debug('wait for steady state({})'.format(timeout))
            green.sleep(timeout, self.timer)

        def get_new_data(self, timeout=None):
            _log.debug('get_new_data({})'.format(timeout))
            return self.data_queue.wait(timeout)

        def command_equip(self, point_name, value, timeout=None):
            _log.debug('set_point({}, {}, {})'.format(point_name, value,
                                                      timeout))
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.publish(topics.ACTUATOR_SET(point=point_name, **rtu_path),
                         headers, str(value))
            try:
                return self.value_queue.wait(timeout)
            except green.Timeout:
                return True

        def weather_request(self, timeout=None):
            _log.debug('weather request for {}'.format(zip_code))
            headers = {'Content-Type': 'text/plain', 'requesterID': agent_id}
            msg = {'zipcode': str(zip_code)}
            self.publish_json('weather/request', headers, msg)
            try:
                return self.weather_data_queue.wait(timeout)
            except green.Timeout:
                return 'INCONCLUSIVE'

        matching.match_headers({headers_mod.REQUESTER_ID: agent_id})

        @matching.match_exact('weather/response/temperature/temp_f')
        def weather_response(self, topic, headers, message, match):
            data = float(jsonapi.loads(message[0]))
            print data
            self.weather_data_queue.notify_all(data)
Exemple #4
0
    class Agent(PublishMixin, BaseAgent):
        '''Agent listens to message bus device and runs when data is published.
        '''
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self._update_event = None
            self._update_event_time = None
            self.keys = None
            # master is where we copy from to get a poppable list of
            # subdevices that should be present before we run the analysis.
            self._master_subdevices = subdevices
            self._needed_subdevices = []
            self._master_devices = units
            self._subdevice_values = {}
            self._needed_devices = []
            self._device_values = {}
            self._initialize_devices()
            self.received_input_datetime = None
            self._kwargs = kwargs
            self.commands = {}
            self.current_point = None
            self.current_key = None
            if output_file is not None:
                with open(output_file, 'w') as writer:
                    writer.close()
            self._header_written = False

        def _initialize_devices(self):
            self._needed_subdevices = deepcopy(self._master_subdevices)
            self._needed_devices = deepcopy(self._master_devices)
            self._subdevice_values = {}
            self._device_values = {}

        def _should_run_now(self):
            # Assumes the unit/all values will have values.
            if not len(self._device_values.keys()) > 0:
                return False
            return not (len(self._needed_subdevices) > 0 or
                        len(self._needed_devices) > 0)

        @matching.match_regex(devices_topic)
        def on_rec_analysis_message(self, topic, headers, message, matched):
            # Do the analysis based upon the data passed (the old code).
            # print self._subdevice_values, self._device_values
            obj = jsonapi.loads(message[0])
            dev_list = topic.split('/')
            device_or_subdevice = dev_list[-2]
            device_id = [dev for dev in self._master_devices
                         if dev == device_or_subdevice]
            subdevice_id = [dev for dev in self._master_subdevices
                            if dev == device_or_subdevice]
            if not device_id and not subdevice_id:
                return
            if isinstance(device_or_subdevice, unicode):
                device_or_subdevice = (
                    device_or_subdevice.decode('utf-8').encode('ascii')
                )

            def agg_subdevice(obj):
                sub_obj = {}
                field_names = {}
                for k, v in obj.items():
                    field_names[k.lower() if isinstance(k, str) else k] = v
                if not converter.initialized and \
                        conv_map is not None:
                    converter.setup_conversion_map(
                        map_names,
                        field_names
                    )
                obj = converter.process_row(field_names)
                for key, value in obj.items():
                    sub_key = ''.join([key, '_', device_or_subdevice])
                    sub_obj[sub_key] = value
                if len(dev_list) > 5:
                    self._subdevice_values.update(sub_obj)
                    self._needed_subdevices.remove(device_or_subdevice)
                else:
                    self._device_values.update(sub_obj)
                    self._needed_devices.remove(device_or_subdevice)
                return
            # The below if statement is used to distinguish between unit/all
            # and unit/sub-device/all
            if (device_or_subdevice not in self._needed_devices and
                    device_or_subdevice not in self._needed_subdevices):
                _log.error("Warning device values already present, "
                           "reinitializing")
                self._initialize_devices()
            agg_subdevice(obj)
            if self._should_run_now():
                self._device_values.update(self._subdevice_values)
                results = app_instance.run(datetime.now(),
                                           self._device_values)
                self.received_input_datetime = datetime.utcnow()
                # results = app_instance.run(
                # dateutil.parser.parse(self._subdevice_values['Timestamp'],
                #                       fuzzy=True), self._subdevice_values)
                self._process_results(results)
                self._initialize_devices()
            else:
                needed = deepcopy(self._needed_devices)
                needed.extend(self._needed_subdevices)
                _log.info("Still need {} before running."
                          .format(needed))

        def _process_results(self, results):
            '''Run driven application with converted data and write the app
            results to a file or database.
            '''
            _log.debug('Processing Results!')
            for key, value in results.commands.iteritems():
                _log.debug("COMMAND: {}->{}".format(key, value))
            for value in results.log_messages:
                _log.debug("LOG: {}".format(value))
            for key, value in results.table_output.iteritems():
                _log.debug("TABLE: {}->{}".format(key, value))
            if output_file is not None:
                if len(results.table_output.keys()) > 0:
                    for _, v in results.table_output.items():
                        fname = output_file  # +"-"+k+".csv"
                        for r in v:
                            with open(fname, 'a+') as f:
                                keys = r.keys()
                                fout = csv.DictWriter(f, keys)
                                if not self._header_written:
                                    fout.writeheader()
                                    self._header_written = True
                                # if not header_written:
                                    # fout.writerow(keys)
                                fout.writerow(r)
                                f.close()
            # publish to message bus.
            if len(results.table_output.keys()) > 0:
                headers = {
                    headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON,
                    headers_mod.DATE: str(self.received_input_datetime),
                }

                for _, v in results.table_output.items():
                    for r in v:
                        for key, value in r.iteritems():
                            if isinstance(value, bool):
                                value = int(value)
                            for item in units:
                                _analysis['unit'] = item
                                analysis_topic = topics.ANALYSIS_VALUE(
                                    point=key, **_analysis)
                                mytime = int(time.time())
                                content = {
                                    analysis_topic: {
                                        "Readings": [[mytime, value]],
                                        "Units": "TU",
                                        "data_type": "double"
                                    }
                                }
                                self.publish_json(topics.LOGGER_LOG, headers,
                                                  content)
            if results.commands and mode:
                self.commands = results.commands
                if self.keys is None:
                    self.keys = self.commands.keys()
                self.schedule_task()

        def schedule_task(self):
            '''Schedule access to modify device controls.'''
            _log.debug('Schedule Device Access')
            headers = {
                'type':  'NEW_SCHEDULE',
                'requesterID': agent_id,
                'taskID': actuator_id,
                'priority': 'LOW'
                }
            start = datetime.now()
            end = start + td(seconds=30)
            start = str(start)
            end = str(end)
            self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers,
                              [["{campus}/{building}/{unit}".format(**device),
                                start, end]])

        def command_equip(self):
            '''Execute commands on configured device.'''
            self.current_key = self.keys[0]
            value = self.commands[self.current_key]
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
                }
            self.publish(topics.ACTUATOR_SET(point=self.current_key, **device),
                         headers, str(value))

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_RESULT())
        def schedule_result(self, topic, headers, message, match):
            '''Actuator response (FAILURE, SUCESS).'''
            _log.debug('Actuator Response')
            msg = jsonapi.loads(message[0])
            msg = msg['result']
            _log.debug('Schedule Device ACCESS')
            if self.keys:
                if msg == "SUCCESS":
                    self.command_equip()
                elif msg == "FAILURE":
                    _log.debug('Auto-correction of device failed.')

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **device))
        def on_set_result(self, topic, headers, message, match):
            '''Setting of point on device was successful.'''
            _log.debug('Set Success:  {point} - {value}'
                       .format(point=self.current_key,
                               value=str(self.commands[self.current_key])))
            _log.debug('set_point({}, {})'.
                       format(self.current_key,
                              self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                _log.debug('Done with Commands - Release device lock.')
                headers = {
                    'type': 'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                    }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(),
                                  headers, {})
                self.keys = None

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **device))
        def on_set_error(self, topic, headers, message, match):
            '''Setting of point on device failed, log failure message.'''
            _log.debug('Set ERROR')
            msg = jsonapi.loads(message[0])
            msg = msg['type']
            _log.debug('Actuator Error: ({}, {}, {})'.
                       format(msg,
                              self.current_key,
                              self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                headers = {
                    'type':  'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                    }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(),
                                  headers, {})
                self.keys = None
Exemple #5
0
    class Agent(PublishMixin, BaseAgent):
        """Class agent"""
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.default_firststage_fanspeed = 0.0
            self.default_secondstage_fanspeed = 0.0
            self.default_damperstpt = 0.0
            self.default_coolingstpt = 0.0
            self.default_heatingstpt = 65.0

            self.current_spacetemp = 72.0

            self.state = 'STARTUP'
            self.e_start_msg = None
            self.lock_handler = None
            self.error_handler = None
            self.actuator_handler = None

            self.all_scheduled_events = {}
            self.currently_running_dr_event_handlers = []
            self.headers = {
                headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON,
                'requesterID': agent_id
            }

        @matching.match_exact(topics.ACTUATOR_LOCK_RESULT(**rtu_path))
        def _on_lock_result(self, topic, headers, message, match):
            """lock result"""
            msg = jsonapi.loads(message[0])
            if headers['requesterID'] == agent_id:
                if msg == 'SUCCESS' and self.lock_handler is not None:
                    self.lock_handler()
                if msg == 'FAILURE' and self.error_handler is not None:
                    self.error_handler(msg)

        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **rtu_path))
        def _on_error_result(self, topic, headers, message, match):
            """lock result"""
            if headers.get('requesterID', '') == agent_id:
                if self.error_handler is not None:
                    self.error_handler(match, jsonapi.loads(message[0]))

        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **rtu_path))
        def _on_actuator_result(self, topic, headers, message, match):
            """lock result"""
            msg = jsonapi.loads(message[0])
            print 'Actuator Results:', match, msg
            if headers['requesterID'] == agent_id:
                if self.actuator_handler is not None:
                    self.actuator_handler(match, jsonapi.loads(message[0]))

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **rtu_path))
        def _on_new_data(self, topic, headers, message, match):
            """watching for new data"""
            data = jsonapi.loads(message[0])
            #             self.current_spacetemp = float(data["ZoneTemp"])
            self.current_spacetemp = 76
            droveride = bool(int(data["CoolCall2"]))
            occupied = bool(int(data["Occupied"]))

            if droveride and self.state not in ('IDLE', 'CLEANUP', 'STARTUP'):
                print 'User Override Initiated'
                self.cancel_event()

            if not occupied and self.state in ('DR_EVENT', 'RESTORE'):
                self.cancel_event()

            if self.state == 'IDLE' or self.state == 'STARTUP':
                #self.default_coolingstpt = float(data["CoolingStPt"])
                #self.default_heatingstpt = float(data["HeatingStPt"])
                self.default_coolingstpt = 75.0
                self.default_heatingstpt = 65.0
                self.default_firststage_fanspeed = float(
                    data["CoolSupplyFanSpeed1"])
                self.default_secondstage_fanspeed = float(
                    data["CoolSupplyFanSpeed2"])
                self.default_damperstpt = float(data["ESMDamperMinPosition"])

            if self.state == 'STARTUP':
                self.state = 'IDLE'

        @matching.match_exact(topics.OPENADR_EVENT())
        def _on_dr_event(self, topic, headers, message, match):
            if self.state == 'STARTUP':
                print "DR event ignored because of startup."
                return
            """handle openADR events"""
            msg = jsonapi.loads(message[0])
            print('EVENT Received')
            print(msg)
            e_id = msg['id']
            e_status = msg['status']
            e_start = msg['start']
            e_start = datetime.datetime.strptime(e_start, datefmt)
            today = datetime.datetime.now().date()
            #e_start_day = e_start.date()
            #e_end = e_start.replace(hour=cpp_end_hour, minute =0, second = 0)
            current_datetime = datetime.datetime.now()
            e_end = e_start + datetime.timedelta(minutes=2)

            if current_datetime > e_end:
                print 'Too Late Event is Over'
                return

            if e_status == 'cancelled':
                if e_start in self.all_scheduled_events:
                    print 'Event Cancelled'
                    self.all_scheduled_events[e_start].cancel()
                    del self.all_scheduled_events[e_start]

                if e_start.date() == today and (self.state == 'PRECOOL'
                                                or self.state == 'DR_EVENT'):
                    self.cancel_event()
                return

            #TODO: change this to UTC later
            #utc_now = datetime.datetime.utcnow()

            if today > e_start.date():
                if e_start in self.all_scheduled_events:
                    self.all_scheduled_events[e_start].cancel()
                    del self.all_scheduled_events[e_start]

                return

            for item in self.all_scheduled_events.keys():
                if e_start.date() == item.date():
                    if e_start.time() != item.time():
                        print "Updating Event"
                        self.all_scheduled_events[item].cancel()
                        del self.all_scheduled_events[item]
                        if e_start.date() == today and (self.state == 'PRECOOL'
                                                        or self.state
                                                        == 'DR_EVENT'):
                            self.update_running_event()
                            self.state = 'IDLE'
                        break
                    elif e_start.time() == item.time():
                        print "same event"
                        return
            #if e_id in self.all_scheduled_dr_events and update is None:


#                 if e_id == self.currently_running_msg:
#                 return
#return

#Minutes used for testing
#event_start = e_start - datetime.timedelta(hours = max_precool_hours)
            event_start = e_start - datetime.timedelta(
                minutes=max_precool_hours)

            event = sched.Event(self.pre_cool_get_lock, args=[e_start, e_end])
            self.schedule(event_start, event)
            self.all_scheduled_events[e_start] = event

        def pre_cool_get_lock(self, e_start, e_end):

            now = datetime.datetime.now()
            day = now.weekday()

            if not Schedule[day]:
                print "Unoccupied today"
                return

            self.state = 'PRECOOL'

            #e_end = e_start.replace(hour=cpp_end_hour, minute =0, second = 0)
            #e_end = e_start + datetime.timedelta(minutes=2)
            e_start_unix = time.mktime(e_start.timetuple())
            e_end_unix = time.mktime(e_end.timetuple())

            def run_schedule_builder():
                #current_time = time.mktime(current_time.timetuple())
                self.schedule_builder(
                    e_start_unix,
                    e_end_unix,
                    current_spacetemp=77.0,
                    pre_csp=csp_pre,
                    building_thermal_constant=building_thermal_constant,
                    normal_coolingstpt=76.0,
                    timestep_length=timestep_length,
                    dr_csp=csp_cpp)
                self.lock_handler = None

            self.lock_handler = run_schedule_builder

            headers = {
                headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON,
                'requesterID': agent_id
            }
            self.publish(topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path), headers)

        def modify_temp_set_point(self, csp, hsp):
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperChangeOverSetPoint',
                                    **rtu_path), self.headers, str(csp))
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperMinPosition',
                                    **rtu_path), self.headers, str(hsp))

            def backup_run():
                self.modify_temp_set_point(csp, hsp)
                self.lock_handler = None

            self.lock_handler = backup_run

        def start_dr_event(self):
            self.state = 'DR_EVENT'
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperChangeOverSetPoint',
                                    **rtu_path), self.headers, str(csp_cpp))

            new_fan_speed = self.default_firststage_fanspeed - (
                self.default_firststage_fanspeed * fan_reduction)
            new_fan_speed = max(new_fan_speed, 0)
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed1', **rtu_path),
                self.headers, str(new_fan_speed))

            new_fan_speed = self.default_secondstage_fanspeed - (
                self.default_firststage_fanspeed * fan_reduction)
            new_fan_speed = max(new_fan_speed, 0)
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed2', **rtu_path),
                self.headers, str(new_fan_speed))

            self.publish(
                topics.ACTUATOR_SET(point='ESMDamperMinPosition', **rtu_path),
                self.headers, str(damper_cpp))

            def backup_run():
                self.start_dr_event()
                self.lock_handler = None

            self.lock_handler = backup_run

        def start_restore_event(self, csp, hsp):
            self.state = 'RESTORE'
            print 'Restore:  Begin restoring normal operations'
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperChangeOverSetPoint',
                                    **rtu_path), self.headers, str(csp))
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperMinPosition',
                                    **rtu_path), self.headers,
                str(hsp))  #heating
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed1', **rtu_path),
                self.headers, str(self.default_firststage_fanspeed))
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed2', **rtu_path),
                self.headers, str(self.default_secondstage_fanspeed))

            self.publish(
                topics.ACTUATOR_SET(point='ESMDamperMinPosition', **rtu_path),
                self.headers, str(self.default_damperstpt))

            def backup_run():
                self.start_restore_event(csp, hsp)
                self.lock_handler = None

            self.lock_handler = backup_run

        def update_running_event(self):
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperChangeOverSetPoint',
                                    **rtu_path), self.headers,
                str(self.default_coolingstpt))
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperMinPosition',
                                    **rtu_path), self.headers,
                str(self.default_heatingstpt))
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed1', **rtu_path),
                self.headers, str(self.default_firststage_fanspeed))
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed2', **rtu_path),
                self.headers, str(self.default_secondstage_fanspeed))

            self.publish(
                topics.ACTUATOR_SET(point='ESMDamperMinPosition', **rtu_path),
                self.headers, str(self.default_damperstpt))

            for event in self.currently_running_dr_event_handlers:
                event.cancel()
            self.currently_running_dr_event_handlers = []

        def cancel_event(self):
            self.state = 'CLEANUP'
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperChangeOverSetPoint',
                                    **rtu_path), self.headers,
                str(self.default_coolingstpt))
            self.publish(
                topics.ACTUATOR_SET(point='StandardDamperMinPosition',
                                    **rtu_path), self.headers,
                str(self.default_heatingstpt))
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed1', **rtu_path),
                self.headers, str(self.default_firststage_fanspeed))
            self.publish(
                topics.ACTUATOR_SET(point='CoolSupplyFanSpeed2', **rtu_path),
                self.headers, str(self.default_secondstage_fanspeed))

            self.publish(
                topics.ACTUATOR_SET(point='ESMDamperMinPosition', **rtu_path),
                self.headers, str(self.default_damperstpt))

            for event in self.currently_running_dr_event_handlers:
                event.cancel()

            self.currently_running_dr_event_handlers = []

            def backup_run():
                self.cancel_event()
                self.lock_handler = None

            self.lock_handler = backup_run

            expected_values = {
                'StandardDamperChangeOverSetPoint': self.default_coolingstpt,
                'StandardDamperMinPosition': self.default_heatingstpt,
                'CoolSupplyFanSpeed1': self.default_firststage_fanspeed,
                'CoolSupplyFanSpeed2': self.default_secondstage_fanspeed,
                'ESMDamperMinPosition': self.default_damperstpt
            }

            EPSILON = 0.5  #allowed difference from expected value

            def result_handler(point, value):
                #print "actuator point being handled:", point, value
                expected_value = expected_values.pop(point, None)
                if expected_value is not None:
                    diff = abs(expected_value - value)
                    if diff > EPSILON:
                        _log.debug("Did not get back expected value for",
                                   point)

                if not expected_values:
                    self.actuator_handler = None
                    self.lock_handler = None
                    self.state = 'IDLE'

                    headers = {
                        headers_mod.CONTENT_TYPE:
                        headers_mod.CONTENT_TYPE.JSON,
                        'requesterID': agent_id
                    }
                    self.publish(topics.ACTUATOR_LOCK_RELEASE(**rtu_path),
                                 headers)

            self.actuator_handler = result_handler

        def schedule_builder(self, start_time, end_time, current_spacetemp,
                             pre_csp, building_thermal_constant,
                             normal_coolingstpt, timestep_length, dr_csp):
            """schedule all events for a DR event."""

            print 'Scheduling all DR actions'
            pre_hsp = pre_csp - 5.0
            current_time = time.time()
            ideal_cooling_window = int(
                ((current_spacetemp - pre_csp) / building_thermal_constant) *
                3600)
            ideal_precool_start_time = start_time - ideal_cooling_window

            max_cooling_window = start_time - current_time

            cooling_window = ideal_cooling_window if ideal_cooling_window < max_cooling_window else max_cooling_window

            precool_start_time = start_time - cooling_window

            if (max_cooling_window > 0):
                print "Schedule Pre Cooling"
                num_cooling_timesteps = int(
                    math.ceil(float(cooling_window) / float(timestep_length)))
                cooling_step_delta = (normal_coolingstpt -
                                      pre_csp) / num_cooling_timesteps

                for step_index in range(1, num_cooling_timesteps + 1):
                    event_time = start_time - (step_index * timestep_length)
                    csp = pre_csp + ((step_index - 1) * cooling_step_delta)

                    print 'Precool step:', datetime.datetime.fromtimestamp(
                        event_time), csp
                    event = sched.Event(self.modify_temp_set_point,
                                        args=[csp, pre_hsp])
                    self.schedule(event_time, event)
                    self.currently_running_dr_event_handlers.append(event)

            else:
                print "Too late to pre-cool!"

            restore_window = int(
                ((dr_csp - normal_coolingstpt) / building_thermal_constant) *
                3600)
            restore_start_time = end_time
            num_restore_timesteps = int(
                math.ceil(float(restore_window) / float(timestep_length)))
            restore_step_delta = (dr_csp -
                                  normal_coolingstpt) / num_restore_timesteps

            print 'Schedule DR Event:', datetime.datetime.fromtimestamp(
                start_time), dr_csp
            event = sched.Event(self.start_dr_event)
            self.schedule(start_time, event)
            self.currently_running_dr_event_handlers.append(event)

            print 'Schedule Restore Event:', datetime.datetime.fromtimestamp(
                end_time), dr_csp - restore_step_delta
            event = sched.Event(
                self.start_restore_event,
                args=[dr_csp - restore_step_delta, self.default_heatingstpt])
            self.schedule(end_time, event)
            self.currently_running_dr_event_handlers.append(event)

            for step_index in range(1, num_restore_timesteps):
                event_time = end_time + (step_index * timestep_length)
                csp = dr_csp - ((step_index + 1) * restore_step_delta)

                print 'Restore step:', datetime.datetime.fromtimestamp(
                    event_time), csp
                event = sched.Event(self.modify_temp_set_point,
                                    args=[csp, self.default_heatingstpt])
                self.schedule(event_time, event)
                self.currently_running_dr_event_handlers.append(event)

            event_time = end_time + (num_restore_timesteps * timestep_length)
            print 'Schedule Cleanup Event:', datetime.datetime.fromtimestamp(
                event_time)
            event = sched.Event(self.cancel_event)
            self.schedule(event_time, event)
            self.currently_running_dr_event_handlers.append(event)
Exemple #6
0
    class DrivenMatlabAgent(Agent):
        '''Agent listens to message bus device and runs when data is published.
        '''
        def __init__(self, **kwargs):
            super(DrivenMatlabAgent, self).__init__(**kwargs)
            self._update_event = None
            self._update_event_time = None
            self.keys = None
            # master is where we copy from to get a poppable list of
            # subdevices that should be present before we run the analysis.
            self._master_subdevices = subdevices
            self._needed_subdevices = []
            self._master_devices = units
            self._subdevice_values = {}
            self._needed_devices = []
            self._device_values = {}
            self._initialize_devices()
            self.received_input_datetime = None
            self._kwargs = kwargs
            self.commands = {}
            self.current_point = None
            self.current_key = None
            if output_file is not None:
                with open(output_file, 'w') as writer:
                    writer.close()
            self._header_written = False

        def _initialize_devices(self):
            self._needed_subdevices = deepcopy(self._master_subdevices)
            self._needed_devices = deepcopy(self._master_devices)
            self._subdevice_values = {}
            self._device_values = {}

        def _should_run_now(self):
            # Assumes the unit/all values will have values.
            if not len(self._device_values.keys()) > 0:
                return False
            return not (len(self._needed_subdevices) > 0
                        or len(self._needed_devices) > 0)

        #@matching.match_regex(devices_topic)
        @PubSub.subscribe('pubsub', '')
        def on_rec_analysis_message(self, peer, sender, bus, topic, headers,
                                    message):
            '''Subscribe to device data and assemble data set to pass
            to applications.
            '''
            topic_rex = re.compile(devices_topic)
            if not topic_rex.match(topic):
                _log.debug("Unmatched topic: {}".format(topic))
                return
            #obj = jsonapi.loads(message[0])
            #if isinstance(obj, list):
            #    obj = obj[0]
            obj = message[0]
            dev_list = topic.split('/')
            device_or_subdevice = dev_list[-2]
            device_id = [
                dev for dev in self._master_devices
                if dev == device_or_subdevice
            ]
            subdevice_id = [
                dev for dev in self._master_subdevices
                if dev == device_or_subdevice
            ]
            if not device_id and not subdevice_id:
                return
            if isinstance(device_or_subdevice, str):
                device_or_subdevice = (
                    device_or_subdevice.decode('utf-8').encode('ascii'))

            def agg_subdevice(obj):
                sub_obj = {}
                for key, value in obj.items():
                    sub_key = ''.join([key, '_', device_or_subdevice])
                    sub_obj[sub_key] = value
                if len(dev_list) > 5:
                    self._subdevice_values.update(sub_obj)
                    self._needed_subdevices.remove(device_or_subdevice)
                else:
                    self._device_values.update(sub_obj)
                    self._needed_devices.remove(device_or_subdevice)
                return

            # The below if statement is used to distinguish between unit/all
            # and unit/sub-device/all
            if (device_or_subdevice not in self._needed_devices
                    and device_or_subdevice not in self._needed_subdevices):
                _log.error("Warning device values already present, "
                           "reinitializing")
                self._initialize_devices()
            agg_subdevice(obj)

            if self._should_run_now():
                field_names = {}
                self._device_values.update(self._subdevice_values)
                for k, v in self._device_values.items():
                    field_names[k.lower() if isinstance(k, str) else k] = v
                if not converter.initialized and \
                        conv_map is not None:
                    converter.setup_conversion_map(map_names, field_names)
                if from_file:
                    _timestamp = parse(headers.get('Date'), fuzzy=True)
                    self.received_input_datetime = _timestamp
                else:
                    _timestamp = datetime.now()
                    self.received_input_datetime = datetime.utcnow()

                obj = converter.process_row(field_names)
                results = app_instance.run(_timestamp, obj)
                # results = app_instance.run(
                # dateutil.parser.parse(self._subdevice_values['Timestamp'],
                #                       fuzzy=True), self._subdevice_values)
                self._process_results(results)
                self._initialize_devices()
            else:
                needed = deepcopy(self._needed_devices)
                needed.extend(self._needed_subdevices)
                _log.info("Still need {} before running.".format(needed))

        def _process_results(self, results):
            '''Run driven application with converted data and write the app
            results to a file or database.
            '''
            _log.debug('Processing Results!')
            for key, value in results.commands.items():
                _log.debug("COMMAND: {}->{}".format(key, value))
            for value in results.log_messages:
                _log.debug("LOG: {}".format(value))
            for key, value in results.table_output.items():
                _log.debug("TABLE: {}->{}".format(key, value))
            if output_file is not None:
                _log.debug('Writing Output File!')
                if len(results.table_output.keys()) > 0:
                    for v in results.table_output.values():
                        fname = output_file  # +"-"+k+".csv"
                        for r in v:
                            with open(fname, 'a+') as f:
                                keys = r.keys()
                                fout = csv.DictWriter(f, keys)
                                if not self._header_written:
                                    fout.writeheader()
                                    self._header_written = True
                                # if not header_written:
                                # fout.writerow(keys)
                                fout.writerow(r)
                                f.close()
            _log.debug('File Written!')

            def get_unit(point):
                ''' Get a unit type based upon the regular expression in the config file.

                    if NOT found returns percent as a default unit.
                '''
                _log.debug('In get unit!')
                for k, v in unittype_map.items():
                    if re.match(k, point):
                        return v
                return 'percent'

            # publish to message bus.
            _log.debug('Publishing table output to message bus')
            if len(results.table_output.keys()) > 0:
                headers = {
                    headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON,
                    headers_mod.DATE: str(self.received_input_datetime),
                }
                for v in list(results.table_output.values()):
                    for r in v:
                        for key, value in list(r.items()):
                            if isinstance(value, bool):
                                value = int(value)
                            for item in units:
                                _analysis['unit'] = item
                                analysis_topic = topics.ANALYSIS_VALUE(
                                    point=key, **_analysis)

                                datatype = 'float'
                                if isinstance(value, int):
                                    datatype = 'int'
                                kbase = key[key.rfind('/') + 1:]
                                message = [{
                                    kbase: value
                                }, {
                                    kbase: {
                                        'tz': 'US/Pacific',
                                        'type': datatype,
                                        'units': 'float',
                                    }
                                }]
                                self.vip.pubsub.publish(peer="pubsub",
                                                        topic=analysis_topic,
                                                        headers=headers,
                                                        message=message)

            _log.debug('Publishing commands to message bus')
            _log.debug(mode)
            if results.commands and mode:
                self.commands = results.commands
                if self.keys is None:
                    self.keys = list(self.commands.keys())
                _log.debug("we have commands")
                #self.schedule_task()
                self.command_equip()

        def schedule_task(self):
            '''Schedule access to modify device controls.'''
            _log.debug('Schedule Device Access')
            headers = {
                'type': 'NEW_SCHEDULE',
                'requesterID': agent_id,
                'taskID': actuator_id,
                'priority': 'LOW'
            }
            start = datetime.now()
            end = start + td(seconds=30)
            start = str(start)
            end = str(end)
            self.vip.pubsub.publish(
                peer='pubsub',
                topic=topics.ACTUATOR_SCHEDULE_REQUEST(),
                headers=headers,
                message=[[
                    "{campus}/{building}/{unit}".format(**device), start, end
                ]])

        def command_equip(self):
            '''Execute commands on configured device.'''
            for key in self.keys:
                self.current_key = key
                value = self.commands[self.current_key]
                headers = {
                    'Content-Type': 'text/plain',
                    'requesterID': agent_id,
                }
                device.update({'unit': ''})
                self.vip.pubsub.publish(peer="pubsub",
                                        topic=topics.ACTUATOR_SET(
                                            point=self.current_key, **device),
                                        headers=headers,
                                        message=str(value))

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_RESULT())
        def schedule_result(self, peer, sender, bus, topic, headers, message):
            '''Actuator response (FAILURE, SUCESS).'''
            _log.debug('Actuator Response')
            msg = jsonapi.loads(message[0])
            msg = msg['result']
            _log.debug('Schedule Device ACCESS')
            if self.keys:
                if msg == "SUCCESS":
                    self.command_equip()
                elif msg == "FAILURE":
                    _log.debug('Auto-correction of device failed.')

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **device))
        def on_set_result(self, peer, sender, bus, topic, headers, message):
            '''Setting of point on device was successful.'''
            _log.debug('Set Success:  {point} - {value}'.format(
                point=self.current_key,
                value=str(self.commands[self.current_key])))
            _log.debug('set_point({}, {})'.format(
                self.current_key, self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                _log.debug('Done with Commands - Release device lock.')
                headers = {
                    'type': 'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers,
                                  {})
                self.keys = None

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **device))
        def on_set_error(self, topic, headers, message, match):
            '''Setting of point on device failed, log failure message.'''
            _log.debug('Set ERROR')
            msg = jsonapi.loads(message[0])
            msg = msg['type']
            _log.debug('Actuator Error: ({}, {}, {})'.format(
                msg, self.current_key, self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                headers = {
                    'type': 'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers,
                                  {})
                self.keys = None
Exemple #7
0
import datetime
import sys
import time
import logging

from volttron.platform.vip.agent import Agent, Core, RPC
from volttron.platform.messaging import topics
from volttron.platform.agent import utils
from volttron.platform.messaging.utils import normtopic
from volttron.platform.agent.sched import EventWithTime
from actuator.scheduler import ScheduleManager

from dateutil.parser import parse

VALUE_RESPONSE_PREFIX = topics.ACTUATOR_VALUE()
ERROR_RESPONSE_PREFIX = topics.ACTUATOR_ERROR()

WRITE_ATTEMPT_PREFIX = topics.ACTUATOR_WRITE()

SCHEDULE_ACTION_NEW = 'NEW_SCHEDULE'
SCHEDULE_ACTION_CANCEL = 'CANCEL_SCHEDULE'

SCHEDULE_RESPONSE_SUCCESS = 'SUCCESS'
SCHEDULE_RESPONSE_FAILURE = 'FAILURE'

SCHEDULE_CANCEL_PREEMPTED = 'PREEMPTED'

ACTUATOR_COLLECTION = 'actuators'

utils.setup_logging()
_log = logging.getLogger(__name__)
Exemple #8
0
    class Agent(PublishMixin, BaseAgent):
        """Class agent"""
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)

            self.normal_firststage_fanspeed = config.get(
                'normal_firststage_fanspeed', 75.0)
            self.normal_secondstage_fanspeed = config.get(
                'normal_secondstage_fanspeed', 90.0)
            self.normal_damper_stpt = config.get('normal_damper_stpt', 5.0)
            self.normal_coolingstpt = config.get('normal_coolingstpt', 74.0)
            self.normal_heatingstpt = config.get('normal_heatingstpt', 67.0)
            self.smap_path = config.get('smap_path')
            self.default_cooling_stage_differential = 0.5
            self.current_spacetemp = 0.0
            self.building_thermal_constant = config.get(
                'building_thermal_constant', 4.0)
            self.timestep_length = config.get('timestep_length', 900)
            self.csp_cpp = config.get('csp_cpp', 80.0)
            self.csp_pre = config.get('csp_pre', 67.0)
            self.restore_window = int(
                ((self.csp_cpp - self.normal_coolingstpt) /
                 self.building_thermal_constant) * 3600)

            self.state = 'STARTUP'
            self.e_start_msg = None
            self.error_handler = None
            self.actuator_handler = None
            self.pre_cool_idle = None
            self.e_start = None
            self.e_end = None
            self.pre_stored_spacetemp = None

            self.device_schedule = {}
            self.all_scheduled_events = {}
            self.currently_running_dr_event_handlers = []
            self.headers = {
                headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON,
                'requesterID': agent_id
            }
            utils.setup_logging()
            self._log = logging.getLogger(__name__)

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_RESULT())
        def schedule_result(self, topic, headers, message, match):
            msg = jsonapi.loads(message[0])
            self._log.info('Schedule Request Acknowledged')
            self.task_timer.cancel()
            task_id = headers.get('taskID', 0)
            response_type = headers.get('type', 0)
            schedule_start = self.device_schedule[task_id]["schedule_start"]
            event_start = schedule_start + datetime.timedelta(minutes=1)
            schedule_end = self.device_schedule[task_id]["schedule_end"]
            e_start = self.device_schedule[task_id]["event_start"]
            e_end = self.device_schedule[task_id]["event_end"]

            if response_type == 'NEW_SCHEDULE' and self.error_handler == None:
                if msg.get('result', 0) == 'SUCCESS':
                    event = sched.Event(self.pre_cool_setup,
                                        args=[e_start, e_end])
                    self.schedule(event_start, event)
                    self.all_scheduled_events[e_start] = event
                elif msg.get('result',
                             0) == 'FAILURE' and schedule_start < schedule_end:
                    schedule_start = schedule_start + datetime.timedelta(
                        minutes=10)
                    headers = {
                        'type': 'NEW_SCHEDULE',
                        'requesterID': agent_id,
                        'taskID': task_id,
                        'priority': 'High'
                    }
                    self.task_timer = self.periodic_timer(
                        20, self.publish_json,
                        topics.ACTUATOR_SCHEDULE_REQUEST(), headers, [[
                            "{campus}/{building}/{unit}".format(**rtu_path),
                            str(schedule_start), schedule_end
                        ]])
                elif schedule_start >= schedule_end:
                    return
            if self.error_handler is not None:
                self.error_handler()

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **rtu_path))
        def _on_error_result(self, topic, headers, message, match):
            """ERROR result"""
            point = match.group(1)
            msg = jsonapi.loads(message[0])
            point = match.group(1)
            today = datetime.datetime.now().date()
            for key, schedule in self.device_schedule.items():
                if schedule["date"] == today:
                    schedule_start = schedule["schedule_start"]
                    schedule_end = schedule["schedule_end"]
                    task_id = key
                    break

            self._log.info('Error Results: ' + str(point) + '  ' + str(msg))
            if msg.get('type', 0) == 'LockError':
                headers = {
                    'type': 'NEW_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': task_id,
                    'priority': 'HIGH'
                }
                self.task_timer = self.periodic_timer(
                    20, self.publish_json, topics.ACTUATOR_SCHEDULE_REQUEST(),
                    headers, [[
                        "{campus}/{building}/{unit}".format(**rtu_path),
                        str(schedule_start),
                        str(schedule_end)
                    ]])

            elif self.error_handler is not None:
                self._log.info('Running error handler')
                self.error_handler()

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **rtu_path))
        def _on_new_data(self, topic, headers, message, match):
            """watching for new data"""
            data = jsonapi.loads(message[0])
            self.current_spacetemp = float(data[space_temp])
            dr_override = bool(int(data[override_command]))
            occupied = bool(int(data[occupied_status]))

            if dr_override and self.state not in ('IDLE', 'CLEANUP',
                                                  'STARTUP'):
                self._log.info('User Override Initiated')
                self.cancel_event(cancel_type='OVERRIDE')

            if not occupied and self.state in ('DR_EVENT', 'RESTORE'):
                self.cancel_event()

            if self.state == 'STARTUP':
                self._log.info('Finished Startup')
                self.state = 'IDLE'

        @matching.match_exact(topics.OPENADR_EVENT())
        def _on_dr_event(self, topic, headers, message, match):
            if self.state == 'STARTUP':
                self._log.info('DR event ignored because of startup.')
                return
            """handle openADR events"""
            msg = jsonapi.loads(message[0])
            self._log.info('EVENT Received:  ' + str(msg))
            e_id = msg['id']
            e_status = msg['status']
            e_start = msg['start_at']
            task_id = msg['id']
            #e_start = datetime.datetime.strptime(e_start,datefmt)
            today = datetime.datetime.now().date()
            e_end = msg['end_at']
            e_end = parser.parse(e_end, fuzzy=True)
            e_start = parser.parse(e_start, fuzzy=True)
            dr_date = e_start.date()
            current_datetime = datetime.datetime.now()

            if current_datetime > e_end:
                self._log.info('Too Late Event is Over')
                return

            if e_status == 'cancelled':
                if e_start in self.all_scheduled_events:
                    self._log.info('Event Cancelled')
                    self.all_scheduled_events[e_start].cancel()
                    del self.all_scheduled_events[e_start]

                if e_start.date() == today and (self.state == 'PRECOOL'
                                                or self.state == 'DR_EVENT'):
                    self.cancel_event()
                return

            if today > e_start.date():
                if e_start in self.all_scheduled_events:
                    self.all_scheduled_events[e_start].cancel()
                    del self.all_scheduled_events[e_start]
                return

            for item in self.all_scheduled_events:
                if e_start.date() == item.date():
                    if e_start.time() != item.time():
                        self._log.info('Updating Event')
                        self.all_scheduled_events[item].cancel()
                        del self.all_scheduled_events[item]
                        if e_start.date() == today and (self.state == 'PRECOOL'
                                                        or self.state
                                                        == 'DR_EVENT'):
                            self.cancel_event(cancel_type='UPDATING')
                        break
                    elif e_start.time() == item.time():
                        self._log.info("same event")
                        return

            #Don't schedule an event if we are currently in OVERRIDE state.
            if e_start.date() == today and (self.state == 'OVERRIDE'):
                return

            schedule_start = e_start - datetime.timedelta(
                hours=max_precool_hours)
            schedule_end = e_end + datetime.timedelta(
                seconds=self.restore_window)
            schedule_end = schedule_end + datetime.timedelta(minutes=10)
            self.device_schedule[task_id] = {
                "date": dr_date,
                "schedule_start": schedule_start,
                "schedule_end": schedule_end,
                "event_start": e_start,
                "event_end": e_end
            }
            headers = {
                'type': 'NEW_SCHEDULE',
                'requesterID': agent_id,
                'taskID': task_id,
                'priority': 'HIGH'
            }
            self.task_timer = self.periodic_timer(
                20, self.publish_json, topics.ACTUATOR_SCHEDULE_REQUEST(),
                headers, [[
                    "{campus}/{building}/{unit}".format(**rtu_path),
                    str(schedule_start),
                    str(schedule_end)
                ]])

        def pre_cool_setup(self, e_start, e_end):
            if self.state == 'OVERRIDE':
                self._log.info("Override today")
                return

            if self.pre_cool_idle == False:
                return

            now = datetime.datetime.now()
            day = now.weekday()

            if not schedule[day]:
                self._log.info("Unoccupied today")
                return

            if self.state == 'PRECOOL' and self.pre_cool_idle == True:
                for event in self.currently_running_dr_event_handlers:
                    event.cancel()
                self.currently_running_dr_event_handlers = []

            self.state = 'PRECOOL'
            e_start_unix = time.mktime(e_start.timetuple())
            e_end_unix = time.mktime(e_end.timetuple())

            event_start = now + datetime.timedelta(minutes=15)
            event = sched.Event(self.pre_cool_setup, args=[e_start, e_end])
            self.schedule(event_start, event)
            self.all_scheduled_events[e_start] = event
            self.schedule_builder(e_start_unix, e_end_unix)

        def modify_temp_set_point(self, csp, hsp):
            self.publish(topics.ACTUATOR_SET(point=volttron_flag, **rtu_path),
                         self.headers, str(3.0))
            self.publish(
                topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path),
                self.headers, str(self.normal_damper_stpt))
            self.publish(
                topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path),
                self.headers, str(self.default_cooling_stage_differential))
            self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path),
                         self.headers, str(csp))
            self.publish(topics.ACTUATOR_SET(point=heating_stpt, **rtu_path),
                         self.headers, str(hsp))

            if self.pre_cool_idle == True:
                self.pre_cool_idle = False

            def backup_run():
                self.modify_temp_set_point(csp, hsp)
                self.error_handler = None

            self.error_handler = backup_run

        def start_dr_event(self):
            self.state = 'DR_EVENT'
            self.publish(topics.ACTUATOR_SET(point=volttron_flag, **rtu_path),
                         self.headers, str(3.0))
            self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path),
                         self.headers, str(self.csp_cpp))

            new_fan_speed = self.normal_firststage_fanspeed - (
                self.normal_firststage_fanspeed * fan_reduction)
            new_fan_speed = max(new_fan_speed, 0)
            self.publish(
                topics.ACTUATOR_SET(point=cooling_fan_sp1, **rtu_path),
                self.headers, str(new_fan_speed))

            new_fan_speed = self.normal_secondstage_fanspeed - (
                self.normal_firststage_fanspeed * fan_reduction)
            new_fan_speed = max(new_fan_speed, 0)
            self.publish(
                topics.ACTUATOR_SET(point=cooling_fan_sp2, **rtu_path),
                self.headers, str(new_fan_speed))

            self.publish(
                topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path),
                self.headers, str(damper_cpp))
            self.publish(
                topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path),
                self.headers, str(cooling_stage_differential))
            mytime = int(time.time())
            content = {
                "Demand Response Event": {
                    "Readings": [[mytime, 1.0]],
                    "Units": "TU",
                    "data_type": "double"
                }
            }
            self.publish(self.smap_path, self.headers, jsonapi.dumps(content))

            def backup_run():
                self.start_dr_event()
                self.error_handler = None

            self.error_handler = backup_run

        def start_restore_event(self, csp, hsp):
            self.state = 'RESTORE'
            self._log.info('Restore:  Begin restoring normal operations')
            self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path),
                         self.headers, str(csp))
            self.publish(topics.ACTUATOR_SET(point=heating_stpt, **rtu_path),
                         self.headers, str(hsp))  #heating
            self.publish(
                topics.ACTUATOR_SET(point=cooling_fan_sp1, **rtu_path),
                self.headers, str(self.normal_firststage_fanspeed))
            self.publish(
                topics.ACTUATOR_SET(point=cooling_fan_sp2, **rtu_path),
                self.headers, str(self.normal_secondstage_fanspeed))

            self.publish(
                topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path),
                self.headers, str(self.normal_damper_stpt))
            self.publish(
                topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path),
                self.headers, str(self.default_cooling_stage_differential))

            def backup_run():
                self.start_restore_event(csp, hsp)
                self.error_handler = None

            self.error_handler = backup_run

        def cancel_event(self, cancel_type='NORMAL'):
            if cancel_type == 'OVERRIDE':
                self.state = 'OVERRIDE'
                smap_input = 3.0
            elif cancel_type != 'UPDATING':
                self.state = 'CLEANUP'
                smap_input = 2.0

            self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path),
                         self.headers, str(self.normal_coolingstpt))
            self.publish(topics.ACTUATOR_SET(point=heating_stpt, **rtu_path),
                         self.headers, str(self.normal_heatingstpt))
            self.publish(
                topics.ACTUATOR_SET(point=cooling_fan_sp1, **rtu_path),
                self.headers, str(self.normal_firststage_fanspeed))
            self.publish(
                topics.ACTUATOR_SET(point=cooling_fan_sp2, **rtu_path),
                self.headers, str(self.normal_secondstage_fanspeed))

            self.publish(
                topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path),
                self.headers, str(self.normal_damper_stpt))
            self.publish(
                topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path),
                self.headers, str(self.default_cooling_stage_differential))
            self.publish(topics.ACTUATOR_SET(point=volttron_flag, **rtu_path),
                         self.headers, str(0))

            for event in self.currently_running_dr_event_handlers:
                event.cancel()

            if cancel_type != 'UPDATING':
                mytime = int(time.time())
                content = {
                    "Demand Response Event": {
                        "Readings": [[mytime, smap_input]],
                        "Units": "TU",
                        "data_type": "double"
                    }
                }
                self.publish(self.smap_path, self.headers,
                             jsonapi.dumps(content))
            self.device_schedule = {}
            self.all_scheduled_events = {}
            self.currently_running_dr_event_handlers = []

            def backup_run():
                self.cancel_event()
                self.error_handler = None

            self.error_handler = backup_run

            expected_values = {
                cooling_stpt: self.normal_coolingstpt,
                heating_stpt: self.normal_heatingstpt,
                cooling_fan_sp1: self.normal_firststage_fanspeed,
                cooling_fan_sp2: self.normal_secondstage_fanspeed,
                min_damper_stpt: self.normal_damper_stpt,
                cooling_stage_diff: self.default_cooling_stage_differential
            }

            EPSILON = 0.5  #allowed difference from expected value

            def result_handler(point, value):
                #print "actuator point being handled:", point, value
                expected_value = expected_values.pop(point, None)
                if expected_value is not None:
                    diff = abs(expected_value - value)
                    if diff > EPSILON:
                        self._log.info(
                            "Did not get back expected value for:  " +
                            str(point))

                if not expected_values:
                    self.actuator_handler = None

                    self.error_handler = None
                    self.state = 'IDLE' if not cancel_type == 'OVERRIDE' else 'OVERRIDE'

            if cancel_type != 'UPDATING':
                self.actuator_handler = result_handler
            else:
                self.actuator_handler = None

            if cancel_type == 'OVERRIDE':

                def on_reset():
                    self.error_handler = None
                    self.state = 'IDLE'

                today = datetime.datetime.now()
                reset_time = today + datetime.timedelta(days=1)
                reset_time = reset_time.replace(hour=0, minute=0, second=0)

                event = sched.Event(on_reset)
                self.schedule(reset_time, event)

        def schedule_builder(self, start_time, end_time):
            """schedule all events for a DR event."""
            current_time = time.time()
            if current_time > end_time:
                return

            self._log.info('Scheduling all DR actions')
            pre_hsp = self.csp_pre - 5.0

            ideal_cooling_window = int(
                ((self.current_spacetemp - self.csp_pre) /
                 self.building_thermal_constant) * 3600)
            ideal_precool_start_time = start_time - ideal_cooling_window

            max_cooling_window = start_time - current_time

            cooling_window = ideal_cooling_window if ideal_cooling_window < max_cooling_window else max_cooling_window

            precool_start_time = start_time - cooling_window
            pre_cool_step = 0
            if (max_cooling_window > 0):
                self._log.info('Schedule Pre Cooling')
                num_cooling_timesteps = int(
                    math.ceil(
                        float(cooling_window) / float(self.timestep_length)))
                cooling_step_delta = (self.normal_coolingstpt -
                                      self.csp_pre) / num_cooling_timesteps

                if num_cooling_timesteps <= 0:
                    num_cooling_timesteps = 1

                for step_index in range(1, num_cooling_timesteps):
                    if step_index == 1:
                        pre_cool_step = 2 * self.timestep_length
                    else:
                        pre_cool_step += self.timestep_length

                    event_time = start_time - pre_cool_step
                    csp = self.csp_pre + (
                        (step_index - 1) * cooling_step_delta)

                    self._log.info(
                        'Precool step:  ' +
                        str(datetime.datetime.fromtimestamp(event_time)) +
                        '   CSP:  ' + str(csp))
                    event = sched.Event(self.modify_temp_set_point,
                                        args=[csp, pre_hsp])
                    self.schedule(event_time, event)
                    self.currently_running_dr_event_handlers.append(event)

            else:
                self._log.info('Too late to pre-cool!')

            restore_start_time = end_time
            num_restore_timesteps = int(
                math.ceil(
                    float(self.restore_window) / float(self.timestep_length)))
            restore_step_delta = (
                self.csp_pre - self.normal_coolingstpt) / num_restore_timesteps

            self._log.info('Schedule DR Event: ' +
                           str(datetime.datetime.fromtimestamp(start_time)) +
                           '   CSP:  ' + str(self.csp_cpp))
            event = sched.Event(self.start_dr_event)
            self.schedule(start_time, event)
            self.currently_running_dr_event_handlers.append(event)

            self._log.info('Schedule Restore Event:  ' +
                           str(datetime.datetime.fromtimestamp(end_time)) +
                           '   CSP:  ' +
                           str(self.csp_pre - restore_step_delta))
            event = sched.Event(self.start_restore_event,
                                args=[
                                    self.csp_pre - restore_step_delta,
                                    self.normal_heatingstpt
                                ])
            self.schedule(end_time, event)
            self.currently_running_dr_event_handlers.append(event)

            for step_index in range(1, num_restore_timesteps):
                event_time = end_time + (step_index * self.timestep_length)
                csp = self.csp_pre - ((step_index + 1) * restore_step_delta)

                self._log.info(
                    'Restore step: ' +
                    str(datetime.datetime.fromtimestamp(event_time)) +
                    '   CSP:  ' + str(csp))
                event = sched.Event(self.modify_temp_set_point,
                                    args=[csp, self.normal_heatingstpt])
                self.schedule(event_time, event)
                self.currently_running_dr_event_handlers.append(event)

            event_time = end_time + (num_restore_timesteps *
                                     self.timestep_length)
            self._log.info('Schedule Cleanup Event:  ' +
                           str(datetime.datetime.fromtimestamp(event_time)))
            event = sched.Event(self.cancel_event)
            self.schedule(event_time, event)
            self.currently_running_dr_event_handlers.append(event)
Exemple #9
0
    class Agent(PublishMixin, BaseAgent):
        """Class agent"""
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.lock_timer = None
            self.lock_acquired = False
            self.tasklet = None
            self.data_queue = green.WaitQueue(self.timer)
            self.value_queue = green.WaitQueue(self.timer)

        def setup(self):
            """acquire lock fom actuator agent"""
            super(Agent, self).setup()
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.lock_timer = self.periodic_timer(
                1, self.publish, topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path),
                headers)

        @matching.match_exact(topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path))
        def __on_lock_sent(self, topic, headers, message, match):
            """lock recieved"""
            self.lock_timer.cancel()

        @matching.match_exact(topics.ACTUATOR_LOCK_RESULT(**rtu_path))
        def __on_lock_result(self, topic, headers, message, match):
            """lock result"""
            msg = jsonapi.loads(message[0])
            holding_lock = self.lock_acquired
            if headers['requesterID'] == agent_id:
                self.lock_acquired = msg == 'SUCCESS'
            elif msg == 'SUCCESS':
                self.lock_acquired = False
            if self.lock_acquired and not holding_lock:
                self.start()

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **rtu_path))
        def __on_new_data(self, topic, headers, message, match):
            """watching for new data"""
            data = jsonapi.loads(message[0])
            self.data_queue.notify_all(data)

        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **rtu_path))
        def __on_set_result(self, topic, headers, message, match):
            """set value in conroller"""
            self.value_queue.notify_all((match.group(1), True))

        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **rtu_path))
        def __on_set_error(self, topic, headers, message, match):
            """watch for actuator error"""
            self.value_queue.notify_all((match.group(1), False))

        def __sleep(self, timeout=None):
            """built in sleep in green"""
            #_log.debug('sleep({})'.format(timeout))
            green.sleep(timeout, self.timer)

        def __get_new_data(self, timeout=None):
            """wait for new data"""
            _log.debug('get_new_data({})'.format(timeout))
            return self.data_queue.wait(timeout)

        def __command_equip(self, point_name, value, timeout=None):
            """set value in controller"""
            _log.debug('set_point({}, {}, {})'.format(point_name, value,
                                                      timeout))
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.publish(topics.ACTUATOR_SET(point=point_name, **rtu_path),
                         headers, str(value))
            try:
                return self.value_queue.wait(timeout)
            except green.Timeout:
                return None

        def __time_out(self):
            """if timeout occurs"""
            global fan1_norm
            global fan2_norm
            global csp_norm
            global min_damper
            if fan1_norm == 0:
                self.__sleep(
                    600)  #If controller loses volttron heartbeat will reset
                self.start()  #wait at least this long
            else:
                try:
                    self.__command_equip('CoolSupplyFanSpeed1', fan1_norm)
                except green.Timeout:
                    self.__sleep(600)
                    self.start()
                try:
                    self.__command_equip('CoolSupplyFanSpeed2', fan2_norm)
                except green.Timeout:
                    self.__sleep(600)
                    self.start()
                try:
                    self.__command_equip('ESMDamperMinPosition', min_damper)
                except green.Timeout:
                    self.__sleep(600)
                    self.start()
                try:
                    self.__command_equip('ReturnAirCO2Stpt', csp_norm)
                except green.Timeout:
                    self.__sleep(600)
                    self.start()

        @matching.match_exact(
            topics.DEVICES_VALUE(point='Occupied', **rtu_path))
        def __overide(self, topic, headers, message, match):
            """watch for override from controller"""
            data = jsonapi.loads(message[0])
            if not bool(data):
                self.tasklet = greenlet.greenlet(self.__on_override)
                self.tasklet.switch()

        def __on_override(self):
            global fan1_norm
            global fan2_norm
            global csp_norm
            global min_damper
            global override_flag

            if fan1_norm != 0 and not override_flag:
                override_flag = True
                _log.debug('Override initiated')
                try:
                    self.__command_equip('CoolSupplyFanSpeed1', fan1_norm)
                except green.Timeout:
                    self.__sleep(
                        43200)  #if override wait for 12 hours then resume
                    self.start(
                    )  #catalyst will default to original operations with no volttron heatbeat
                try:
                    self.__command_equip('CoolSupplyFanSpeed2', fan2_norm)
                except green.Timeout:
                    self.__sleep(43200)
                    self.start()
                try:
                    self.__command_equip('ESMDamperMinPosition', min_damper)
                except green.Timeout:
                    self.__sleep(43200)
                    self.start()
                try:
                    self.__command_equip('ReturnAirCO2Stpt', csp_norm)
                except green.Timeout:
                    self.__sleep(43200)
                    self.start()
            elif fan1_norm == 0 and not override_flag:
                override_flag = True
                self.__sleep(43200)
                self.start()

        def start(self):
            """Starting point for DR application"""
            global override
            override_flag = False
            self.tasklet = greenlet.greenlet(self.__go)
            self.tasklet.switch()

        def __go(self):
            """start main DR procedure"""
            #self.__command_equip('CoolSupplyFanSpeed1', 75)
            #self.__command_equip('CoolSupplyFanSpeed2', 90)
            #self.__command_equip('ESMDamperMinPosition', 5)
            global fan1_norm
            global fan2_norm
            global csp_norm
            global min_damper
            try:
                self.__command_equip('ReturnAirCO2Stpt', 74)
            except green.Timeout:
                self.__time_out()
            try:
                voltron_data = self.__get_new_data()
            except green.Timeout:
                self.__time_out()
                # Gracefully handle exception
            min_damper = float(voltron_data["ESMDamperMinPosition"])
            fan1_norm = float(voltron_data["CoolSupplyFanSpeed1"])
            fan2_norm = float(voltron_data["CoolSupplyFanSpeed2"])
            csp_norm = float(voltron_data["ReturnAirCO2Stpt"])
            _log.debug("Zone normal cooling temperature setpoint:  " +
                       repr(csp_norm))
            _log.debug("Supply fan cooling speed 1:  " + repr(fan1_norm))
            _log.debug("Supply fan cooling speed 2:  " + repr(fan2_norm))
            _log.debug("Normal minimum damper position:  " + repr(min_damper))
            self.tasklet = greenlet.greenlet(self.get_signal)
            self.tasklet.switch()

        def __pre_cpp_timer(self):
            """Schedule to run in get_signal"""
            _log.debug("Pre-cooling for CPP Event"
                       )  #pre-cool change cooling set point
            self.tasklet = greenlet.greenlet(self.__pre_csp)
            self.tasklet.switch()
            self.pre_timer = self.periodic_timer(settings.pre_cooling_time,
                                                 self.__pre_cpp_cooling)

        def __pre_cpp_cooling(self):
            """start pre cooling procedure"""
            self.tasklet = greenlet.greenlet(self.__pre_csp)
            self.tasklet.switch()

        def __pre_csp(self):
            """set cooling temp. set point"""
            self.__sleep(1)
            try:
                voltron_data = self.__get_new_data()
            except green.Timeout:
                self.__time_out()
            csp_now = float(voltron_data["ReturnAirCO2Stpt"])
            if csp_now > settings.csp_pre:
                try:
                    csp = csp_now - cooling_slope
                    self.__command_equip("ReturnAirCO2Stpt", csp)
                except green.Timeout:
                    self.__time_out()
            elif csp_now <= settings.csp_pre:
                try:
                    self.__command_equip("ReturnAirCO2Stpt", settings.csp_pre)
                except green.Timeout:
                    self.__time_out()
                self.pre_timer.cancel()

        def __accelerated_pre_cpp_timer(self):
            """if DR signal is received after normal pre"""
            _log.debug("Pre-cooling for CPP Event"
                       )  #pre-cool change cooling set point
            self.tasklet = greenlet.greenlet(self.__accelerated_pre_csp)
            self.tasklet.switch()
            self.pre_timer = self.periodic_timer(
                settings.pre_time, self.__accelerated_cpp_cooling)

        def __accelerated_cpp_cooling(self):
            """start accelerated pre-cooling"""
            self.tasklet = greenlet.greenlet(self.__accelerated_pre_csp)
            self.tasklet.switch()

        def __accelerated_pre_csp(self):
            """set cooling temp set point"""
            _log.debug("Accelerated pre-cooling for CPP Event")
            global accel_slope
            self.__sleep(2)
            try:
                voltron_data = self.__get_new_data()
            except green.Timeout:
                self.__time_out()
            csp_now = float(voltron_data["ReturnAirCO2Stpt"])
            csp = csp_now - accel_slope
            if csp_now > settings.csp_pre:
                try:
                    self.__command_equip("ReturnAirCO2Stpt", csp)
                except green.Timeout:
                    self.__time_out()
            elif csp_now <= settings.csp_pre:
                try:
                    self.__command_equip("ReturnAirCO2Stpt", settings.csp_pre)
                except green.Timeout:
                    self.__time_out()
                self.pre_timer.cancel()

        def __during_cpp_timer(self):
            """during CPP scheduled in get_signal"""
            self.tasklet = greenlet.greenlet(self.__during_cpp)
            self.tasklet.switch()

        def __during_cpp(self):
            """start CPP procedure"""
            _log.debug("During CPP Event")  # remove when done testing
            self.__sleep(2)
            global fan1_norm
            global fan2_norm
            cpp_damper = settings.cpp_damper
            fan_reduction = settings.fan_reduction
            cpp_csp = settings.cpp_csp
            cpp_fan1 = fan1_norm - fan1_norm * fan_reduction
            cpp_fan2 = fan2_norm - fan2_norm * fan_reduction
            self.__sleep(1)
            try:
                self.__command_equip("CoolSupplyFanSpeed1", cpp_fan1)
            except green.Timeout:
                self.__time_out()
            try:
                self.__command_equip("CoolSupplyFanSpeed2", cpp_fan2)
            except green.Timeout:
                self.__time_out()
            try:
                self.__command_equip("ReturnAirCO2Stpt", cpp_csp)
            except green.Timeout:
                self.__time_out()
            try:
                self.__command_equip('ESMDamperMinPosition', cpp_damper)
            except green.Timeout:
                self.__time_out()

        def __after_cpp_timer(self):
            """after CPP scheduled in get_signal"""
            self.tasklet = greenlet.greenlet(self.__restore_fan_damper)
            self.tasklet.switch()
            _log.debug("After CPP Event, returning to normal operations")
            self.tasklet = greenlet.greenlet(self.__restore_cooling_setpoint)
            self.tasklet.switch()
            timer = settings.after_time
            self.after_timer = self.periodic_timer(timer,
                                                   self.__after_cpp_cooling)

        def __after_cpp_cooling(self):
            """Start after CPP procedure"""
            _log.debug("After_CPP_COOLING")
            self.tasklet = greenlet.greenlet(self.__restore_cooling_setpoint)
            self.tasklet.switch()

        def __restore_fan_damper(self):
            """restore original fan speeds"""
            global fan1_norm
            global fan2_norm
            global min_damper
            self.__sleep(
                2
            )  # so screen _log.debugs in correct order remove after testing.
            try:
                self.__command_equip("ESMDamperMinPosition", min_damper)
            except green.Timeout:
                self.__time_out()
            try:
                self.__command_equip("CoolSupplyFanSpeed1", fan1_norm)
            except green.Timeout:
                self.__time_out()
            try:
                self.__command_equip("CoolSupplyFanSpeed2", fan2_norm)
            except green.Timeout:
                self.__time_out()

        def __restore_cooling_setpoint(self):
            """restore normal cooling temp setpoint"""
            global csp_norm
            self.__sleep(2)  #remove after testing
            try:
                voltron_data = self.__get_new_data()
            except green.Timeout:
                self.__time_out()
            csp_now = float(voltron_data["ReturnAirCO2Stpt"])
            if csp_now > csp_norm:
                csp = csp_now - cooling_slope
                try:
                    self.__command_equip("ReturnAirCO2Stpt", csp)
                except green.Timeout:
                    self.__time_out()
            elif csp_now <= csp_norm:
                self.after_timer.cancel()
                try:
                    self.__command_equip("ReturnAirCO2Stpt", csp_norm)
                except green.Timeout:
                    self.__time_out()

        def get_signal(self):
            """get and format DR signal and schedule DR proc."""
            #Pull signal from source
            self.__sleep(2)  #remove after testing
            global csp_norm
            global cooling_slope
            global accel_slope
            time_now = time.mktime(datetime.datetime.now().timetuple())
            time_pre = time.mktime(
                datetime.datetime.now().replace(hour=settings.pre_cpp_hour,
                                                minute=23,
                                                second=0,
                                                microsecond=0).timetuple())
            time_event = time.mktime(datetime.datetime.now().replace(
                hour=settings.during_cpp_hour,
                minute=25,
                second=0,
                microsecond=0).timetuple())
            time_after = time.mktime(datetime.datetime.now().replace(
                hour=settings.after_cpp_hour,
                minute=27,
                second=0,
                microsecond=0).timetuple())
            if (settings.signal and time_now < time_pre):
                _log.debug("Scheduling1")
                time_step = settings.pre_cooling_time / 3600
                #cooling_slope = (csp_norm-settings.csp_pre)/((((time_event-time_pre)/3600)-0.5)*time_step)
                cooling_slope = 1  # for testing use a constant
                temp = ((time_event - time_pre) / 3600)
                _log.debug("cooling slope: " + repr(cooling_slope))
                pre_cpp_time = datetime.datetime.now().replace(
                    hour=settings.pre_cpp_hour,
                    minute=23,
                    second=0,
                    microsecond=0)
                self.schedule(pre_cpp_time, sched.Event(self.__pre_cpp_timer))
                during_cpp_time = datetime.datetime.now().replace(
                    hour=settings.during_cpp_hour,
                    minute=25,
                    second=0,
                    microsecond=0)
                self.schedule(during_cpp_time,
                              sched.Event(self.__during_cpp_timer))
                after_cpp_time = datetime.datetime.now().replace(
                    hour=settings.after_cpp_hour,
                    minute=27,
                    second=0,
                    microsecond=0)
                self.schedule(after_cpp_time,
                              sched.Event(self.__after_cpp_timer))
                #self.start_timer.cancel()
            elif (settings.signal and time_now > time_pre
                  and time_now < time_event):
                _log.debug("Scheduling2")
                #self.start_timer.cancel()
                #accel_slope = (csp_norm-settings.csp_pre)/((time_event-time_now)/(3600))
                accel_slope = 2  #for testing use a constant
                during_cpp_time = datetime.datetime.now().replace(
                    hour=settings.during_cpp_hour,
                    minute=36,
                    second=20,
                    microsecond=0)
                self.schedule(during_cpp_time,
                              sched.Event(self.__during_cpp_timer))
                after_cpp_time = datetime.datetime.now().replace(
                    hour=settings.after_cpp_hour,
                    minute=39,
                    second=10,
                    microsecond=0)
                self.schedule(after_cpp_time,
                              sched.Event(self.__after_cpp_timer))
                self.__accelerated_pre_cpp_timer()
            elif (settings.signal and time_now > time_event
                  and time_now < time_after):
                _log.debug("Too late to pre-cool!")
                #self.start_timer.cancel()
                after_cpp_time = datetime.datetime.now().replace(
                    hour=settings.after_cpp_hour,
                    minute=17,
                    second=0,
                    microsecond=0)
                self.schedule(after_cpp_time,
                              sched.Event(self.__after_cpp_timer))
                self.tasklet = greenlet.greenlet(self.__during_cpp)
                self.tasklet.switch()
            else:
                _log.debug("CPP Event Is Over")
                #self.start_timer.cancel()
                self.__sleep(60)
                self.get_signal()
Exemple #10
0
    class Agent(PublishMixin, BaseAgent):
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.lock_timer = None
            self.lock_acquired = False
            self.tasklet = None
            self.data_queue = green.WaitQueue(self.timer)
            self.value_queue = green.WaitQueue(self.timer)

        def setup(self):
            super(Agent, self).setup()
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.lock_timer = self.periodic_timer(
                1, self.publish, topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path),
                headers)

        def start(self, algo=None):
            if algo is None:
                algo = afdd
            self.tasklet = greenlet.greenlet(algo)
            self.tasklet.switch(self)

        @matching.match_exact(topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path))
        def on_lock_sent(self, topic, headers, message, match):
            self.lock_timer.cancel()

        @matching.match_exact(topics.ACTUATOR_LOCK_RESULT(**rtu_path))
        def on_lock_result(self, topic, headers, message, match):
            msg = jsonapi.loads(message[0])
            holding_lock = self.lock_acquired
            if headers['requesterID'] == agent_id:
                self.lock_acquired = msg == 'SUCCESS'
            elif msg == 'SUCCESS':
                self.lock_acquired = False
            if self.lock_acquired and not holding_lock:
                self.start()

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **rtu_path))
        def on_new_data(self, topic, headers, message, match):
            data = jsonapi.loads(message[0])
            self.data_queue.notify_all(data)

        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **rtu_path))
        def on_set_result(self, topic, headers, message, match):
            self.value_queue.notify_all((match.group(1), True))

        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **rtu_path))
        def on_set_error(self, topic, headers, message, match):
            self.value_queue.notify_all((match.group(1), False))

        def sleep(self, timeout):
            _log.debug('sleep({})'.format(timeout))
            green.sleep(timeout, self.timer)

        def get_new_data(self, timeout=None):
            _log.debug('get_new_data({})'.format(timeout))
            return self.data_queue.wait(timeout)

        def set_point(self, point_name, value, timeout=None):
            _log.debug('set_point({}, {}, {})'.format(point_name, value,
                                                      timeout))
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.publish(topics.ACTUATOR_SET(point=point_name, **rtu_path),
                         headers, str(value))
            try:
                return self.value_queue.wait(timeout)
            except green.Timeout:
                return None
Exemple #11
0
    class Agent(PublishMixin, BaseAgent):
        """Class agent"""
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self.schedule_state = False
            self.fan1_norm = 0
            self.fan2_norm = 0
            self.csp_norm = 0
            self.accel_slope = 0
            self.cooling_slope = 0
            self.min_damper = 0
            self.override_flag = False
            self.lock_timer = None
            self.lock_acquired = False
            self.timers = []
            self.tasks = []
            self.tasklet = None
            self.data_queue = green.WaitQueue(self.timer)
            self.value_queue = green.WaitQueue(self.timer)
            self.running = False

        def setup(self):
            """acquire lock fom actuator agent"""
            super(Agent, self).setup()

        @matching.match_exact(
            topics.DEVICES_VALUE(point='CoolCall1', **rtu_path))
        def dr_signal(self, topic, headers, message, match):
            data = jsonapi.loads(message[0])
            if not self.running and bool(data):
                print("start")
                self.running = True
                time_now = time.mktime(datetime.datetime.now().timetuple())
                self.update_schedule_state(time_now)
                #self.schedule(next_time, self.update_schedule_state)
                if (self.schedule_state):
                    self.start()
                else:
                    _log.debug(
                        "DR signal is False or day is not an occupied day")

        def update_schedule_state(self, unix_time):
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            now = datetime.datetime.fromtimestamp(unix_time)
            day = now.weekday()
            if Schedule[day]:
                self.schedule_state = True
                #TODO: set this up to handle platform not running.
                #This will hang after a while otherwise.
                self.lock_timer = super(Agent, self).periodic_timer(
                    1, self.publish, topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path),
                    headers)
            else:
                self.schedule_state = False
                self.publish(topics.ACTUATOR_LOCK_RELEASE(**rtu_path), headers)

        @matching.match_exact(topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path))
        def _on_lock_sent(self, topic, headers, message, match):
            """lock request received"""
            self.lock_timer.cancel()

        @matching.match_exact(topics.ACTUATOR_LOCK_RESULT(**rtu_path))
        def _on_lock_result(self, topic, headers, message, match):
            """lock result"""
            msg = jsonapi.loads(message[0])
            holding_lock = self.lock_acquired
            if headers['requesterID'] == agent_id:
                self.lock_acquired = msg == 'SUCCESS'
            elif msg == 'SUCCESS':
                self.lock_acquired = False
            if self.lock_acquired and not holding_lock:
                self.start()

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **rtu_path))
        def _on_new_data(self, topic, headers, message, match):
            """watching for new data"""
            data = jsonapi.loads(message[0])
            self.data_queue.notify_all(data)

        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **rtu_path))
        def _on_set_result(self, topic, headers, message, match):
            """set value in conroller"""
            self.value_queue.notify_all((match.group(1), True))

        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **rtu_path))
        def _on_set_error(self, topic, headers, message, match):
            """watch for actuator error"""
            self.value_queue.notify_all((match.group(1), False))

        def _sleep(self, timeout=None):
            """built in sleep in green"""
            _log.debug('sleep({})'.format(timeout))
            green.sleep(timeout, self.timer)

        def _get_new_data(self, timeout=data_timeout):  #timeout=data_timeout
            """wait for new data"""
            _log.debug('get_new_data({})'.format(timeout))
            return self.data_queue.wait(timeout)

        def _command_equip(self, point_name, value, timeout):
            """set value in controller"""
            _log.debug('set_point({}, {}, {})'.format(point_name, value,
                                                      timeout))
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
            }
            self.publish(topics.ACTUATOR_SET(point=point_name, **rtu_path),
                         headers, str(value))
            while True:
                point, success = self.value_queue.wait(timeout)
                if point == point_name:
                    if success:
                        return
                    raise CommandSetError()

        def _time_out(self):
            """if timeout occurs"""
            if (self.fan1_norm and self.fan2_norm and self.min_damper
                    and self.csp_norm):
                try:
                    self._command_equip('CoolSupplyFanSpeed1', self.fan1_norm)
                    self._command_equip('CoolSupplyFanSpeed2', self.fan2_norm)
                    self._command_equip('ESMDamperMinPosition',
                                        self.min_damper)
                    self._command_equip('StandardDamperMinPosition',
                                        self.csp_norm)
                except green.Timeout:
                    pass
            for timer in self.timers:
                timer.cancel()
            del self.timers[:]
            current = greenlet.getcurrent()
            for task in self.tasks:
                if task is not current:
                    task.parent = current
                    task.throw()
            print 'adding current task to task list'
            self.tasks[:] = [current]
            self._sleep(
                600)  #If controller loses volttron heartbeat will reset
            self.running = False

        @matching.match_exact(
            topics.DEVICES_VALUE(point='Occupied', **rtu_path)
        )  # for now look for Occuppied, DR Override will be added
        def _override(self, topic, headers, message, match):
            """watch for override from controller"""
            data = jsonapi.loads(message[0])
            if not bool(data):
                self.greenlet(self._on_override)

        def _on_override(self):
            if not self.override_flag:
                self.override_flag = True
                _log.debug("Override initiated")
                for timer in self.timers:
                    timer.cancel()
                del self.timers[:]
                current = greenlet.getcurrent()
                for task in self.tasks:
                    if task is not current:
                        task.parent = current
                        task.throw()
            if self.fan1_norm:
                try:
                    self._command_equip('CoolSupplyFanSpeed1', self.fan1_norm)
                    self._command_equip('CoolSupplyFanSpeed2', self.fan2_norm)
                    self._command_equip('ESMDamperMinPosition',
                                        self.min_damper)
                    self._command_equip('ReturnAirCO2Stpt', self.csp_norm)
                except green.Timeout:
                    self._sleep(
                        43200)  #if override wait for 12 hours then resume
                    self._go(
                    )  #catalyst will default to original operations with no volttron heatbeat
                self._sleep(43200)
                self.override_flag = False
                self.running = False
            elif not self.fan1_norm and not self.override_flag:
                self.override_flag = True
                self._sleep(43200)
                self.override_flag = False
                self.running = False

        def start(self):
            """Starting point for DR application"""
            self.override_flag = False
            self.greenlet(self._go)

        def _go(self):
            """start main DR procedure"""
            self._command_equip('StandardDamperChangeOverSetPoint', 75, 20)
            self._command_equip('CoolSupplyFanSpeed2', 90, 20)
            self._command_equip('CoolSupplyFanSpeed1', 75, 20)
            self._command_equip('StandardDamperMinPosition', 65, 20)
            try:
                self._command_equip('ESMDamperMinPosition', 5, 20)

                voltron_data = self._get_new_data()
            except CommandSetError:
                self._time_out()
            except green.Timeout:
                self._time_out()
            self.min_damper = float(voltron_data["ESMDamperMinPosition"])
            self.fan1_norm = float(voltron_data["CoolSupplyFanSpeed1"])
            self.fan2_norm = float(voltron_data["CoolSupplyFanSpeed2"])
            self.csp_norm = float(voltron_data["ReturnAirCO2Stpt"])
            _log.debug("Zone normal cooling temperature setpoint:  " +
                       repr(self.csp_norm))
            _log.debug("Supply fan cooling speed 1:  " + repr(self.fan1_norm))
            _log.debug("Supply fan cooling speed 2:  " + repr(self.fan2_norm))
            _log.debug("Normal minimum damper position:  " +
                       repr(self.min_damper))
            self.get_signal()

        def _pre_cpp_timer(self):
            """Schedule to run in get_signal"""
            _log.debug("Pre-cooling for CPP Event"
                       )  #pre-cool change cooling set point
            self._pre_csp()
            self.pre_timer = self.periodic_timer(settings.pre_time,
                                                 self._pre_cpp_cooling)

        def _pre_cpp_cooling(self):
            """start pre cooling procedure"""
            self.greenlet(self._pre_csp)

        def _pre_csp(self):
            """set cooling temp set point"""
            try:
                voltron_data = self._get_new_data()
            except green.Timeout:
                self._time_out()
            csp_now = float(voltron_data["ReturnAirCO2Stpt"])
            if csp_now > csp_pre and not csp < csp_pre:
                try:
                    csp = csp_now - self.cooling_slope
                    self._command_equip("ReturnAirCO2Stpt", csp)
                except green.Timeout:
                    self._time_out()
            elif csp_now <= csp_pre and not csp < csp_pre:
                try:
                    self._command_equip("ReturnAirCO2Stpt", settings.csp_pre)
                except green.Timeout:
                    self._time_out()
                self.pre_timer.cancel()

        def _accelerated_pre_cpp_timer(self):
            """if DR signal is received after normal pre"""
            _log.debug("Pre-cooling for CPP Event"
                       )  #pre-cool change cooling set point
            self._accelerated_pre_csp()
            self.pre_timer = self.periodic_timer(settings.pre_time,
                                                 self._accelerated_cpp_cooling)

        def _accelerated_cpp_cooling(self):
            """start accelerated pre-cooling"""
            self.greenlet(self._accelerated_pre_csp)

        def _accelerated_pre_csp(self):
            """set cooling temp set point"""
            _log.debug("Accelerated pre-cooling for CPP Event")
            try:
                voltron_data = self._get_new_data()
            except green.Timeout:
                self._time_out()
            csp_now = float(voltron_data["ReturnAirCO2Stpt"])
            csp = csp_now - self.accel_slope
            if csp_now > csp_pre and not csp < csp_pre:
                try:
                    self._command_equip("ReturnAirCO2Stpt", csp)
                except green.Timeout:
                    self._time_out()
            elif csp_now <= csp_pre or csp < csp_pre:
                try:
                    self._command_equip("ReturnAirCO2Stpt", settings.csp_pre)
                except green.Timeout:
                    self._time_out()
                self.pre_timer.cancel()

        def _during_cpp_timer(self):
            """during CPP scheduled in get_signal"""
            self.greenlet(self._during_cpp)

        def _during_cpp(self):
            """start CPP procedure"""
            # remove when done testing
            _log.debug("During CPP Event")
            damper_cpp = settings.damper_cpp
            fan_reduction = settings.fan_reduction
            csp_cpp = settings.csp_cpp
            cpp_fan1 = self.fan1_norm - self.fan1_norm * fan_reduction
            cpp_fan2 = self.fan2_norm - self.fan2_norm * fan_reduction
            try:
                self._command_equip("CoolSupplyFanSpeed1", cpp_fan1)
                self._command_equip("CoolSupplyFanSpeed2", cpp_fan2)
                self._command_equip("ReturnAirCO2Stpt", csp_cpp)
                self._command_equip('ESMDamperMinPosition', damper_cpp)
            except green.Timeout:
                self._time_out()

        def _after_cpp_timer(self):
            """after CPP scheduled in get_signal"""
            self.greenlet(self._restore_fan_damper)
            _log.debug("After CPP Event, returning to normal operations")
            self.greenlet(self._restore_cooling_setpoint)
            timer = settings.after_time
            self.after_timer = self.periodic_timer(timer,
                                                   self._after_cpp_cooling)

        def _after_cpp_cooling(self):
            """Start after CPP procedure"""
            _log.debug("After_CPP_COOLING")
            self.greenlet(self._restore_cooling_setpoint)

        def _restore_fan_damper(self):
            """restore original fan speeds"""
            try:
                self._command_equip("ESMDamperMinPosition", self.min_damper)
                self._command_equip("CoolSupplyFanSpeed1", self.fan1_norm)
                self._command_equip("CoolSupplyFanSpeed2", self.fan2_norm)
            except green.Timeout:
                self._time_out()

        def _restore_cooling_setpoint(self):
            """restore normal cooling temp setpoint"""
            try:
                voltron_data = self._get_new_data()
            except green.Timeout:
                self._time_out()
            csp_now = float(voltron_data["ReturnAirCO2Stpt"])
            if csp_now > self.csp_norm:
                csp = csp_now - self.cooling_slope
                try:
                    self._command_equip("ReturnAirCO2Stpt", csp)
                except green.Timeout:
                    self._time_out()
            elif csp_now <= self.csp_norm:
                self.after_timer.cancel()
                try:
                    self._command_equip("ReturnAirCO2Stpt", self.csp_norm)
                    self._sleep(60)
                    self.running = False
                except green.Timeout:
                    self._time_out()

        def periodic_timer(self, *args, **kwargs):
            timer = super(Agent, self).periodic_timer(*args, **kwargs)
            self.timers.append(timer)
            return timer

        def schedule(self, time, event):
            super(Agent, self).schedule(time, event)
            self.timers.append(event)

        def greenlet(self, *args, **kwargs):
            task = greenlet.greenlet(*args, **kwargs)
            self.tasks.append(task)
            current = greenlet.getcurrent()
            if current.parent is not None:
                task.parent = current.parent
            task.switch()

        def get_signal(self):
            """get and format DR signal and schedule DR proc."""
            time_now = time.mktime(datetime.datetime.now().timetuple())
            time_pre = time.mktime(
                datetime.datetime.now().replace(hour=settings.pre_cpp_hour,
                                                minute=0,
                                                second=0,
                                                microsecond=0).timetuple())
            time_event = time.mktime(datetime.datetime.now().replace(
                hour=settings.during_cpp_hour,
                minute=12,
                second=0,
                microsecond=0).timetuple())
            time_after = time.mktime(datetime.datetime.now().replace(
                hour=settings.after_cpp_hour,
                minute=14,
                second=0,
                microsecond=0).timetuple())

            if (settings.signal and time_now < time_pre):
                _log.debug("Scheduling1")
                time_step = settings.pre_time / 3600
                #self.cooling_slope = (self.csp_norm - settings.csp_pre) / ((((time_event - time_pre) / 3600) - 0.5) * time_step)
                self.cooling_slope = 1  # for testing use a constant
                temp = ((time_event - time_pre) / 3600)
                _log.debug("cooling slope: " + repr(self.cooling_slope))
                self.schedule(time_pre, sched.Event(self._pre_cpp_timer))
                self.schedule(time_event, sched.Event(self._during_cpp_timer))
                after_cpp_time = datetime.datetime.now().replace(
                    hour=settings.after_cpp_hour,
                    minute=59,
                    second=0,
                    microsecond=0)
                self.schedule(time_after, sched.Event(self._after_cpp_timer))
                #self.start_timer.cancel()
            elif (settings.signal and time_now > time_pre
                  and time_now < time_event):
                _log.debug("Scheduling2")
                #self.start_timer.cancel()
                #self.accel_slope = (self.csp_norm - settings.csp_pre) / ((time_event - time_now) / (3600))
                self.accel_slope = 2  #for testing use a constant
                #self.cooling_slope = (self.csp_norm - settings.csp_pre) / ((((time_event - time_pre) / 3600) - 0.5) * time_step)
                self.cooling_slope = 1  # for testing use a constant
                self.schedule(time_event, sched.Event(self._during_cpp_timer))
                self.schedule(time_after, sched.Event(self._after_cpp_timer))
                self._accelerated_pre_cpp_timer()
            elif (settings.signal and time_now > time_event
                  and time_now < time_after):
                _log.debug("Too late to pre-cool!")
                #self.start_timer.cancel()
                self.schedule(time_after, sched.Event(self._after_cpp_timer))
                self._during_cpp()
            else:
                _log.debug("CPP Event Is Over")
                #self.start_timer.cancel()
                self._sleep(60)
                self.get_signal()
Exemple #12
0
    class Agent(PublishMixin, BaseAgent):
        '''Agent listens to message bus device and runs when data is published.
        '''
        def __init__(self, **kwargs):
            super(Agent, self).__init__(**kwargs)
            self._update_event = None
            self._update_event_time = None
            self.keys = None
            self._device_states = {}
            self._required_subdevice_values = subdevices
            self._subdevice_values = {}
            self._kwargs = kwargs
            self.commands = {}
            self.current_point = None
            self.current_key = None
            if output_file != None:
                with open(output_file, 'w') as writer:
                    writer.close()
            self._header_written = False

        def initialize_subdevices(self):
            self._subdevice_values = {}
            for r in self._required_subdevice_values:
                for s in r:
                    self._subdevice_values[r][s] = None

        def should_run_now(self):
            if len(self._required_subdevice_values) < 1:
                return True

            def has_subdevice_value(unit, subdevice):
                return self.subdevice_value[unit][subdevice] != None

            for r in self._required_subdevice_values:
                for s in r:
                    if not has_subdevice_value(r, s):
                        return False

            return True

        @matching.match_exact(topics.DEVICES_VALUE(point='all', **device))
        def on_received_message(self, topic, headers, message, matched):
            '''Subscribe to device data and convert data to correct type for
            the driven application.
            '''
            _log.debug("Message received")
            _log.debug("MESSAGE: " + jsonapi.dumps(message[0]))
            _log.debug("TOPIC: " + topic)
            data = jsonapi.loads(message[0])
            if not converter.initialized and \
                config.get('conversion_map') is not None:
                converter.setup_conversion_map(config.get('conversion_map'),
                                               data.keys())
            data = converter.process_row(data)

            if len(self._required_subdevice_values) < 1:
                results = app_instance.run(datetime.now(), data)
                self._process_results(results)
            else:
                # apply data to subdevice values.
                if self.should_run_now():
                    results = app_instance.run(datetime.now(), self._subdevice_values)
                    self._process_results(results)

        @matching.match_exact(topics.ANALYSIS_VALUE(point='all', **device))
        def on_rec_analysis_message(self, topic, headers, message, matched):
            print('here!')


        def _process_results(self, results):
            '''Run driven application with converted data and write the app
            results to a file or database.
            '''
            _log.debug('Processing Results!')
            for key, value in results.commands.iteritems():
                _log.debug("COMMAND: {}->{}".format(key, value))
            for value in results.log_messages:
                _log.debug("LOG: {}".format(value))
            for key, value in results.table_output.iteritems():
                _log.debug("TABLE: {}->{}".format(key, value))
            if output_file != None:
                if len(results.table_output.keys()) > 0:
                    for _, v in results.table_output.items():
                        fname = output_file  # +"-"+k+".csv"
                        for r in v:
                            with open(fname, 'a+') as f:
                                keys = r.keys()
                                fout = csv.DictWriter(f, keys)
                                if not self._header_written:
                                    fout.writeheader()
                                    self._header_written = True
                                # if not header_written:
                                    # fout.writerow(keys)
                                fout.writerow(r)
                                f.close()
            if results.commands and mode:
                self.commands = results.commands
                if self.keys is None:
                    self.keys = self.commands.keys()
                self.schedule_task()

        def schedule_task(self):
            '''Schedule access to modify device controls.'''
            _log.debug('Schedule Device Access')
            headers = {
                'type':  'NEW_SCHEDULE',
                'requesterID': agent_id,
                'taskID': actuator_id,
                'priority': 'LOW'
                }
            start = datetime.now()
            end = start + td(seconds=30)
            start = str(start)
            end = str(end)
            self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers,
                              [["{campus}/{building}/{unit}".format(**device),
                                start, end]])

        def command_equip(self):
            '''Execute commands on configured device.'''
            self.current_key = self.keys[0]
            value = self.commands[self.current_key]
            headers = {
                'Content-Type': 'text/plain',
                'requesterID': agent_id,
                }
            self.publish(topics.ACTUATOR_SET(point=self.current_key, **device),
                         headers, str(value))

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_exact(topics.ACTUATOR_SCHEDULE_RESULT())
        def schedule_result(self, topic, headers, message, match):
            '''Actuator response (FAILURE, SUCESS).'''
            print 'Actuator Response'
            msg = jsonapi.loads(message[0])
            msg = msg['result']
            _log.debug('Schedule Device ACCESS')
            if self.keys:
                if msg == "SUCCESS":
                    self.command_equip()
                elif msg == "FAILURE":
                    print 'auto correction failed'
                    _log.debug('Auto-correction of device failed.')

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_VALUE(point='*', **device))
        def on_set_result(self, topic, headers, message, match):
            '''Setting of point on device was successful.'''
            print ('Set Success:  {point} - {value}'
                   .format(point=self.current_key,
                           value=str(self.commands[self.current_key])))
            _log.debug('set_point({}, {})'.
                       format(self.current_key,
                              self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                print 'Done with Commands - Release device lock.'
                headers = {
                    'type': 'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                    }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(),
                                  headers, {})
                self.keys = None

        @matching.match_headers({headers_mod.REQUESTER_ID: agent_id})
        @matching.match_glob(topics.ACTUATOR_ERROR(point='*', **device))
        def on_set_error(self, topic, headers, message, match):
            '''Setting of point on device failed, log failure message.'''
            print 'Set ERROR'
            msg = jsonapi.loads(message[0])
            msg = msg['type']
            _log.debug('Actuator Error: ({}, {}, {})'.
                       format(msg,
                              self.current_key,
                              self.commands[self.current_key]))
            self.keys.remove(self.current_key)
            if self.keys:
                self.command_equip()
            else:
                headers = {
                    'type':  'CANCEL_SCHEDULE',
                    'requesterID': agent_id,
                    'taskID': actuator_id
                    }
                self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(),
                                  headers, {})
                self.keys = None

        def publish_to_smap(self, smap_identifier, value, smap_identifier2,
                        value2, time_value):
            '''
            Push diagnostic results and energy
            impact to sMAP historian.
            '''
            self._log.debug(''.join(['Push to sMAP - ', smap_identifier, str(dx_msg),
                                     ' Energy Impact: ', str(energy_impact)]))
            if time_value is None:
                mytime = int(time.time())
            else:
                mytime = time.mktime(time_value.timetuple())
            if value2 is not None:
                content = {
                    smap_identifier: {
                         "Readings": [[mytime, value]],
                         "Units": "TU",
                         "data_type": "double"
                     },
                      smap_identifier2: {
                         "Readings": [[mytime, value2]],
                         "Units": "kWh/h",
                         "data_type": "double"}
                 }
            else:
                content = {
                    smap_identifier: {
                         "Readings": [[mytime, value]],
                         "Units": "TU",
                         "data_type": "double"
                     }
                }
            self._agent.publish(self.smap_path, self.headers, jsonapi.dumps(content))