def __init__(self, config_path, **kwargs): super(PublisherAgent2, self).__init__(**kwargs) self._config = load_config(config_path) self._src_file_handle = open(settings.source_file) header_line = self._src_file_handle.readline().strip() self._headers = header_line.split(',')
def __init__(self, config_path, **kwargs): super(AppLauncherAgent, self).__init__(**kwargs) self.config = utils.load_config(config_path) # self.app_number = 0 #connect to the database try: self.con = psycopg2.connect(host=db_host, port=db_port, database=db_database, user=db_user, password=db_password) self.cur = self.con.cursor() # open a cursor to perform database operations print("AppLauncher Agent connects to the database name {} successfully".format(db_database)) except: print("ERROR: {} fails to connect to the database name {}".format(app_name, db_database)) self.time_applauncher_start = datetime.datetime.now() self.already_started_previous_apps = False
def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) config = utils.load_config(config_path) self._connection_params = { key.upper(): val for key, val in config.get('connection', {}).iteritems()} for key in ['server', 'database', 'uid', 'pwd']: if key in kwargs: self._connection_params[key.upper()] = kwargs.pop(key) self._last = {} self._start_time = get_config('start_time') # Set defaults then do immediate update based on the default time in the config self.set_defaults() self.do_update()
def TestAgent(config_path, condition, **kwargs): config = utils.load_config(config_path) agent_id = config['agentid'] rtu_path = dict((key, config[key]) for key in ['campus', 'building', 'unit']) class Agent(PublishMixin, BaseAgent): def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) def setup(self): super(Agent, self).setup() self.damper = 0 with condition: condition.notify() @matching.match_regex(topics.ACTUATOR_LOCK_ACQUIRE() + '(/.*)') def on_lock_result(self, topic, headers, message, match): _log.debug("Topic: {topic}, {headers}, Message: {message}".format( topic=topic, headers=headers, message=message)) self.publish(topics.ACTUATOR_LOCK_RESULT() + match.group(0), headers, jsonapi.dumps('SUCCESS')) @matching.match_regex(topics.ACTUATOR_SET() + '(/.*/([^/]+))') def on_new_data(self, topic, headers, message, match): _log.debug("Topic: {topic}, {headers}, Message: {message}".format( topic=topic, headers=headers, message=message)) if match.group(2) == 'Damper': self.damper = int(message[0]) self.publish(topics.ACTUATOR_VALUE() + match.group(0), headers, message[0]) @periodic(5) def send_data(self): data = { 'ReturnAirTemperature': 55, 'OutsideAirTemperature': 50, 'MixedAirTemperature': 45, 'Damper': self.damper } self.publish_ex(topics.RTU_VALUE(point='all', **rtu_path), {}, ('application/json', jsonapi.dumps(data))) Agent.__name__ = 'TestAgent' return Agent(**kwargs)
def AhpAgent(config_path, **kwargs): config = utils.load_config(config_path) agent_id = config['agentid'] threshold = 14 def get_config(name): try: value = kwargs.pop(name) return value except KeyError: return config[name] # This agent uses the implementation of the ahp agorithm to determine what (if any) # devices in a building are to be curtailed to shed electrical load. The criteria # matrix is expected to be in a table in an excel spredsheet (to simplify data entry by the end user) # The agent will listen for information being sent from the bacnet drivers regarding the desired # devices, and on a periodic basis (to be set in the configuration file) perform calculations to # select the device(s) that are candidates for curtailment. This initial implementation will # be focused on heat pumps in a commercial building. As a result, the agent will also need # to keep track of the times when the compressor was stopped to allow for a period of equilibrium. # # TODO: # * Have the agent open and read the criteria matrix from the excel spreadsheet # * Subscribe to the topics that provide the information from the bacnet drivers # * Store the readings from the heat pumps that are to be watched class Agent(PublishMixin, BaseAgent): """Agent that performs curtailment in a building using the AHP algorithm""" def __init__(self, **kwars): super(Agent, self).__init__(**kwargs) def setup(self): # Load criteria matrix excel_doc = get_config("excel_doc") self.output_log = open("ahp_algorithm.log", "w") self.logger = LoggerWriter(_log, logging.DEBUG) self.logger.write("Testing") (self.criteria_labels, self.criteria_matrix) = extract_criteria_matrix(excel_doc) self.criteria_matrix_sums = calc_column_sums(self.criteria_matrix) # Validate criteria matrix if (not validate_input(self.criteria_matrix, self.criteria_matrix_sums, True, criteria_labels, criteria_labelstring, matrix_rowstring, display_dest=self.logger)): # TODO: log a warning indicatin invalid matrix input pass # TODO: Load device list. Right now, it will come from the config file. # Eventually this will come from the excel spreadsheet self.device_list = get_config('device_list') self.deviceLabels = [row[0] for row in self.device_list] self.deviceDataHandlers = {} for deviceRow in self.device_list: self.deviceDataHandlers[deviceRow[0]] = DeviceData( deviceRow[0], deviceRow[1], logger=self.logger) super(Agent, self).setup() # TODO: Set up subscriptions. Need to subscribe to sigma4/all @match_glob('RTU/PNNL/BOCC/Sigma4/HP*/all') def process_data(self, topic, headers, message, match): data = jsonapi.loads(message[0]) # print >> self.logger, topic, message device_label = topic.split('/')[4] if device_label in self.deviceDataHandlers: # look up device device = self.deviceDataHandlers[device_label] # call device process_data method device.process_data(time.time(), data) @periodic(600) def schedule_algorithm(self): # submit request for schedule to change points headers = { 'AgentID': agent_id, 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': agent_id, 'priority': 'LOW_PREEMPT' } # Build up schedule start = str(datetime.datetime.now()) end = str(datetime.datetime.now() + datetime.timedelta(minutes=1)) # msg = [['PNNL/BOCC/Sigma4/HP1', start, end]] msg = [] for label in self.deviceLabels: msg.append(['PNNL/BOCC/Sigma4/' + label, start, end]) print >> self.logger, "Submitting schedule" self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers, msg) print >> self.logger, "Schedule submitted" # Example from afddagent # 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]]) @match_headers({headers_mod.REQUESTER_ID: agent_id}) @match_exact(topics.ACTUATOR_SCHEDULE_RESULT()) def handle_scheduler_response(self, topic, headers, message, match): msg = jsonapi.loads(message[0]) response_type = headers.get('type', 0) if response_type == 'NEW_SCHEDULE': if msg.get('result', 0) == 'SUCCESS': self.logger.write("Schedule Successful") self.ready = True @match_headers({headers_mod.REQUESTER_ID: agent_id}) @match_start('RTU/actuators/schedule/announce') def do_algorithm(self, topic, headers, message, match): if not (headers[headers_mod.REQUESTER_ID] == agent_id): return if self.ready: self.ready = False print >> self.logger, "====== Calculate Curtailment ======" # The actual ahp algorithm stuff will happen as part of the response to a successful request for a schedule. device_matrix = generateMatrix(self.device_list, self.deviceDataHandlers) print >> self.logger, self.deviceDataHandlers scores = demo_ahp(self.criteria_matrix, device_matrix, self.deviceLabels, self.criteria_labels, criteria_labelstring, matrix_rowstring, display_dest=self.logger) # def demo_ahp(criteria_matrix, device_matrix, devices, criteria_labels="", criteria_labelstring="", matrix_rowstring="", display_dest=sys.stdout): pwr_saved = 0 device_offsets = [] for device in scores: if pwr_saved >= threshold: device_offsets.append(0.0) else: if self.deviceDataHandlers[ device[0]].curtailWithThreshold(): device_offsets.append(3.0) else: device_offsets.append(0.0) pwr_saved += 7 header = {'requesterID': agent_id} device_count = 0 for (device, score) in scores: path = 'RTU/actuators/set/PNNL/BOCC/Sigma4/' + device + '/Volttron_Temp_Offset' print >> self.logger, 'Updating %s with %d' % ( path, device_offsets[device_count]) self.publish(path, header, str(device_offsets[device_count])) device_count += 1 headers = { 'AgentID': agent_id, 'type': 'CANCEL_SCHEDULE', 'requesterID': agent_id, 'taskID': agent_id, } self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers, []) else: self.ready = False Agent.__name__ = 'AhpAgent' return Agent(**kwargs)
def PushAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: value = kwargs.pop(name) except KeyError: return config[name] agent_id = get_config('agentid') log_path = get_config('log_path') periodic_days = get_config('periodic_days') class Agent(PublishMixin, BaseAgent): '''This agent grabs data from a database then pushes that data to sMAP based on the paths returned. On startup, it sets the latest time for all retrieved points to the start_time value. If this is <0 then it uses current time. After that, latest time is the value returned by the DB query. This has the potential for a problem if data fails to be inserted into sMAP since the agent will not request it again. ''' #TODO: The agent should verify that data went into sMAP and the #latest time for a point should be based on that, not on the # time returned by the DB. #TODO: Ideally, on startup the agent would try to find the #most recent time for each point in sMAP. Not sure there's #an easy query to do this through sMAP so may require #backing up time range queries until you find data. def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) config = utils.load_config(config_path) self._connection_params = { key.upper(): val for key, val in config.get('connection', {}).iteritems()} for key in ['server', 'database', 'uid', 'pwd']: if key in kwargs: self._connection_params[key.upper()] = kwargs.pop(key) self._last = {} self._start_time = get_config('start_time') # Set defaults then do immediate update based on the default time in the config self.set_defaults() self.do_update() def setup(self): # Always call the base class setup() super(Agent, self).setup() def set_defaults(self): conn = Connection(**self._connection_params) if (self._start_time < 0): self._start_time = time.time() # _log.debug(DatetimeFromValue(self._start_time)) for chan_id, path, units, latest in conn.get_points(): stamp = DatetimeFromValue(latest) if latest != None else '' self._last[chan_id] = self._start_time _log.debug('{chan}: {path} {units} - {latest}'.format(chan=chan_id, path=path, latest=stamp, units=units)) # @periodic(periodic_days * 24 * 60 * 60) @periodic(60 * 60) def do_update(self): conn = Connection(**self._connection_params) for chan_id, path, units, latest in conn.get_points(): _log.debug(str(chan_id) + " " + path + " " + str(DatetimeFromValue(self._last.get(chan_id)))) values = conn.get_values(chan_id, self._last.get(chan_id)) if values: # Send them to sMAP self._last[chan_id] = values[-1][0] time.sleep(3) self.log_to_smap(log_path, path, values, units) def log_to_smap(self, log_source, path, values, units="Units"): '''Push data to sMAP. This will push data into a path off the main source: /Source/log_source/path. If logging data is to go right beside real data, log source will need to be removed. ''' _log.debug( path) headers = {} headers[headers_mod.FROM] = agent_id headers[headers_mod.CONTENT_TYPE] = headers_mod.CONTENT_TYPE.JSON # headers['SourceName'] = log_source # Split full path to get path and point name path_to_point = path[0:path.rfind('/')] point = path[path.rfind('/')+1:len(path)] content = { point: { "Readings": values, "Units": units, "data_type": "double" } } topic = 'datalogger/log/'+log_source+path_to_point self.publish(topic, headers, json.dumps(content)) @matching.match_headers({headers_mod.TO: agent_id}) @matching.match_exact('datalogger/status') def on_logger_status(self, topic, headers, message, match): if message != ["Success"]: _log.error("Logging attempt failed") Agent.__name__ = 'SMDSPushAgent' return Agent(**kwargs)
def DemandResponseAgent(config_path, **kwargs): """DR application for time of use pricing""" config = utils.load_config(config_path) agent_id = config['agentid'] rtu_path = dict((key, config[key]) for key in ['campus', 'building', 'unit']) command_timeout = config.get('command-timeout', settings.default_command_timeout) csp_pre = config.get('csp_pre', settings.csp_pre) csp_cpp = config.get('csp_cpp', settings.csp_cpp) damper_cpp = config.get('damper_cpp', settings.damper_cpp) fan_reduction = config.get('fan_reduction', settings.fan_reduction) time_steps_perhour = config.get('time_steps_perhour', settings.pre_time) Schedule = config.get('Schedule') max_precool_hours = config.get('max_precool_hours') datefmt = '%m-%d-%y %H:%M' cpp_end_hour = config.get('cpp_end_hour') timestep_length = config.get('timestep_length') building_thermal_constant = config.get('building_thermal_constant') 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.RTU_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) Agent.__name__ = 'DemandResponseAgent' return Agent(**kwargs)
def ArchiverAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: value = kwargs.pop(name) except KeyError: return config[name] agent_id = get_config('agentid') source_name = get_config('source_name') archiver_url = get_config('archiver_url') class Agent(PublishMixin, BaseAgent): def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) # self.subscribe(settings.REQUEST_TOPIC, self.handle_request) @match_start(topics.BASE_ARCHIVER_REQUEST) def handle_request(self, topic, headers, message, matched): # Path is part of topic. path = topic[len(topics.BASE_ARCHIVER_REQUEST):] # Range is message. It will either be "start"-"end" or 1h, 1d, # etc... from now range_str = message[0] source = headers.get('SourceName', source_name) # Find UUID for path payload = ('select uuid where Metadata/SourceName="{}" ' 'and Path="{}"'.format(source, path)) done = False retries = 0 while not done and retries <= 5: # TODO: Need to do some error handling here! try: r = requests.post(archiver_url, data=payload) if r.status_code == 200: # Data should be a list of dictionaries at this point in time uuid_list = jsonapi.loads(r.text) done = True else: print str(retries) + ": " + str( r.status_code) + ": " + payload retries += 1 except ValueError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 except ConnectionError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 #Can get a 503 network busy #TODO: Respond with error # TODO: Need to do some error handling here! # Data should be a list of dictionaries at this point in time if not uuid_list: # TODO: log this error return payload_template = "select data in {} where {{}}".format(range_str) if len(uuid_list) == 0: return uuid_clause = "uuid='{}'".format(uuid_list[0]['uuid']) for stream in uuid_list[1:]: uuid_clause.append(" or uuid='{}'".format(stream['uuid'])) payload = payload_template.format(uuid_clause) full_data = None tries = 0 done = False while not done and retries <= 5: # TODO: Need to do some error handling here! try: r = requests.post(archiver_url, data=payload) if 'Syntax error' in r.text: # TODO Log this error self.publish(topics.BASE_ARCHIVER_RESPONSE + path, None, 'Syntax error in date range') return # Request data for UUID in range #[{"uuid": "5b94d5ed-1e1d-51cf-a6d8-afae5c055292", "Readings": [[1368750281000.0, 75.5], [1368750341000.0, 75.5], ... done = True except ValueError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 except ConnectionError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 if 'Syntax error' in r.text: # TODO Log this error self.publish(topics.BASE_ARCHIVER_RESPONSE + path, None, 'Syntax error in date range') return # Request data for UUID in range #[{"uuid": "5b94d5ed-1e1d-51cf-a6d8-afae5c055292", # "Readings": [[1368750281000.0, 75.5], [1368750341000.0, 75.5], ... full_data = jsonapi.loads(r.text) data = full_data[0].get('Readings', []) pub_headers = { headers_mod.FROM: 'ArchiverAgent', headers_mod.TO: headers[headers_mod.FROM] if headers_mod.FROM in headers else 'Unknown' } if data > 0: # There was data for this stream in the specified range. # Convert data to json and publish self.publish_json(topics.BASE_ARCHIVER_RESPONSE + path, pub_headers, data) @match_start(topics.BASE_ARCHIVER_FULL_REQUEST) def handle_full_request(self, topic, headers, message, matched): # Path is part of topic. path = topic[len(topics.BASE_ARCHIVER_FULL_REQUEST):] result_layout = headers.get('ResultLayout', 'HIERARCHICAL') # Range is message. It will either be "start"-"end" or 1h, 1d, # etc... from now range_str = message[0] source = headers.get('SourceName', source_name) # Find UUID for path payload = ('select * where Metadata/SourceName="{}" ' 'and Path~"{}"'.format(source, path)) done = False retries = 0 while not done and retries <= 5: # TODO: Need to do some error handling here! try: r = requests.post(archiver_url, data=payload) if r.status_code == 200: # Data should be a list of dictionaries at this point in time uuid_list = jsonapi.loads(r.text) done = True else: print str(retries) + ": " + str( r.status_code) + ": " + payload retries += 1 except ValueError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 except ConnectionError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 #Can get a 503 network busy #TODO: Respond with error # TODO: Need to do some error handling here! # Data should be a list of dictionaries at this point in time if not uuid_list: # TODO: log this error return timeseries = {} hierarchichal = {} payload_template = "select data in {} where {{}}".format(range_str) if len(uuid_list) == 0: return uuid_clause = "uuid='{}'".format(uuid_list[0]['uuid']) for stream in uuid_list[1:]: uuid_clause += (" or uuid='{}'".format(stream['uuid'])) payload = payload_template.format(uuid_clause) # # Request data and store Readings in timeseries[path] full_data = None tries = 0 done = False while not done and retries <= 5: # TODO: Need to do some error handling here! try: r = requests.post(archiver_url, data=payload) if 'Syntax error' in r.text: # TODO Log this error self.publish(topics.BASE_ARCHIVER_RESPONSE + path, None, 'Syntax error in date range') return # Request data for UUID in range #[{"uuid": "5b94d5ed-1e1d-51cf-a6d8-afae5c055292", "Readings": [[1368750281000.0, 75.5], [1368750341000.0, 75.5], ... done = True except ValueError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 except ConnectionError as e: print str(retries) + ": " + str(e) + ": " + payload retries += 1 if 'Syntax error' in r.text: # TODO Log this error self.publish(topics.BASE_ARCHIVER_RESPONSE + path, None, 'Syntax error in date range') return # Request data for UUID in range #[{"uuid": "5b94d5ed-1e1d-51cf-a6d8-afae5c055292", # "Readings": [[1368750281000.0, 75.5], [1368750341000.0, 75.5], ... full_data = jsonapi.loads(r.text) reading_dict = {} for readings in full_data: reading_dict[readings['uuid']] = readings.get('Readings', []) for stream in uuid_list: # uuid_clause += (" or uuid='{}'".format(stream['uuid'])) path = stream['Path'] print stream (hierarchichal, timeseries) = build_paths(path, hierarchichal, timeseries) timeseries[path]['uuid'] = stream['uuid'] timeseries[path]['Properties'] = stream['Properties'] timeseries[path]['Path'] = path timeseries[path]['Readings'] = reading_dict[stream['uuid']] pub_headers = { headers_mod.FROM: 'ArchiverAgent', headers_mod.TO: headers[headers_mod.FROM] if headers_mod.FROM in headers else 'Unknown' } if result_layout == 'FLAT': self.publish_json(topics.BASE_ARCHIVER_RESPONSE + path, pub_headers, timeseries) else: self.publish_json(topics.BASE_ARCHIVER_RESPONSE + path, pub_headers, hierarchichal) Agent.__name__ = 'ArchiverAgent' return Agent(**kwargs)
def SMDSAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: value = kwargs.pop(name) except KeyError: return config[name] agent_id = get_config('agentid') time_window_minutes = get_config('time_window_minutes') rtu_path = { 'campus': get_config('campus'), 'building': get_config('building'), 'unit': get_config('unit'), } class Agent(PublishMixin, BaseAgent): '''This agent grabs a day's worth of data for a Catalyst's Data points out of the historian. It then sends the data on to an application in the cloud. ''' def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) self._raw_air_temp = None self._raw_fan_speed = None self._raw_unit_power = None def setup(self): self._agent_id = get_config('agentid') self._service_url = get_config('service_url') self._provider_id = get_config('provider_id') self._unit_power_chan = get_config('unit_power_chan') self._outdoor_temp_chan = get_config('outdoor_temp_chan') self._fan_supply_chan = get_config('fan_supply_chan') self._campusid = get_config('campus') self._buildingid = get_config('building') self._deviceid = get_config('unit') # self._time_window_minutes = int(self.config['time_window_minutes']) self._backlog_hours = get_config('backlog_hours') # Always call the base class setup() super(Agent, self).setup() self.setup_topics() self._catching_up = True self._last_update = datetime.now() - timedelta( hours=self._backlog_hours) self._query_end_time = None self.publish_requests() def setup_topics(self): self.request_temptopic = topics.ARCHIVER_REQUEST( point='OutsideAirTemperature', **rtu_path) self.request_powertopic = topics.ARCHIVER_REQUEST( point='UnitPower', **rtu_path) self.request_fantopic = topics.ARCHIVER_REQUEST( point='SupplyFanSpeed', **rtu_path) @matching.match_headers({headers_mod.TO: agent_id}) @matching.match_exact( topics.ARCHIVER_RESPONSE(point='OutsideAirTemperature', **rtu_path)) def on_temp_response(self, topic, headers, message, match): '''Method for dealing with temp data from smap''' if DEBUG: print "Topic: {topic}, Headers: {headers}, Message: {message}".format( topic=topic, headers=headers, message=message) self._raw_air_temp = message[0] self.go_if_ready() @matching.match_exact( topics.ARCHIVER_RESPONSE(point='UnitPower', **rtu_path)) @matching.match_headers({headers_mod.TO: agent_id}) def on_unit_power(self, topic, headers, message, match): '''Method for dealing with power data from smap''' if DEBUG: print "Topic: {topic}, Headers: {headers}, Message: {message}".format( topic=topic, headers=headers, message=message) self._raw_unit_power = message[0] self.go_if_ready() @matching.match_headers({headers_mod.TO: agent_id}) @matching.match_exact( topics.ARCHIVER_RESPONSE(point='SupplyFanSpeed', **rtu_path)) def on_fan_speed(self, topic, headers, message, match): '''Method for dealing with fan data from smap''' if DEBUG: print "Topic: {topic}, Headers: {headers}, Message: {message}".format( topic=topic, headers=headers, message=message) self._raw_fan_speed = message[0] self.go_if_ready() def go_if_ready(self): if (self._raw_air_temp != None and self._raw_fan_speed != None and self._raw_unit_power != None): message = self.convert_raw() worked = self.post_data(message) if (worked): self._raw_air_temp = None self._raw_fan_speed = None self._raw_unit_power = None self._last_update = self._query_end_time if self._catching_up: # self.publish_requests self.timer(1, self.publish_requests) def make_dataset(self, message, channelid, units): list = eval(message) values = [] if DEBUG: print len(list) if len(list) >= 1: start_time = list[0][0] time_index = start_time for data in list: values.append({ "Utc": "/Date({})/".format(str(int(data[0]))), "Val": data[1] }) return {"ChannelId": channelid, "Units": units, "Values": values} def convert_raw(self): dataset = [] dataset.append( self.make_dataset( self._raw_air_temp, self._provider_id + "/" + self._outdoor_temp_chan, "DegreesF")) dataset.append( self.make_dataset( self._raw_fan_speed, self._provider_id + "/" + self._fan_supply_chan, "%")) dataset.append( self.make_dataset( self._raw_unit_power, self._provider_id + "/" + self._unit_power_chan, "kW")) providerid = self._provider_id reply = {"ProviderId": providerid, "Datalogs": dataset} # reply = json.dumps(reply).replace('/','\/') if DEBUG: print json.dumps(reply, sort_keys=True, indent=4, separators=(',', ': ')) return reply # Periodically get data and push to cloud service @periodic(time_window_minutes * 60) def publish_requests(self): '''Publish lookup requests to the ArchiverAgent ''' now = datetime.now() if (now - self._last_update) > timedelta( minutes=time_window_minutes): self._catching_up = True self._query_end_time = self._last_update + timedelta( minutes=time_window_minutes) else: self._catching_up = False self._query_end_time = now # if DEBUG: #Print a readable time range start = self._last_update.strftime(readable_format) end = (self._query_end_time).strftime(readable_format) print '({start}, {end})'.format(start=start, end=end) start = self._last_update.strftime(date_format) end = (self._query_end_time).strftime(date_format) if DEBUG: print '({start}, {end})'.format(start=start, end=end) headers = { headers_mod.FROM: agent_id, headers_mod.TO: 'ArchiverAgent' } self.publish(self.request_temptopic, headers, '({start}, {end})'.format(start=start, end=end)) self.publish(self.request_powertopic, headers, '({start}, {end})'.format(start=start, end=end)) self.publish(self.request_fantopic, headers, '({start}, {end})'.format(start=start, end=end)) def post_data(self, params): post_data = json.dumps(params) headers_post = { 'Content-Type': 'application/json', 'User-Agent': 'RTUNetwork', 'Accept': 'application/json', 'Connection': 'close' } done = False tries = 0 while (done != True and tries < 5): try: response = requests.post(self._service_url, data=post_data, headers=headers_post) done = True except ConnectionError as e: print '{}: {}: {}'.format(str(tries), str(e), post_data) tries += 1 worked = False if (response.content != None and response.content != ""): root = ET.fromstring(response.content) is_error = root.find( '{http://schemas.datacontract.org/2004/07/RestServiceWebRoles}IsError' ).text transaction_id = root.find( '{http://schemas.datacontract.org/2004/07/RestServiceWebRoles}TransactionId' ).text worked = is_error.lower( ) == 'false' and int(transaction_id) > 0 else: worked = False return worked Agent.__name__ = 'SMDSAgent' return Agent(**kwargs)
def ThermostatAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: kwargs.pop(name) except KeyError: return config.get(name, '') # 1. @params agent agent_id = get_config('agent_id') LOG_DATA_PERIOD = get_config('poll_time') device_monitor_time = get_config('device_monitor_time') publish_address = 'ipc:///tmp/volttron-lite-agent-publish' subscribe_address = 'ipc:///tmp/volttron-lite-agent-subscribe' debug_agent = False agentknowledge = dict(day=["day"], hour=["hour"], minute=["minute"], temperature=["temp", "temperature", "current_temp"], thermostat_mode=["tmode", "ther_mode", "thermostat_mode"], fan_mode=["fmode", "fan_mode"], heat_setpoint=["t_heat", "temp_heat", "heat_setpoint"], cool_setpoint=["t_cool", "temp_cool", "cool_setpoint"], thermostat_state=["tstate", "thermostat_state"], fan_state=["fstate", "fan_state"]) agentAPImapping = dict(temperature=[], thermostat_mode=[], fan_mode=[], heat_setpoint=[], cool_setpoint=[], thermostat_state=[], fan_state=[]) # 2. @params device_info building_name = get_config('building_name') zone_id = get_config('zone_id') # room = get_config('room') model = get_config('model') device_type = get_config('type') address = get_config('address') _address = address _address = _address.replace('http://', '') _address = _address.replace('https://', '') try: # validate whether or not address is an ip address socket.inet_aton(_address) ip_address = _address # print "yes ip_address is {}".format(ip_address) except socket.error: # print "yes ip_address is None" ip_address = None identifiable = get_config('identifiable') # mac_address = get_config('mac_address') # 3. @params agent & DB interfaces # get database parameters from settings.py, add db_table for specific table db_host = get_config('db_host') db_port = get_config('db_port') db_database = get_config('db_database') db_user = get_config('db_user') db_password = get_config('db_password') db_table_thermostat = settings.DATABASES['default']['TABLE_thermostat'] db_table_notification_event = settings.DATABASES['default']['TABLE_notification_event'] # construct _topic_Agent_UI based on data obtained from DB _topic_Agent_UI = building_name+'/'+str(zone_id)+'/'+device_type+'/'+agent_id + '/' # print(_topic_Agent_UI) # construct _topic_Agent_sMAP based on data obtained from DB _topic_Agent_sMAP = 'datalogger/log/'+building_name+'/'+str(zone_id)+'/'+device_type+'/'+agent_id # print(_topic_Agent_sMAP) # 4. @params device_api api = get_config('api') # discovery agent locate the location of api in launch file e.g. "api": "testAPI.classAPI_RadioThermostat", apiLib = importlib.import_module("testAPI."+api) # print("testAPI."+api) # 4.1 initialize thermostat device object Thermostat = apiLib.API(model=model, device_type=device_type, api=api, address=address, agent_id=agent_id) print("{0}agent is initialized for {1} using API={2} at {3}".format(agent_id, Thermostat.get_variable('model'), Thermostat.get_variable('api'), Thermostat.get_variable('address'))) # 5. @params notification_info send_notification = True email_fromaddr = settings.NOTIFICATION['email']['fromaddr'] email_recipients = settings.NOTIFICATION['email']['recipients'] email_username = settings.NOTIFICATION['email']['username'] email_password = settings.NOTIFICATION['email']['password'] email_mailServer = settings.NOTIFICATION['email']['mailServer'] notify_heartbeat = settings.NOTIFICATION['heartbeat'] class Agent(PublishMixin, BaseAgent): # 1. agent initialization def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) #1. initialize all agent variables self.variables = kwargs self.valid_data = False self._keep_alive = True self.first_time_update = True self.topic = _topic_Agent_sMAP self.ip_address = ip_address if ip_address != None else None self.flag = 1 self.authorized_thermostat_mode = None self.authorized_fan_mode = None self.authorized_heat_setpoint = None self.authorized_cool_setpoint = None self.time_sent_notifications_device_tampering = datetime.datetime.now() self.first_time_detect_device_tampering = True self.event_ids = list() self.time_sent_notifications = {} self.notify_heartbeat = notify_heartbeat self._override = False #2. setup connection with db -> Connect to bemossdb database try: self.con = psycopg2.connect(host=db_host, port=db_port, database=db_database, user=db_user, password=db_password) self.cur = self.con.cursor() # open a cursor to perform database operations print("{} connects to the database name {} successfully".format(agent_id, db_database)) except: print("ERROR: {} fails to connect to the database name {}".format(agent_id, db_database)) #3. send notification to notify building admin self.send_notification = send_notification # self.time_send_notification = 0 # if self.send_notification: self.subject = 'Message from ' + agent_id # self.text = 'Now an agent device_type {} for {} with API {} at address {} is launched!'.format( # Thermostat.get_variable('device_type'), Thermostat.get_variable('model'), # Thermostat.get_variable('api'), Thermostat.get_variable('address')) # emailService = EmailService() # emailService.sendEmail(email_fromaddr, email_recipients, email_username, email_password, self.subject, # self.text, email_mailServer) # These set and get methods allow scalability def set_variable(self, k, v): # k=key, v=value self.variables[k] = v def get_variable(self, k): return self.variables.get(k, None) # default of get_variable is none # 2. agent setup method def setup(self): super(Agent, self).setup() #1. Do a one time push when we start up so we don't have to wait for the periodic self.timer(1, self.deviceMonitorBehavior) if identifiable == "True": Thermostat.identifyDevice() try: # update initial value of override column of a thermostat to False self.cur.execute("UPDATE "+db_table_thermostat+" SET override=%s WHERE thermostat_id=%s", (self._override, agent_id)) self.con.commit() except: print "{} >> cannot update override column of thermostat".format(agent_id) # 3. deviceMonitorBehavior (CyclicBehavior) @periodic(device_monitor_time) def deviceMonitorBehavior(self): # step1: get current status of a thermostat, then map keywords and variables to agent knowledge try: Thermostat.getDeviceStatus() # mapping variables from API to Agent's knowledge for APIKeyword, APIvariable in Thermostat.variables.items(): if debug_agent: print (APIKeyword, APIvariable) self.set_variable(self.getKeyword(APIKeyword), APIvariable) # set variables of agent from API variables agentAPImapping[self.getKeyword(APIKeyword)] = APIKeyword # map keyword of agent and API except: print("device connection is not successful") if self.first_time_update: if self.get_variable('heat_setpoint') is None: self.set_variable('heat_setpoint', 70) else: pass if self.get_variable('cool_setpoint') is None: self.set_variable('cool_setpoint', 70) else: pass self.first_time_update = False else: pass # step2: send notification to a user if required if self.send_notification: self.track_event_send_notification() # step3: update PostgresQL (meta-data) database try: self.cur.execute("UPDATE "+db_table_thermostat+" SET temperature=%s WHERE thermostat_id=%s", (self.get_variable('temperature'), agent_id)) self.con.commit() self.cur.execute("UPDATE "+db_table_thermostat+" SET fan_mode=%s WHERE thermostat_id=%s", (self.get_variable('fan_mode'), agent_id)) self.con.commit() if self.get_variable('battery') is not None: self.cur.execute("UPDATE "+db_table_thermostat+" SET battery=%s WHERE thermostat_id=%s", (self.get_variable('battery'), agent_id)) self.con.commit() if self.get_variable('thermostat_mode') == "HEAT": self.cur.execute("UPDATE "+db_table_thermostat+" SET heat_setpoint=%s WHERE thermostat_id=%s", (self.get_variable('heat_setpoint'), agent_id)) self.con.commit() self.cur.execute("UPDATE "+db_table_thermostat+" SET thermostat_mode=%s WHERE thermostat_id=%s", ('HEAT', agent_id)) self.con.commit() elif self.get_variable('thermostat_mode') == "COOL": self.cur.execute("UPDATE "+db_table_thermostat+" SET cool_setpoint=%s WHERE thermostat_id=%s", (self.get_variable('cool_setpoint'), agent_id)) self.con.commit() self.cur.execute("UPDATE "+db_table_thermostat+" SET thermostat_mode=%s WHERE thermostat_id=%s", ('COOL', agent_id)) self.con.commit() elif self.get_variable('thermostat_mode') == "OFF": self.cur.execute("UPDATE "+db_table_thermostat+" SET thermostat_mode=%s WHERE thermostat_id=%s", ('OFF', agent_id)) self.con.commit() elif self.get_variable('thermostat_mode') == "AUTO": self.cur.execute("UPDATE "+db_table_thermostat+" SET thermostat_mode=%s WHERE thermostat_id=%s", ('AUTO', agent_id)) self.con.commit() else: pass if self.ip_address != None: psycopg2.extras.register_inet() _ip_address = psycopg2.extras.Inet(self.ip_address) self.cur.execute("UPDATE "+db_table_thermostat+" SET ip_address=%s WHERE thermostat_id=%s", (_ip_address, agent_id)) self.con.commit() _time_stamp_last_scanned = str(datetime.datetime.now()) self.cur.execute("UPDATE "+db_table_thermostat+" SET last_scanned_time=%s " "WHERE thermostat_id=%s", (_time_stamp_last_scanned, agent_id)) self.con.commit() print("{} updates database name {} during deviceMonitorBehavior successfully".format(agent_id,db_database)) except: print("ERROR: {} failed to update database name {}".format(agent_id, db_database)) # step4: update sMAP (time-series) database try: self.publish_logdata1() self.publish_logdata2() self.publish_logdata3() self.publish_logdata4() self.publish_logdata5() self.publish_logdata6() self.publish_logdata7() print "{} success update sMAP database\n".format(agent_id) except: print("ERROR: {} fails to update sMAP database".format(agent_id)) # step5: debug agent knowledge if debug_agent: print("printing agent's knowledge") for k, v in self.variables.items(): print (k, v) print('') if debug_agent: print("printing agentAPImapping's fields") for k, v in agentAPImapping.items(): if k is None: agentAPImapping.update({v: v}) agentAPImapping.pop(k) for k, v in agentAPImapping.items(): print (k, v) def getKeyword(self, APIKeyword): for k, v in agentknowledge.items(): if APIKeyword in agentknowledge[k]: return k flag = 1 break else: flag = 0 pass if flag == 0: # if flag still 0 means that a APIKeyword is not in an agent knowledge, # then add it to agent knowledge return APIKeyword # 4. updateUIBehavior (generic behavior) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'device_status') def updateUIBehavior(self, topic, headers, message, match): print agent_id + " got\nTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}\n".format(message=message) #reply message topic = '/agent/ui/'+_topic_Agent_UI+'device_status/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, # headers_mod.DATE: now, headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } #TODO add battery field to _data if self.get_variable('battery') != None: _data = {'temperature': self.get_variable('temperature'), 'thermostat_mode': self.get_variable('thermostat_mode'), 'fan_mode': self.get_variable('fan_mode'), 'heat_setpoint': self.get_variable('heat_setpoint'), 'cool_setpoint': self.get_variable('cool_setpoint'), 'thermostat_state': self.get_variable('thermostat_state'), 'fan_state': self.get_variable('fan_state'), 'battery': self.get_variable('battery'), 'override': self._override } else: _data = {'temperature': self.get_variable('temperature'), 'thermostat_mode': self.get_variable('thermostat_mode'), 'fan_mode': self.get_variable('fan_mode'), 'heat_setpoint': self.get_variable('heat_setpoint'), 'cool_setpoint': self.get_variable('cool_setpoint'), 'thermostat_state': self.get_variable('thermostat_state'), 'fan_state': self.get_variable('fan_state'), 'override': self._override } message = json.dumps(_data) message = message.encode(encoding='utf_8') self.publish(topic, headers, message) # 5. deviceControlBehavior (generic behavior) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'update') def deviceControlBehavior(self, topic, headers, message, match): print agent_id + " got\nTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}\n".format(message=message) #step1: change device status according to the receive message if self.isPostmsgValid(message[0]): # check if the data is valid # _data = json.dumps(message[0]) _data = json.loads(message[0]) for k, v in _data.items(): if k == 'thermostat_mode': self.authorized_thermostat_mode = _data.get('thermostat_mode') if _data.get('thermostat_mode') == "HEAT": for k, v in _data.items(): if k == 'heat_setpoint': self.authorized_heat_setpoint = _data.get('heat_setpoint') else: pass elif _data.get('thermostat_mode') == "COOL": for k, v in _data.items(): if k == 'cool_setpoint': self.authorized_cool_setpoint = _data.get('cool_setpoint') else: pass elif k == 'fan_mode': self.authorized_fan_mode = _data.get('fan_mode') else: pass print "{} >> self.authorized_thermostat_mode {}".format(agent_id, self.authorized_thermostat_mode) print "{} >> self.authorized_heat_setpoint {}".format(agent_id, self.authorized_heat_setpoint) print "{} >> self.authorized_cool_setpoint {}".format(agent_id, self.authorized_cool_setpoint) print "{} >> self.authorized_fan_mode {}".format(agent_id, self.authorized_fan_mode) setDeviceStatusResult = Thermostat.setDeviceStatus(json.loads(message[0])) # convert received message from string to JSON #TODO need to do additional checking whether the device setting is actually success!!!!!!!! #step2: update agent's knowledge on this device Thermostat.getDeviceStatus() #step3: send reply message back to the UI topic = '/agent/ui/'+_topic_Agent_UI+'update/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT, # headers_mod.DATE: now, } if setDeviceStatusResult: message = 'success' else: message = 'failure' else: print("The POST message is invalid, check thermostat_mode, heat_setpoint, cool_setpoint " "setting and try again\n") message = 'failure' self.publish(topic, headers, message) def isPostmsgValid(self, postmsg): # check validity of postmsg dataValidity = True try: # _data = json.dumps(postmsg) _data = json.loads(postmsg) for k, v in _data.items(): if k == 'thermostat_mode': self.authorized_thermostat_mode = _data.get('thermostat_mode') if _data.get('thermostat_mode') == "HEAT": for k, v in _data.items(): if k == 'heat_setpoint': self.authorized_heat_setpoint = _data.get('heat_setpoint') elif k == 'cool_setpoint': dataValidity = False break else: pass elif _data.get('thermostat_mode') == "COOL": for k, v in _data.items(): if k == 'cool_setpoint': self.authorized_cool_setpoint = _data.get('cool_setpoint') elif k == 'heat_setpoint': dataValidity = False break else: pass elif k == 'fan_mode': self.authorized_fan_mode = _data.get('fan_mode') else: pass except: dataValidity = True print("dataValidity failed to validate data comes from UI") return dataValidity # 6. deviceIdentifyBehavior (generic behavior) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'identify') def deviceIdentifyBehavior(self, topic, headers, message, match): print agent_id+ " got\nTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}\n".format(message=message) #step1: change device status according to the receive message identifyDeviceResult = Thermostat.identifyDevice() #TODO need to do additional checking whether the device setting is actually success!!!!!!!! #step2: send reply message back to the UI topic = '/agent/ui/'+_topic_Agent_UI+'identify/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT, # headers_mod.DATE: now, } if identifyDeviceResult: message = 'success' else: message = 'failure' self.publish(topic, headers, message) # Filter agent knowledge before sending out data to sMAP def publish_logdata1(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) content = { "temperature": { "Readings": [[mytime, float(self.get_variable("temperature"))]], "Units": "F", "data_type": "double" } } print("{} published temperature to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata2(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) if self.get_variable("thermostat_mode") == "OFF": _thermostat_mode = 0 elif self.get_variable("thermostat_mode") == "HEAT": _thermostat_mode = 1 elif self.get_variable("thermostat_mode") == "COOL": _thermostat_mode = 2 elif self.get_variable("thermostat_mode") == "AUTO": _thermostat_mode = 3 else: _thermostat_mode = 4 content = { "thermostat_mode": { "Readings": [[mytime, float(_thermostat_mode)]], "Units": "N/A", "data_type": "double" } } print("{} published thermostat_mode to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata3(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) if self.get_variable("fan_mode") == "AUTO": _fan_mode = 0 elif self.get_variable("fan_mode") == "CIRCULATE": _fan_mode = 1 elif self.get_variable("fan_mode") == "ON": _fan_mode = 2 else: _fan_mode = 3 content = { "fan_mode": { "Readings": [[mytime, float(_fan_mode)]], "Units": "N/A", "data_type": "double" } } print("{} published fan_mode to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata4(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) content = { "heat_setpoint": { "Readings": [[mytime, float(self.get_variable("heat_setpoint"))]], "Units": "F", "data_type": "double" } } print("{} published heat_setpoint to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata5(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) content = { "cool_setpoint": { "Readings": [[mytime, float(self.get_variable("cool_setpoint"))]], "Units": "F", "data_type": "double" } } print("{} published cool_setpoint to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata6(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) if self.get_variable("thermostat_state") == "OFF": _thermostat_state = 0 elif self.get_variable("thermostat_state") == "HEAT": _thermostat_state = 1 elif self.get_variable("thermostat_state") == "COOL": _thermostat_state = 2 else: _thermostat_state = 3 content = { "thermostat_state": { "Readings": [[mytime, float(_thermostat_state)]], "Units": "N/A", "data_type": "double" } } print("{} published thermostat_state to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata7(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) if self.get_variable("fan_state") == "OFF": _fan_state = 0 elif self.get_variable("fan_state") == "ON": _fan_state = 1 else: _fan_state = 2 content = { "fan_state": { "Readings": [[mytime, float(_fan_state)]], "Units": "N/A", "data_type": "double" } } print("{} published fan_state to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'add_notification_event') def add_notification_event(self, topic, headers, message, match): print agent_id + " got\nTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}".format(message=message) #reply message topic = '/agent/ui/'+_topic_Agent_UI+'add_notification_event/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, # headers_mod.DATE: now, headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } #add event_id to self.event_ids _data = json.loads(message[0]) event_id = _data['event_id'] print "{} added notification event_id: {}".format(agent_id, event_id) self.event_ids.append(event_id) _data = "success" message = _data # message = json.dumps(_data) # message = message.encode(encoding='utf_8') self.publish(topic, headers, message) @matching.match_exact('/ui/agent/'+_topic_Agent_UI+'remove_notification_event') def remove_notification_event(self, topic, headers, message, match): print agent_id + " got\nTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}".format(message=message) #reply message topic = '/agent/ui/'+_topic_Agent_UI+'remove_notification_event/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, # headers_mod.DATE: now, headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } #add event_id to self.event_ids _data = json.loads(message[0]) event_id = _data['event_id'] print "{} removed notification event_id: {}".format(agent_id, event_id) self.event_ids.remove(event_id) _data = "success" message = _data # message = json.dumps(_data) # message = message.encode(encoding='utf_8') self.publish(topic, headers, message) def track_event_send_notification(self): for event_id in self.event_ids: print "{} is monitoring event_id: {}\n".format(agent_id, event_id) # collect information about event from notification_event table self.cur.execute("SELECT event_name, notify_device_id, triggered_parameter, comparator," "threshold, notify_channel, notify_address, notify_heartbeat FROM " + db_table_notification_event + " WHERE event_id=%s", (event_id,)) if self.cur.rowcount != 0: row = self.cur.fetchone() event_name = str(row[0]) notify_device_id = str(row[1]) triggered_parameter = str(row[2]) comparator = str(row[3]) threshold = row[4] notify_channel = str(row[5]) notify_address = row[6] notify_heartbeat = row[7] if row[7] is not None else self.notify_heartbeat _event_has_triggered = False print "{} triggered_parameter:{} self.get_variable(triggered_parameter):{} comparator:{} threshold:{}"\ .format(agent_id, triggered_parameter, self.get_variable(triggered_parameter), comparator, threshold) #check whether message is already sent try: if (datetime.datetime.now() - self.time_sent_notifications[ event_id]).seconds > notify_heartbeat: if notify_device_id == agent_id: if comparator == "<": threshold = float(threshold) if self.get_variable(triggered_parameter) < threshold: _event_has_triggered = True elif comparator == ">": threshold = float(threshold) if self.get_variable(triggered_parameter) > threshold: _event_has_triggered = True print "{} triggered_parameter:{} self.get_variable(triggered_parameter):{} comparator:{} threshold:{}"\ .format(agent_id, triggered_parameter, self.get_variable(triggered_parameter), comparator, threshold) print "{} _event_has_triggerered {}".format(agent_id,_event_has_triggered) elif comparator == "<=": threshold = float(threshold) if self.get_variable(triggered_parameter) <= threshold: _event_has_triggered = True elif comparator == ">=": threshold = float(threshold) if self.get_variable(triggered_parameter) >= threshold: _event_has_triggered = True elif comparator == "is": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable(triggered_parameter) is threshold: _event_has_triggered = True elif comparator == "isnot": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable( triggered_parameter) is not threshold: _event_has_triggered = True else: pass if _event_has_triggered: # notify the user if triggered #step2 notify user to notify_channel at notify_address with period notify_heartbeat if notify_channel == 'email': _email_text = '{} notification event triggered_parameter: {}, comparator: {}, ' \ 'threshold: {}\n now the current status of triggered_parameter: {} is {}' \ .format(agent_id, triggered_parameter, comparator, threshold, triggered_parameter, self.get_variable(triggered_parameter)) emailService = EmailService() emailService.sendEmail(email_fromaddr, notify_address, email_username, email_password, self.subject, _email_text, email_mailServer) # self.send_notification_status = True #TODO store time_send_notification for each event self.time_sent_notifications[event_id] = datetime.datetime.now() print "time_sent_notifications is {}".format( self.time_sent_notifications[event_id]) print('{} >> sent notification message for {}'.format(agent_id, event_name)) print( '{} notification event triggered_parameter: {}, comparator: {}, threshold: {}' .format(agent_id, triggered_parameter, comparator, threshold)) else: print "{} >> notification channel: {} is not supported yet".format(agent_id, notify_channel) else: print "{} >> Event is not triggered".format(agent_id) else: "{} >> this event_id {} is not for this device".format(agent_id, event_id) else: "{} >> Email is already sent, waiting for another heartbeat period".format(agent_id) except: #step1 compare triggered_parameter with comparator to threshold #step1.1 classify comparator <,>,<=,>=,is,isnot #case1 comparator < print "{} >> first time trigger notification".format(agent_id) if notify_device_id == agent_id: if comparator == "<": threshold = float(threshold) if self.get_variable(triggered_parameter) < threshold: _event_has_triggered = True elif comparator == ">": threshold = float(threshold) if self.get_variable(triggered_parameter) > threshold: _event_has_triggered = True print "{} triggered_parameter:{} self.get_variable(triggered_parameter):{} comparator:{} threshold:{}"\ .format(agent_id, triggered_parameter, self.get_variable(triggered_parameter), comparator, threshold) print "{} _event_has_triggerered {}".format(agent_id,_event_has_triggered) elif comparator == "<=": threshold = float(threshold) if self.get_variable(triggered_parameter) <= threshold: _event_has_triggered = True elif comparator == ">=": threshold = float(threshold) if self.get_variable(triggered_parameter) >= threshold: _event_has_triggered = True elif comparator == "is": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable(triggered_parameter) is threshold: _event_has_triggered = True elif comparator == "isnot": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable(triggered_parameter) is not threshold: _event_has_triggered = True else: pass print "{} >> _event_has_triggered {}".format(agent_id, _event_has_triggered) if _event_has_triggered: # notify the user if triggered #step2 notify user to notify_channel at notify_address with period notify_heartbeat if notify_channel == 'email': _email_text = '{} notification event triggered_parameter: {}, comparator: {}, ' \ 'threshold: {}\n now the current status of triggered_parameter: {} is {}' \ .format(agent_id, triggered_parameter, comparator, threshold, triggered_parameter, self.get_variable(triggered_parameter)) emailService = EmailService() emailService.sendEmail(email_fromaddr, notify_address, email_username, email_password, self.subject, _email_text, email_mailServer) # self.send_notification_status = True #store time_send_notification for each event self.time_sent_notifications[event_id] = datetime.datetime.now() print "{} >> time_sent_notifications is {}".format(agent_id, self.time_sent_notifications[event_id]) print('{} >> sent notification message for {}'.format(agent_id, event_name)) print('{} notification event triggered_parameter: {}, comparator: {}, threshold: {}' .format(agent_id, triggered_parameter, comparator, threshold)) else: print "{} >> notification channel: {} is not supported yet".format(agent_id, notify_channel) else: print "{} >> Event is not triggered".format(agent_id) else: "{} >> this event_id {} is not for this device".format(agent_id, event_id) else: pass Agent.__name__ = 'Thermostat Agent' return Agent(**kwargs)
def AhpAgent(config_path, **kwargs): config = utils.load_config(config_path) agent_id = config['agentid'] threshold = 14 def get_config(name): try: value = kwargs.pop(name) return value except KeyError: return config[name] # This agent uses the implementation of the ahp agorithm to determine what (if any) # devices in a building are to be curtailed to shed electrical load. The criteria # matrix is expected to be in a table in an excel spredsheet (to simplify data entry by the end user) # The agent will listen for information being sent from the bacnet drivers regarding the desired # devices, and on a periodic basis (to be set in the configuration file) perform calculations to # select the device(s) that are candidates for curtailment. This initial implementation will # be focused on heat pumps in a commercial building. As a result, the agent will also need # to keep track of the times when the compressor was stopped to allow for a period of equilibrium. # # TODO: # * Have the agent open and read the criteria matrix from the excel spreadsheet # * Subscribe to the topics that provide the information from the bacnet drivers # * Store the readings from the heat pumps that are to be watched class Agent(PublishMixin, BaseAgent): """Agent that performs curtailment in a building using the AHP algorithm""" def __init__(self, **kwars): super(Agent, self).__init__(**kwargs) def setup(self): # Load criteria matrix excel_doc = get_config("excel_doc") self.output_log = open("ahp_algorithm.log", "w") self.logger = LoggerWriter(_log, logging.DEBUG) self.logger.write("Testing") (self.criteria_labels, self.criteria_matrix) = extract_criteria_matrix(excel_doc) self.criteria_matrix_sums = calc_column_sums(self.criteria_matrix) # Validate criteria matrix if(not validate_input(self.criteria_matrix, self.criteria_matrix_sums, True, criteria_labels, criteria_labelstring, matrix_rowstring, display_dest=self.logger)): # TODO: log a warning indicatin invalid matrix input pass # TODO: Load device list. Right now, it will come from the config file. # Eventually this will come from the excel spreadsheet self.device_list = get_config('device_list') self.deviceLabels = [row[0] for row in self.device_list] self.deviceDataHandlers = {} for deviceRow in self.device_list: self.deviceDataHandlers[deviceRow[0]] = DeviceData(deviceRow[0], deviceRow[1], logger=self.logger) super(Agent, self).setup() # TODO: Set up subscriptions. Need to subscribe to sigma4/all @match_glob('RTU/PNNL/BOCC/Sigma4/HP*/all') def process_data(self, topic, headers, message, match): data = jsonapi.loads(message[0]) # print >> self.logger, topic, message device_label = topic.split('/')[4] if device_label in self.deviceDataHandlers: # look up device device = self.deviceDataHandlers[device_label] # call device process_data method device.process_data(time.time(), data) @periodic(600) def schedule_algorithm(self): # submit request for schedule to change points headers = { 'AgentID': agent_id, 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': agent_id, 'priority': 'LOW_PREEMPT' } # Build up schedule start = str(datetime.datetime.now()) end = str(datetime.datetime.now() + datetime.timedelta(minutes=1)) # msg = [['PNNL/BOCC/Sigma4/HP1', start, end]] msg = [] for label in self.deviceLabels: msg.append(['PNNL/BOCC/Sigma4/' + label, start, end]) print >> self.logger, "Submitting schedule" self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers, msg) print >> self.logger, "Schedule submitted" # Example from afddagent # 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]]) @match_headers({headers_mod.REQUESTER_ID: agent_id}) @match_exact(topics.ACTUATOR_SCHEDULE_RESULT()) def handle_scheduler_response(self, topic, headers, message, match): msg = jsonapi.loads(message[0]) response_type = headers.get('type', 0) if response_type == 'NEW_SCHEDULE': if msg.get('result', 0) == 'SUCCESS': self.logger.write("Schedule Successful") self.ready = True @match_headers({headers_mod.REQUESTER_ID: agent_id}) @match_start('RTU/actuators/schedule/announce') def do_algorithm(self, topic, headers, message, match): if not (headers[headers_mod.REQUESTER_ID] == agent_id): return if self.ready: self.ready = False print >> self.logger, "====== Calculate Curtailment ======" # The actual ahp algorithm stuff will happen as part of the response to a successful request for a schedule. device_matrix = generateMatrix(self.device_list, self.deviceDataHandlers) print >> self.logger, self.deviceDataHandlers scores = demo_ahp(self.criteria_matrix, device_matrix, self.deviceLabels, self.criteria_labels, criteria_labelstring, matrix_rowstring, display_dest=self.logger) # def demo_ahp(criteria_matrix, device_matrix, devices, criteria_labels="", criteria_labelstring="", matrix_rowstring="", display_dest=sys.stdout): pwr_saved = 0 device_offsets = [] for device in scores: if pwr_saved >= threshold: device_offsets.append(0.0) else: if self.deviceDataHandlers[device[0]].curtailWithThreshold(): device_offsets.append(3.0) else: device_offsets.append(0.0) pwr_saved += 7 header = {'requesterID': agent_id} device_count = 0 for (device, score) in scores: path = 'RTU/actuators/set/PNNL/BOCC/Sigma4/' + device + '/Volttron_Temp_Offset' print >> self.logger, 'Updating %s with %d' % (path, device_offsets[device_count]) self.publish(path, header, str(device_offsets[device_count])) device_count += 1 headers = { 'AgentID': agent_id, 'type': 'CANCEL_SCHEDULE', 'requesterID': agent_id, 'taskID': agent_id, } self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers, []) else: self.ready = False Agent.__name__ = 'AhpAgent' return Agent(**kwargs)
def dragent(config_path, **kwargs): """DR application for time of use pricing""" config = utils.load_config(config_path) agent_id = config['agentid'] rtu_path = dict((key, config[key]) for key in ['campus', 'building', 'unit']) 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.RTU_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.RTU_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() Agent.__name__ = 'dragent' return Agent(**kwargs)
def DemandResponseAgent(config_path, **kwargs): """DR application for time of use pricing""" config = utils.load_config(config_path) agent_id = config['agentid'] rtu_path = dict( (key, config[key]) for key in ['campus', 'building', 'unit']) command_timeout = config.get('command-timeout', settings.default_command_timeout) data_timeout = config.get('data-timeout', settings.default_data_timeout) csp_pre = config.get('csp_pre', settings.csp_pre) csp_cpp = config.get('csp_cpp', settings.csp_cpp) damper_cpp = config.get('damper_cpp', settings.damper_cpp) fan_reduction = config.get('fan_reduction', settings.fan_reduction) pre_time = config.get('pre_time', settings.pre_time) after_time = config.get('after_time', settings.after_time) time_step = config.get('time_step', settings.after_time) Schedule = config.get('Schedule') 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.RTU_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.RTU_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.RTU_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() Agent.__name__ = 'DemandResponseAgent' return Agent(**kwargs)
def WeatherAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: value = kwargs.pop(name) except KeyError: return config.get(name, '') agent_id = get_config('agentid') poll_time = get_config('poll_time') zip_code = get_config("zip") key = get_config('key') state = '' country = '' city = '' region = state if state != "" else country city = city max_requests_per_day = get_config('daily_threshold') max_requests_per_minute = get_config('minute_threshold') headers = {headers_mod.FROM: agent_id} class Agent(PublishMixin, BaseAgent): """Agent for querying WeatherUndergrounds API""" def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) self.valid_data = False def setup(self): super(Agent, self).setup() self._keep_alive = True self.requestCounter = RequestCounter(max_requests_per_day, max_requests_per_minute) # TODO: get this information from configuration file instead baseUrl = "http://api.wunderground.com/api/" + (key if not key == '' else settings.KEY) + "/conditions/q/" self.requestUrl = baseUrl if(zip_code != ""): self.requestUrl += zip_code + ".json" elif self.region != "": self.requestUrl += region + "/" + city + ".json" else: # Error Need to handle this print "No location selected" #Do a one time push when we start up so we don't have to wait for the periodic self.timer(10, self.weather_push) def build_dictionary(self): weather_dict = {} for category in categories.keys(): weather_dict[category] = {} weather_elements = categories[category] for element in weather_elements: weather_dict[category][element] = self.observation[element] return weather_dict def publish_all(self): self.publish_subtopic(self.build_dictionary(), "weather") def publish_subtopic(self, publish_item, topic_prefix): #TODO: Update to use the new topic templates if type(publish_item) is dict: # Publish an "all" property, converting item to json headers[headers_mod.CONTENT_TYPE] = headers_mod.CONTENT_TYPE.JSON self.publish_json(topic_prefix + topic_delim + "all", headers, json.dumps(publish_item)) # Loop over contents, call publish_subtopic on each for topic in publish_item.keys(): self.publish_subtopic(publish_item[topic], topic_prefix + topic_delim + topic) else: # Item is a scalar type, publish it as is headers[headers_mod.CONTENT_TYPE] = headers_mod.CONTENT_TYPE.PLAIN_TEXT self.publish(topic_prefix, headers, str(publish_item)) @periodic(poll_time) def weather_push(self): self.request_data() if self.valid_data: self.publish_all() else: _log.error("Invalid data, not publishing") def request_data(self): if self.requestCounter.request_available(): try: r = requests.get(self.requestUrl) r.raise_for_status() parsed_json = r.json() self.observation = parsed_json['current_observation'] self.observation = convert(self.observation) self.valid_data = True except Exception as e: _log.error(e) self.valid_data = False #self.print_data() else: _log.warning("No requests available") def print_data(self): print "{0:*^40}".format(" ") for key in self.observation.keys(): print "{0:>25}: {1}".format(key, self.observation[key]) print "{0:*^40}".format(" ") Agent.__name__ = 'WeatherAgent' return Agent(**kwargs)
def AFDDAgent(config_path, **kwargs): config = utils.load_config(config_path) agent_id = config['agentid'] rtu_path = dict((key, config[key]) for key in ['campus', 'building', 'unit']) 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.RTU_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 Agent.__name__ = 'AFDDAgent' return Agent(**kwargs)
def AFDDAgent(config_path, **kwargs): publish_address = kwargs['publish_address'] config = utils.load_config(config_path) agent_id = config['agentid'] rtu_path = dict( (key, config[key]) for key in ['campus', 'building', 'unit']) 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.RTU_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 Agent.__name__ = 'AFDDAgent' return Agent(**kwargs)
def __init__(self, config_path, **kwargs): super(ListenerAgent, self).__init__(**kwargs) self.config = utils.load_config(config_path)
def DemandResponseAgent(config_path, **kwargs): """DR application for time of use pricing""" config = utils.load_config(config_path) agent_id = config['agentid'] rtu_path = dict((key, config[key]) for key in ['campus', 'building', 'unit']) schedule = config.get('Schedule') datefmt = '%Y-%m-%d %H:%M:%S' csp_pre = config.get('csp_pre', 67.0) csp_cpp = config.get('csp_cpp', 80.0) damper_cpp = config.get('damper_cpp', 0.0) fan_reduction = config.get('fan_reduction', 0.1) max_precool_hours = config.get('max_precool_hours', 5) cooling_stage_differential = config.get('cooling_stage_differential', 1.0) cpp_end_hour = config.get('cpp_end_hour', 18) cpp_end_minute = config.get('cpp_end_minute', 0) timestep_length = config.get('timestep_length', 900) building_thermal_constant = config.get('building_thermal_constant', 4.0) #point names for controller cooling_stpt = config.get('cooling_stpt') heating_stpt = config.get('heating_stpt') min_damper_stpt = config.get('min_damper_stpt') cooling_stage_diff = config.get('cooling_stage_diff') cooling_fan_sp1 = config.get('cooling_fan_sp1') cooling_fan_sp2 = config.get('cooling_fan_sp2') override_command = config.get('override_command') occupied_status = config.get('occupied_status') space_temp = config.get('space_temp') volttron_flag = config.get('volttron_flag') log_filename = config.get('file_name') debug_flag = False if not debug_flag: _log = logging.getLogger(__name__) logging.basicConfig(level=logging.DEBUG, stream=sys.stderr, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') else: _log = logging.getLogger(__name__) logging.basicConfig(level=logging.NOTSET, stream=sys.stderr, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S', filename=log_filename, filemode='a+') fmt_str = '%(asctime)s %(levelname)-8s %(message)s' formatter = logging.Formatter(fmt_str, datefmt = '%Y-%m-%d %H:%M:%S') console = logging.StreamHandler() console.setLevel(logging.DEBUG) console.setFormatter(formatter) logging.getLogger("").addHandler(console) 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.state = 'STARTUP' self.e_start_msg = None self.lock_handler = 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.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_headers({headers_mod.REQUESTER_ID: 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]) _log.debug('Lock Result: ' + str(headers.get('requesterID', '')) + ' ' + str(msg)) 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() @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): """lock result""" point = match.group(1) msg = jsonapi.loads(message[0]) point = match.group(1) _log.debug('Lock Error Results: '+str(point) + ' '+ str(msg)) if self.error_handler is not None: _log.debug('Running lock error handler') self.error_handler() @matching.match_headers({headers_mod.REQUESTER_ID: agent_id}) @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]) point = match.group(1) if point != 'PlatformHeartBeat': _log.debug('Actuator Results: ' + str(point) +' ' + str(msg)) if self.actuator_handler is not None: _log.debug('Running Actuator Handler') self.actuator_handler(point, jsonapi.loads(message[0])) @matching.match_exact(topics.RTU_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'): _log.debug('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': _log.debug('Finished Startup') self.state = 'IDLE' @matching.match_exact(topics.OPENADR_EVENT()) def _on_dr_event(self, topic, headers, message, match): if self.state == 'STARTUP': _log.debug('DR event ignored because of startup.') return """handle openADR events""" msg = jsonapi.loads(message[0]) _log.debug('EVENT Received: ' + str(msg)) e_id = msg['id'] e_status = msg['status'] e_start = msg['start_at'] #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) #e_end = datetime.datetime.strptime(e_end, datefmt) current_datetime = datetime.datetime.now() #For UTC offset #offset= datetime.datetime.utcnow() - datetime.datetime.now() #e_end = e_end - offset #e_start = e_start - offset if current_datetime > e_end: _log.debug('Too Late Event is Over') return if e_status == 'cancelled': if e_start in self.all_scheduled_events: _log.debug('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.keys(): if e_start.date() == item.date(): if e_start.time() != item.time(): _log.debug( '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(): _log.debug("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 self.e_start = e_start self.e_end = e_end event_start = e_start - datetime.timedelta(hours = max_precool_hours) event = sched.Event(self.pre_cool_get_lock, args=[self.e_start, self.e_end]) self.schedule(event_start, event) self.all_scheduled_events[e_start] = event def pre_cool_get_lock(self, e_start, e_end): if self.state == 'OVERRIDE': _log.debug("Override today") return if self.pre_cool_idle == False: return now = datetime.datetime.now() day=now.weekday() if not schedule[day]: _log.debug("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.all_scheduled_events = {} self.state = 'PRECOOL' e_start_unix = time.mktime(e_start.timetuple()) e_end_unix = time.mktime(e_end.timetuple()) if self.pre_cool_idle == True: event_start = now + datetime.timedelta(minutes=15) event = sched.Event(self.pre_cool_get_lock, args=[self.e_start, self.e_end]) self.schedule(event_start, event) self.all_scheduled_events[e_start] = event self.pre_cool_idle = True self.schedule_builder(e_start_unix, e_end_unix, current_spacetemp=self.current_spacetemp, pre_csp=csp_pre, building_thermal_constant=building_thermal_constant, normal_coolingstpt=self.normal_coolingstpt, timestep_length=timestep_length, dr_csp=csp_cpp) else: event_start = now + datetime.timedelta(minutes=15) event = sched.Event(self.pre_cool_get_lock, args=[self.e_start, self.e_end]) self.schedule(event_start, event) self.all_scheduled_events[e_start] = event self.pre_cool_idle = True def run_schedule_builder(): self.schedule_builder(e_start_unix, e_end_unix, current_spacetemp=self.current_spacetemp, pre_csp=csp_pre, building_thermal_constant=building_thermal_constant, normal_coolingstpt=self.normal_coolingstpt, 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 retry_lock(): def retry_lock_event(): headers = { headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, 'requesterID': agent_id} self.publish(topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path), headers) retry_time = datetime.datetime.now() + datetime.timedelta(seconds=5) if retry_time > e_end: self.state = 'IDLE' self.error_handler = None return event = sched.Event(retry_lock_event) self.schedule(retry_time, event) self.error_handler = retry_lock 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.lock_handler=None self.lock_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)) self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path), self.headers, str(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.lock_handler = None self.lock_handler = backup_run def start_restore_event(self, csp, hsp): self.state = 'RESTORE' _log.debug('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.lock_handler=None self.lock_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.currently_running_dr_event_handlers = [] def backup_run(): self.cancel_event() self.lock_handler=None self.lock_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: _log.debug( "Did not get back expected value for: " + str(point)) if not expected_values: self.actuator_handler = None self.lock_handler=None self.error_handler = None self.state = 'IDLE' if not cancel_type == 'OVERRIDE' else 'OVERRIDE' headers = { headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, 'requesterID': agent_id} self.publish(topics.ACTUATOR_LOCK_RELEASE(**rtu_path), headers) if cancel_type != 'UPDATING': self.actuator_handler = result_handler else: self.actuator_handler = None self.lock_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, current_spacetemp, pre_csp, building_thermal_constant, normal_coolingstpt, timestep_length, dr_csp): """schedule all events for a DR event.""" current_time = time.time() if current_time > end_time: return _log.debug('Scheduling all DR actions') pre_hsp = pre_csp - 5.0 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 pre_cool_step = 0 if (max_cooling_window > 0): _log.debug('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 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*timestep_length else: pre_cool_step += timestep_length event_time = start_time - pre_cool_step csp = pre_csp + ((step_index-1) * cooling_step_delta) _log.debug('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: _log.debug('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 _log.debug('Schedule DR Event: ' + str(datetime.datetime.fromtimestamp(start_time)) +' CSP: ' + str(dr_csp)) event = sched.Event(self.start_dr_event) self.schedule(start_time, event) self.currently_running_dr_event_handlers.append(event) _log.debug('Schedule Restore Event: '+ str(datetime.datetime.fromtimestamp(end_time)) + ' CSP: ' + str(dr_csp-restore_step_delta)) event = sched.Event(self.start_restore_event, args = [dr_csp-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 * timestep_length) csp = dr_csp - ((step_index + 1) * restore_step_delta) _log.debug('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 * timestep_length) _log.debug('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) Agent.__name__ = 'DemandResponseAgent' return Agent(**kwargs)
def __init__(self, config_path, **kwargs): super(IEBSubscriber, self).__init__(**kwargs) self.config = utils.load_config(config_path)
def DeviceDiscoveryAgent(config_path, **kwargs): config = utils.load_config( config_path ) # load the config_path from devicediscoveryagent.launch.json def get_config(name): try: value = kwargs.pop( name) # from the **kwargs when call this function except KeyError: return config.get(name, '') #1. @params agent agent_id = get_config('agent_id') device_scan_time = get_config('device_scan_time') device_scan_time_multiplier = get_config('device_scan_time_multiplier') #device_scan_time_multiplier=1 headers = {headers_mod.FROM: agent_id} publish_address = 'ipc:///tmp/volttron-lite-agent-publish' subscribe_address = 'ipc:///tmp/volttron-lite-agent-subscribe' topic_delim = '/' # topic delimiter #3. @params agent & DB interfaces #@params DB interfaces db_database = settings.DATABASES['default']['NAME'] db_host = settings.DATABASES['default']['HOST'] db_port = settings.DATABASES['default']['PORT'] db_user = settings.DATABASES['default']['USER'] db_password = settings.DATABASES['default']['PASSWORD'] db_table_device_info = settings.DATABASES['default']['TABLE_device_info'] #db_table_dashboard_current_status = settings.DATABASES['default']['TABLE_dashboard_current_status'] db_table_supported_devices = settings.DATABASES['default'][ 'TABLE_supported_devices'] #4. @params dummy devicediscovery agent setting dummy_device_discovery = settings.DUMMY_SETTINGS['dummy_discovery'] num_of_dummy_hvac = settings.DUMMY_SETTINGS['number_of_hvac'] num_of_dummy_lighting = settings.DUMMY_SETTINGS['number_of_lighting'] num_of_dummy_plugload = settings.DUMMY_SETTINGS['number_of_plugload'] #5. @params devicediscovery agent setting device_monitor_time = settings.DEVICES['device_monitor_time'] findWiFi = settings.FIND_DEVICE_SETTINGS['findWiFi'] findWiFiHue = settings.FIND_DEVICE_SETTINGS['findWiFiHue'] findWiFiWeMo = settings.FIND_DEVICE_SETTINGS['findWiFiWeMo'] #@paths PROJECT_DIR = settings.PROJECT_DIR Loaded_Agents_DIR = settings.Loaded_Agents_DIR Autostart_Agents_DIR = settings.Autostart_Agents_DIR Applications_Launch_DIR = settings.Applications_Launch_DIR Agents_Launch_DIR = settings.Agents_Launch_DIR class Agent(PublishMixin, BaseAgent): def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) # Connect to database self.con = psycopg2.connect(host=db_host, port=db_port, database=db_database, user=db_user, password=db_password) self.cur = self.con.cursor( ) # open a cursor to perform database operations sys.path.append(PROJECT_DIR) self.device_scan_time = device_scan_time self.device_discovery_start_time = datetime.datetime.now() self.scan_for_devices = True self.findWiFi = findWiFi self.findWiFiHue = findWiFiHue self.findWiFiWeMo = findWiFiWeMo self.dummy_device_discovery = dummy_device_discovery if self.dummy_device_discovery: self.dummy_device_types = { 'hvac': { 'thermostat': 'thermostat' }, 'lighting': { 'Philips': 'lighting', 'WeMo_lighting': 'lighting' }, 'plugload': { 'WeMo_plugload': 'plugload' } } self.num_of_dummy_devices = dict() self.num_of_dummy_devices['hvac'] = num_of_dummy_hvac self.num_of_dummy_devices['lighting'] = num_of_dummy_lighting self.num_of_dummy_devices['plugload'] = num_of_dummy_plugload self.new_discovery = True self.no_new_discovery_count = 0 try: # Find total number of devices in the dashboard_device_info table self.cur.execute("SELECT * FROM " + db_table_device_info) self.device_num = self.cur.rowcount # count no. of devices discovered by Device Discovery Agent print "{} >> there are existing {} device(s) in database".format( agent_id, self.device_num) #if self.device_num != 0: # change network status of devices to OFF (OFFLINE) # rows = self.cur.fetchall() # for row in rows: # self.cur.execute("UPDATE "+db_table_device_info+" SET device_status=%s", ("OFF",)) # self.con.commit() except: self.device_num = 0 def setup(self): super(Agent, self).setup() self.valid_data = False '''Discovery Processes''' while True: if self.scan_for_devices: self.deviceScannerBehavior() if not self.new_discovery: self.no_new_discovery_count += 1 else: self.no_new_discovery_count = 0 if self.no_new_discovery_count >= 10: self.device_scan_time *= device_scan_time_multiplier time.sleep(self.device_scan_time) else: pass #deviceScannerBehavior (TickerBehavior) def deviceScannerBehavior(self): self.new_discovery = False print "Start Discovery Processes--------------------------------------------------" print "{} >> device next scan time in {} sec".format( agent_id, str(self.device_scan_time)) print "{} >> start_time {}".format( agent_id, str(self.device_discovery_start_time)) self.device_discovery_time_now = datetime.datetime.now() print "{} >> current time {}".format( agent_id, str(self.device_discovery_time_now)) print "{} >> is trying to discover all available devices\n".format( agent_id) # Check if dummy or real device discovery: if self.dummy_device_discovery: for device_type in self.dummy_device_types.keys(): if self.num_of_dummy_devices[device_type] > 0: num_of_devices_to_discover_now = random.randint( 1, self.num_of_dummy_devices[device_type]) type_no_of_device_to_discover_now = random.randint( 0, len(self.dummy_device_types[device_type].keys()) - 1) type_of_device_to_discover_now = self.dummy_device_types[ device_type].keys( )[type_no_of_device_to_discover_now] device_number = num_of_devices_to_discover_now while device_number > 0: device_number -= self.findDevicesbytype( "Dummy", self.dummy_device_types[device_type] [type_of_device_to_discover_now], type_of_device_to_discover_now) self.num_of_dummy_devices[ device_type] -= num_of_devices_to_discover_now else: # Finding devices by type: if self.findWiFi: self.findDevicesbytype("WiFi", "thermostat", "thermostat") if self.findWiFiWeMo: self.findDevicesbytype("WiFi", "plugload", "WeMo") self.findDevicesbytype("WiFi", "lighting", "WeMo") if self.findWiFiHue: self.findDevicesbytype("WiFi", "lighting", "Philips") print "Stop Discovery Processes---------------------------------------------------" def findDevicesbytype(self, com_type, controller_type, discovery_type): #****************************************************************************************************** self.cur.execute( "SELECT device_type FROM " + db_table_device_info + " WHERE device_type=%s", (controller_type, )) num_Devices = self.cur.rowcount num_new_Devices = 0 print "{} >> is finding available {} {} devices ...".format( agent_id, com_type, discovery_type) discovery_module = importlib.import_module("discoverAPI." + com_type) discovery_returns_ip = True discovered_address = discovery_module.discover(discovery_type) print discovered_address for address in discovered_address: if discovery_returns_ip: ip_address = address try: macaddress = discovery_module.getMACaddress( discovery_type, ip_address) if macaddress is not None: _valid_macaddress = True else: _valid_macaddress = False except: _valid_macaddress = False else: ip_address = None macaddress = address _valid_macaddress = True if _valid_macaddress: if self.checkMACinDB(self.con, macaddress): newdeviceflag = False self.cur.execute( "SELECT device_id from " + db_table_device_info + " where mac_address=%s", (macaddress, )) deviceID = self.cur.fetchone()[0] agent_launch_file = deviceID + ".launch.json" self.cur.execute( "SELECT bemoss from " + db_table_device_info + " where device_id=%s", (deviceID, )) bemoss_status = self.cur.fetchone()[0] if self.device_agent_still_running(agent_launch_file): print "{} >> {} for device with MAC address {} is still running"\ .format(agent_id, agent_launch_file, macaddress) if not bemoss_status: print '{} >> Device with MAC address {} found to be Non-BEMOSS, Stopping agent {}'\ .format(agent_id, macaddress, agent_launch_file) os.system("bin/volttron-ctrl stop-agent " + agent_launch_file) # else: # self.cur.execute("UPDATE "+db_table_device_info+" SET device_status=%s where " # "id=%s", ("ON", deviceID)) # self.con.commit() else: print "{} >> {} for device with MAC address {} is not running"\ .format(agent_id, agent_launch_file, macaddress) #restart agent if in BEMOSS Core if bemoss_status: self.cur.execute( "SELECT device_type from " + db_table_device_info + " where device_id=%s", (deviceID, )) stopped_agent_device_type = self.cur.fetchone( )[0] self.cur.execute( "SELECT zone_id from " + stopped_agent_device_type + " where " + stopped_agent_device_type + "_id=%s", (deviceID, )) stopped_agent_zone_id = self.cur.fetchone()[0] if stopped_agent_zone_id == 999: self.launch_agent(Agents_Launch_DIR, agent_launch_file) print "{} >> {} has been restarted"\ .format(agent_id, agent_launch_file) else: print "{} >> {} is running on another node, ignoring restart"\ .format(agent_id, agent_launch_file) # self.cur.execute("UPDATE "+db_table_device_info+" SET device_status=%s where " # "id=%s", ("ON", deviceID)) # self.con.commit() else: print '{} >> Device with MAC address {} found to be Non-BEMOSS'\ .format(agent_id, macaddress) #case2: new device has been discovered else: print '{} >> new device found with macaddress {}'\ .format(agent_id, macaddress) newdeviceflag = True else: print "Invalid MAC address at: {}"\ .format(address) newdeviceflag = False if newdeviceflag: model_info_received = False try: modelinfo = discovery_module.getmodelvendor( discovery_type, address) if modelinfo != None: deviceModel = modelinfo['model'] deviceVendor = modelinfo['vendor'] print 'Model information found: ' print { 'model': deviceModel, 'vendor': deviceVendor } model_info_received = True except: pass if model_info_received: try: self.cur.execute( "SELECT device_type from " + db_table_supported_devices + " where vendor_name=%s and device_model=%s", (deviceVendor, deviceModel)) controller_type_from_model = self.cur.fetchone()[0] supported = True except: supported = False if (supported): if (controller_type == 'All') | (controller_type_from_model == controller_type): self.device_num += 1 #deviceType = com_type + controller_type deviceType = controller_type_from_model self.cur.execute( "SELECT device_model_id from " + db_table_supported_devices + " where vendor_name=%s and device_model=%s", (deviceVendor, deviceModel)) device_type_id = self.cur.fetchone()[0] self.cur.execute( "SELECT identifiable from " + db_table_supported_devices + " where vendor_name=%s and device_model=%s", (deviceVendor, deviceModel)) identifiable = self.cur.fetchone()[0] if (ip_address != None): if ('/' in ip_address): IPparsed = urlparse(ip_address) print IPparsed deviceIP = str(IPparsed.netloc) if str(IPparsed.scheme) != '': address = str( IPparsed.scheme) + "://" + str( IPparsed.netloc) else: address = deviceIP if ':' in deviceIP: deviceIP = deviceIP.split(':')[0] else: if ':' in ip_address: deviceIP = ip_address.split(':')[0] else: deviceIP = ip_address address = ip_address else: deviceIP = ip_address self.cur.execute( "SELECT api_name from " + db_table_supported_devices + " where vendor_name=%s and device_model=%s", (deviceVendor, deviceModel)) deviceAPI = self.cur.fetchone()[0] deviceID = device_type_id + macaddress # self.cur.execute("INSERT INTO "+db_table_device_info+" VALUES(%s,%s,%s,%s,%s,%s,%s,999,%s,'ON')", # (deviceID, deviceID, deviceType+str(self.device_num), deviceType, device_type_id, # deviceVendor, deviceModel, macaddress)) self.cur.execute( "INSERT INTO " + db_table_device_info + " VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", (deviceID, deviceType, deviceVendor, deviceModel, device_type_id, macaddress, None, None, identifiable, com_type, str(datetime.datetime.now()), macaddress, True)) self.con.commit() self.cur.execute( "INSERT INTO " + deviceType + " (" + deviceType + "_id, ip_address,nickname,zone_id,network_status) VALUES(%s,%s,%s,%s,%s)", (deviceID, deviceIP, deviceType + str(self.device_num), 999, 'ONLINE')) self.con.commit() agent_name = deviceType.replace('_', '') num_new_Devices += 1 #After found new device-> Assign a suitable agent to each device to communicate, control, and collect data print( 'Now DeviceDiscoverAgent is assigning a suitable agent to the discovered Weme device to communicate, control, and collect data' ) self.write_launch_file( agent_name + "agent", deviceID, device_monitor_time, deviceModel, deviceVendor, deviceType, deviceAPI, address, db_host, db_port, db_database, db_user, db_password) self.launch_agent(Agents_Launch_DIR, deviceID + ".launch.json") self.new_discovery = True else: pass else: print "Device currently not supported by BEMOSS" else: print 'Unable to get device model information. Ignoring device...' #Print how many WiFi devices this DeviceDiscoverAgent found! print("{} >> Found {} new {} {} devices".format( agent_id, num_new_Devices, com_type, controller_type)) print("{} >> There are existing {} {} {} devices\n".format( agent_id, num_Devices, com_type, controller_type)) print " " return num_new_Devices def launch_agent(self, dir, launch_file): _launch_file = os.path.join(dir, launch_file) os.system("bin/volttron-ctrl stop-agent " + launch_file) os.system("bin/volttron-ctrl load-agent " + _launch_file) os.system("bin/volttron-ctrl start-agent " + os.path.basename(_launch_file)) os.system("bin/volttron-ctrl list-agent") print "{} >> has successfully launched {} located in {}".format( agent_id, launch_file, dir) def checkMACinDB(self, conn, macaddr): cur = conn.cursor() cur.execute( "SELECT device_id FROM " + db_table_device_info + " WHERE mac_address=%(id)s", {'id': macaddr}) if cur.rowcount != 0: mac_already_in_db = True else: mac_already_in_db = False return mac_already_in_db def device_agent_still_running(self, agent_launch_filename): os.system("bin/volttron-ctrl list-agent > running_agents.txt") infile = open('running_agents.txt', 'r') agent_still_running = False reg_search_term = agent_launch_filename for line in infile: #print(line, end='') #write to a next file name outfile match = re.search(reg_search_term, line) and re.search( 'running', line) if match: # The agent for this device is running agent_still_running = True else: pass infile.close() return agent_still_running def write_launch_file(self, executable, deviceID, device_monitor_time, deviceModel, deviceVendor, deviceType, api, address, db_host, db_port, db_database, db_user, db_password): data = { "agent": { "exec": executable + "-0.1-py2.7.egg --config \"%c\" --sub \"%s\" --pub \"%p\"" }, "agent_id": deviceID, "device_monitor_time": device_monitor_time, "model": deviceModel, "vendor": deviceVendor, "type": deviceType, "api": api, "address": address, "db_host": db_host, "db_port": db_port, "db_database": db_database, "db_user": db_user, "db_password": db_password, "building_name": "bemoss", "zone_id": 999 } __launch_file = os.path.join(Agents_Launch_DIR + deviceID + ".launch.json") with open(__launch_file, 'w') as outfile: json.dump(data, outfile, indent=4, sort_keys=True) Agent.__name__ = 'DeviceDiscoveryAgent' return Agent(**kwargs)
def scheduleragent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: kwargs.pop(name) except KeyError: return config.get(name, '') # 1. define name of this application app_name = "thermostat_scheduler" # 2. @params agent agent_id = get_config('agent_id') clock_time = 1 # schedule is updated every second publish_address = 'ipc:///tmp/volttron-lite-agent-publish' subscribe_address = 'ipc:///tmp/volttron-lite-agent-subscribe' debug_agent = False # 3. @params DB interfaces (settings file in ~/workspace/bemoss_os/) # 3.1 PostgreSQL (meta-data database) connection information db_host = settings.DATABASES['default']['HOST'] db_port = settings.DATABASES['default']['PORT'] db_database = settings.DATABASES['default']['NAME'] db_user = settings.DATABASES['default']['USER'] db_password = settings.DATABASES['default']['PASSWORD'] db_table_application_registered = settings.DATABASES['default'][ 'TABLE_application_registered'] db_table_application_running = settings.DATABASES['default'][ 'TABLE_application_running'] # 3.2 sMAP (time-series data database) connection information # construct topic_app_sMAP based on data obtained from the launcher _topic_Agent_sMAP = 'datalogger/log/' + app_name + agent_id + '/' # 4. set exchanged topics between this app and other entities # 4.1 exchanged topic app and agent # construct topic_app_agent based on data obtained from the launcher topic_app_agent = '/ui/agent/' + 'building1/999/thermostat/' + agent_id + '/' + 'update' topic_agent_app = '/agent/ui/' + 'building1/999/thermostat/' + agent_id + '/' + 'update/response' # 4.2 exchanged topic ui and app # construct topic_ui_app based on data obtained from the launcher topic_ui_app = '/ui/app/' + app_name + '/' + agent_id + '/' + 'update' topic_app_ui = '/app/ui/' + app_name + '/' + agent_id + '/' + 'update/response' class Agent(PublishMixin, BaseAgent): # 1. agent initialization def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) # 1. initialize all agent variables self.variables = kwargs self.timeModeTemp = kwargs self.timeModeTemp.clear() self.flag_time_to_change_status = False self.current_use_schedule = None self.schedule_first_run = False self.old_day = self.find_day() self.active_scheduler_mode = list() self.time_next_schedule_sec = int(24 * 3600) self.weekday_list = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday' ] self.weekend_list = ['saturday', 'sunday'] self.current_schedule_object = None # 2. connect to the database try: self.con = psycopg2.connect(host=db_host, port=db_port, database=db_database, user=db_user, password=db_password) self.cur = self.con.cursor( ) # open a cursor to perfomm database operations print( "{} for Agent: {} >> connects to the database name {} successfully" .format(app_name, agent_id, db_database)) except: print( "ERROR: {} for Agent: {} >> fails to connect to the database name {}" .format(app_name, agent_id, db_database)) try: app_agent_id = app_name + '_' + agent_id self.cur.execute( "SELECT app_setting FROM application_running WHERE app_agent_id=%s", (app_agent_id, )) if self.cur.rowcount != 0: _launch_file = str(self.cur.fetchone()[0]) with open(_launch_file) as json_data: _new_schedule_object = json.load(json_data) # pprint(_new_schedule_object) # print '_launch_file' + _launch_file # set self.current_schedule_object to be the new schedule self.current_schedule_object = _new_schedule_object[ 'thermostat'] # 3. get currently active schedule self.active_scheduler_mode = list() print '{} for Agent: {} >> new active schedule are as follows:'.format( app_name, agent_id) for each1 in self.current_schedule_object[agent_id]: if each1 == 'active': for each2 in self.current_schedule_object[ agent_id][each1]: self.active_scheduler_mode.append(each2) for index in range(len(self.active_scheduler_mode)): print "- " + self.active_scheduler_mode[index] # 4. RESTART Scheduler Agent **************** IMPORTANT self.set_query_mode_all_day() self.schedule_first_run = True #******************************************* IMPORTANT print( "{} for Agent: {} >> DONE getting data from applications_running" .format(app_name, agent_id)) else: print("{} for Agent: {} >> has no previous setting before". format(app_name, agent_id)) except: print( "{} for Agent: {} >> error getting data from applications_running" .format(app_name, agent_id)) # 2. agent setup method def setup(self): super(Agent, self).setup() print "{} for Agent: {} >> has been launched successfully".format( app_name, agent_id) print "{} for Agent: {} >> is waiting to be configured by the setting sent from the UI"\ .format(app_name, agent_id) # 3. clockBehavior (CyclicBehavior) @periodic(clock_time) def clockBehavior(self): #1. check current time self.htime = datetime.datetime.now().hour self.mtime = datetime.datetime.now().minute self.stime = datetime.datetime.now().second self.now_decimal = int(self.htime) * 60 + int(self.mtime) self.time_now_sec = int( self.htime * 3600) + int(self.mtime) * 60 + int(self.stime) # 2. update self.schedule_first_run to True if day has change self.new_day = self.find_day() if self.new_day != self.old_day: if self.current_schedule_object != None: #RESTART Scheduler Agent ******************* IMPORTANT self.set_query_mode_all_day() self.schedule_first_run = True self.old_day = self.new_day #******************************************* IMPORTANT print '{} for Agent: {} >> today: {} is a new day (yesterday: {}), load new schedule for today'\ .format(app_name, agent_id, self.new_day, self.old_day) else: print '{} for Agent: {} >> today: {} is a new day (yesterday: {}), but no schedule has been set yet'\ .format(app_name, agent_id, self.new_day, self.old_day) else: # same day no reset is required pass # 3. self.schedule_first_run to True is triggered by 1. setting from UI or 2. Day change if self.schedule_first_run is True: self.schedule_action() self.schedule_first_run = False else: pass # 4. if next schedule comes up, run self.schedule_action() if self.time_now_sec >= self.time_next_schedule_sec: if debug_agent: print "{} for Agent: >> {} now _time_now_sec= {}, self.time_next_schedule_sec ={}"\ .format(app_name, agent_id, self.time_now_sec, self.time_next_schedule_sec) print "{} for Agent: {} >> is now woken up".format( app_name, agent_id) self.schedule_action() # 4. updateScheduleBehavior (GenericBehavior) @matching.match_exact(topic_ui_app) def updateScheduleBehavior(self, topic, headers, message, match): # print agent_id + " got\nTopic: {topic}".format(topic=topic) # print "Headers: {headers}".format(headers=headers) # print "Message: {message}\n".format(message=message) # take action to update new schedule to an agent _time_receive_update_from_ui = datetime.datetime.now() print "{} for Agent: {} >> got new update from UI at {}".format( app_name, agent_id, _time_receive_update_from_ui) try: # 1. get path to the new launch file from the message sent by UI _data = json.dumps(message[0]) _data = json.loads(message[0]) _launch_file = _data.get('path') print '{} for Agent: {} >> new schedule path is at: {}'.format( app_name, agent_id, _launch_file) app_agent_id = app_name + "_" + agent_id print "app_agent_id = {}".format(app_agent_id) self.cur.execute( "UPDATE application_running SET app_setting=%s WHERE app_agent_id=%s", (_launch_file, app_agent_id)) self.con.commit() print '{} for Agent: {} >> DONE update applications_running table with path {}'\ .format(app_name, agent_id, _launch_file) # 2. load new schedule from the new launch file with open(_launch_file) as json_data: _new_schedule_object = json.load(json_data) # pprint(_new_schedule_object) # set self.current_schedule_object to be the new schedule self.current_schedule_object = _new_schedule_object[ 'thermostat'] # 3. get currently active schedule self.active_scheduler_mode = list() print '{} for Agent: {} >> new active schedule are as follows:'.format( app_name, agent_id) for each1 in self.current_schedule_object[agent_id]: if each1 == 'active': for each2 in self.current_schedule_object[agent_id][ each1]: self.active_scheduler_mode.append(each2) for index in range(len(self.active_scheduler_mode)): print "- " + self.active_scheduler_mode[index] # 4. RESTART Scheduler Agent **************** IMPORTANT self.set_query_mode_all_day() self.schedule_first_run = True #******************************************* IMPORTANT # reply message from app to ui _headers = { 'AppName': app_name, 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } _message = 'success' self.publish(topic_app_ui, _headers, _message) print '{} for Agent: {} >> DONE update new active schedule received from UI'\ .format(app_name, agent_id) except: print "{} for Agent: {} >> ERROR update new schedule received from UI at {}"\ .format(app_name, agent_id, _time_receive_update_from_ui) print "{} for Agent: {} >> possible ERRORS are as follows: "\ .format(app_name, agent_id, _time_receive_update_from_ui) print "{} for Agent: {} >> ERROR 1 : path to update schedule sent by UI is incorrect "\ .format(app_name, agent_id, _time_receive_update_from_ui) print "{} for Agent: {} >> ERROR 2 : content inside schedule setting file (JSON) sent by UI" \ " is incorrect ".format(app_name, agent_id, _time_receive_update_from_ui) # Helper methods -------------------------------------- def set_query_mode_all_day(self): if 'holiday' in self.active_scheduler_mode: # 1. find whether today is a holiday by query db 'public.holiday' _date_today = str(datetime.datetime.now().date()) self.cur.execute( "SELECT description FROM holiday WHERE date=%s", (_date_today, )) if self.cur.rowcount != 0: self.holiday_description = self.cur.fetchone()[0] print '---------------------------------' print "{} for Agent: {} >> Hoorey! today is a holiday: {}"\ .format(app_name, agent_id, self.holiday_description) self.todayHoliday = True else: print '---------------------------------' print "{} for Agent: {} >> Today is not a holiday".format( app_name, agent_id) self.todayHoliday = False # 2. select schedule according to the day if self.todayHoliday is True: self.today = self.find_day() print "{} for Agent: {} >> mode: holiday".format( app_name, agent_id) self.query_schedule_mode = 'holiday' self.get_schedule_time_mode_temperature() else: self.set_query_mode_point_everyday_weekdayweekend() self.get_schedule_time_mode_temperature() else: # no holiday mode self.todayHoliday = False self.set_query_mode_point_everyday_weekdayweekend() self.get_schedule_time_mode_temperature() def set_query_mode_point_everyday_weekdayweekend(self): # find the day of today then print the setting of the Scheduler Agent self.today = self.find_day() if 'everyday' in self.active_scheduler_mode: print '---------------------------------' print "{} for Agent: {} >> mode: everyday".format( app_name, agent_id) self.query_schedule_mode = 'everyday' self.query_schedule_point = self.today elif 'weekdayweekend' in self.active_scheduler_mode: print '---------------------------------' print '{} for Agent: {} >> mode: weekdayweekend'.format( app_name, agent_id) self.query_schedule_mode = 'weekdayweekend' # find whether today is a weekday or weekend if self.today in self.weekday_list: self.query_schedule_point = 'weekday' elif self.today in self.weekend_list: self.query_schedule_point = 'weekend' else: #TODO change this default setting self.query_schedule_point = 'weekday' def get_schedule_time_mode_temperature(self): # 1. get time-related schedule including: 1.mode and 2. temperature setpoint self.newTimeScheduleChange = list() if self.todayHoliday is True: for eachmode in self.current_schedule_object[agent_id][ 'schedulers'][self.query_schedule_mode]: for eachtime in self.current_schedule_object[agent_id][ 'schedulers'][self.query_schedule_mode][eachmode]: #time that scheduler need to change thermostat setting self.newTimeScheduleChange.append(int(eachtime['at'])) #dictionary relate time with mode and temperature setpoint self.timeModeTemp[str( eachtime['at'])] = str(eachmode + " " + eachtime['setpoint']) else: for eachmode in self.current_schedule_object[agent_id][ 'schedulers'][self.query_schedule_mode][ self.query_schedule_point]: for eachtime in self.current_schedule_object[agent_id][ 'schedulers'][self.query_schedule_mode][ self.query_schedule_point][eachmode]: #time that scheduler need to change thermostat setting self.newTimeScheduleChange.append(int(eachtime['at'])) #dictionary relate time with mode and temperature set point self.timeModeTemp[str( eachtime['at'])] = str(eachmode + " " + eachtime['setpoint']) # 2. sort time to change the schedule in ascending order self.newTimeScheduleChange.sort() # 3. get the schedule: reordered time and corresponding mode and temperature setting if self.todayHoliday is True: print "{} for Agent: {} >> Today is {} and here is the schedule for holiday:"\ .format(app_name, agent_id, self.holiday_description) else: if self.query_schedule_mode is 'everyday': print "{} for Agent: {} >> Today is {} and here is the schedule for today:"\ .format(app_name, agent_id, self.today) elif self.query_schedule_point is 'weekday': print "{} for Agent: {} >> Today is {} and here is the schedule for the weekday:"\ .format(app_name, agent_id, self.today) elif self.query_schedule_point is 'weekend': print "{} for Agent: {} >> Today is {} and here is the schedule for the weekend:"\ .format(app_name, agent_id, self.today) for index in range(len(self.newTimeScheduleChange)): # _time = float(self.newTimeScheduleChange[index])/60 _time = self.newTimeScheduleChange[index] _hour = int(_time / 60) _minute = int(_time - (_hour * 60)) _modetemp = self.timeModeTemp[str( self.newTimeScheduleChange[index])].split() _mode = _modetemp[0] _temp = _modetemp[1] print "{} for Agent: {} >> At {} hr {} min mode: {}, setpoint: {} F"\ .format(app_name, agent_id, str(_hour), str(_minute), str(_mode).upper(), str(_temp)) print '' def schedule_action(self): # *before call this method make sure that self.timeModeTemp and self.newTimeScheduleChange has been updated! print '{} for Agent: {} >> current time {} hr {} min {} sec (decimal = {})'\ .format(app_name, agent_id, self.htime, self.mtime, self.stime, str(self.now_decimal)) # 1. check current and past schedule _pastSchedule = list() # list to save times of previous schedules for index in range(len(self.newTimeScheduleChange)): if self.now_decimal >= self.newTimeScheduleChange[index]: _pastSchedule.append(self.newTimeScheduleChange[index]) # if length of _pastSchedule is not 0, there is a previous schedule(s) if len(_pastSchedule) != 0: # previous schedule exists, use previous schedule to update thermostat to latest setting _latest_schedule = max(_pastSchedule) self.current_use_schedule = _latest_schedule _time_current_schedule = int(self.current_use_schedule) self.hour_current_schedule = int(_time_current_schedule / 60) self.minute_current_schedule = int(_time_current_schedule - self.hour_current_schedule * 60) self.modetemp_current = self.timeModeTemp[str( self.current_use_schedule)].split() self.modeToChange_current = self.modetemp_current[0] self.tempToChange_current = self.modetemp_current[1] self.flag_time_to_change_status = True print "{} for Agent: {} >> Here is the new schedule: at {} hr {} min mode = {} setpoint = {} F".\ format(app_name, agent_id, self.hour_current_schedule, self.minute_current_schedule, str(self.modeToChange_current).upper(), self.tempToChange_current) else: # previous schedule does not exist # scheduler is trying to get setting from the old setting file if self.current_use_schedule is not None: # previous schedule from old setting file exists _time_current_schedule = int(self.current_use_schedule) self.hour_current_schedule = int(_time_current_schedule / 60) self.minute_current_schedule = int( _time_current_schedule - self.hour_current_schedule * 60) self.modetemp_current = self.timeModeTemp[str( self.current_use_schedule)].split() self.modeToChange_current = self.modetemp_current[0] self.tempToChange_current = self.modetemp_current[1] self.flag_time_to_change_status = True print "{} for Agent: {} >> previous schedule is at {} hr {} min mode: {}, setpoint: {} F"\ .format(app_name, agent_id, str(self.hour_current_schedule), str(self.minute_current_schedule), str(self.modeToChange_current).upper(), str(self.tempToChange_current)) else: # previous schedule from old setting file does not exist print "{} for Agent: {} >> There is no previous schedule from old setting file".format( app_name, agent_id) print '{} for Agent: {} >> Let' 's check for the next schedule'.format( app_name, agent_id) # 2. take action based on current time # 2.1 case 1: time to change schedule is triggered if self.flag_time_to_change_status is True: if debug_agent: print "{} for Agent: {} >> is changing the thermostat mode and temperature setpoint"\ .format(app_name, agent_id) try: _headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } if str(self.modeToChange_current).upper() == "HEAT": _content = { "thermostat_mode": str(self.modeToChange_current).upper(), "heat_setpoint": int(self.tempToChange_current) } elif str(self.modeToChange_current).upper() == "COOL": _content = { "thermostat_mode": str(self.modeToChange_current).upper(), "cool_setpoint": int(self.tempToChange_current) } else: _content = {} print "{} for Agent: {} >> published message to IEB with mode = {} setpoint = {} F"\ .format(app_name, agent_id, str(self.modeToChange_current).upper(), self.tempToChange_current) self.publish(topic_app_agent, _headers, json.dumps(_content)) self.flag_time_to_change_status = False except: print "{} for Agent: {} >> ERROR changing status of a thermostat"\ .format(app_name, agent_id) # 2.2 case2: time to change schedule is not triggered else: pass # print("Scheduler Agent takes no action") # 3. check next schedule # 3.1 check whether there is a next schedule _nextSchedule = list() for index in range(len(self.newTimeScheduleChange)): if self.now_decimal < self.newTimeScheduleChange[index]: _nextSchedule.append(self.newTimeScheduleChange[index]) if len(_nextSchedule) != 0: _time_next_schedule = int(min(_nextSchedule)) self.hour_next_schedule = int(_time_next_schedule / 60) self.minute_next_schedule = int(_time_next_schedule - (self.hour_next_schedule * 60)) self.modetemp_next = self.timeModeTemp[str( min(_nextSchedule))].split() self.modeToChange_next = self.modetemp_next[0] self.tempToChange_next = self.modetemp_next[1] print "{} for Agent: {} >> Here is the next schedule: at {} hr {} min mode = {} setpoint = {} F".\ format(app_name, agent_id, self.hour_next_schedule, self.minute_next_schedule, str(self.modeToChange_next).upper(), self.tempToChange_next) self.time_next_schedule_sec = int(_time_next_schedule * 60) _time_to_wait = self.time_next_schedule_sec - self.time_now_sec if _time_to_wait >= 3600: _hr_to_wait = int(_time_to_wait / 3600) _min_to_wait = int( (_time_to_wait - (_hr_to_wait * 3600)) / 60) _sec_to_wait = int(_time_to_wait - (_hr_to_wait * 3600) - (_min_to_wait * 60)) elif _time_to_wait > 60: _hr_to_wait = 0 _min_to_wait = int(_time_to_wait / 60) _sec_to_wait = int(_time_to_wait - (_min_to_wait * 60)) else: _hr_to_wait = 0 _min_to_wait = 0 _sec_to_wait = _time_to_wait print "{} for Agent: {} >> is going to sleep now until next schedule come up in {} hr {} min {} sec"\ .format(app_name, agent_id, _hr_to_wait, _min_to_wait, _sec_to_wait) else: print "{} for Agent: {} >> There is no next schedule".format( app_name, agent_id) self.time_next_schedule_sec = int(24 * 3600) print '---------------------------------' def find_day(self): localtime = time.localtime(time.time()) today = None if localtime.tm_wday == 0: today = 'monday' elif localtime.tm_wday == 1: today = 'tuesday' elif localtime.tm_wday == 2: today = 'wednesday' elif localtime.tm_wday == 3: today = 'thursday' elif localtime.tm_wday == 4: today = 'friday' elif localtime.tm_wday == 5: today = 'saturday' elif localtime.tm_wday == 6: today = 'sunday' return today def set_variable(self, k, v): # k=key, v=value self.variables[k] = v def get_variable(self, k): return self.variables.get(k, None) # default of get_variable is none Agent.__name__ = 'Thermostat Scheduler Agent' return Agent(**kwargs)
def PlugloadAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: value = kwargs.pop(name) except KeyError: return config.get(name, '') #1. @params agent agent_id = get_config('agent_id') device_monitor_time = get_config('device_monitor_time') LOG_DATA_PERIOD = get_config('poll_time') publish_address = 'ipc:///tmp/volttron-lite-agent-publish' subscribe_address = 'ipc:///tmp/volttron-lite-agent-subscribe' debug_agent = False #List of all keywords for a Plugload agent agentknowledge = dict(status=["status", "on", "off", "ON", "OFF"], power=["power"], energy=["energy"]) agentAPImapping = dict(status=[], power=[], energy=[]) #2. @params device_info #TODO correct the launchfile in Device Discovery Agent building_name = get_config('building_name') zone_id = get_config('zone_id') # room = get_config('room') model = get_config('model') device_type = get_config('type') address = get_config('address') address_get = get_config('address_get') address_put = get_config('address_put') address_post = get_config('address_post') vendor = get_config('vendor') auth_header = get_config('auth_header') smt_username = get_config('smt_username') smt_password = get_config('smt_password') address = get_config('address') _address = address _address = _address.replace('http://', '') _address = _address.replace('https://', '') try: # validate whether or not address is an ip address socket.inet_aton(_address) ip_address = _address # print "yes ip_address is {}".format(ip_address) except socket.error: # print "yes ip_address is None" ip_address = None identifiable = get_config('identifiable') # mac_address = get_config('mac_address') #3. @params agent & DB interfaces #TODO delete variable topic topic = get_config('topic') #TODO get database parameters from settings.py, add db_table for specific table db_host = get_config('db_host') db_port = get_config('db_port') db_database = get_config('db_database') db_user = get_config('db_user') db_password = get_config('db_password') db_table_plugload = settings.DATABASES['default']['TABLE_plugload'] db_table_notification_event = settings.DATABASES['default'][ 'TABLE_notification_event'] #TODO construct _topic_Agent_UI based on data obtained from DB _topic_Agent_UI = building_name + '/' + str( zone_id) + '/' + device_type + '/' + agent_id + '/' # _topic_Agent_UI = 'building1/999/'+device_type+'/'+agent_id+'/' # print(_topic_Agent_UI) #TODO construct _topic_Agent_sMAP based on data obtained from DB # _topic_Agent_sMAP = 'datalogger/log/building1/999/'+device_type+'/'+agent_id _topic_Agent_sMAP = 'datalogger/log/' + building_name + '/' + str( zone_id) + '/' + device_type + '/' + agent_id # print(_topic_Agent_sMAP) #4. @params device_api api = get_config('api') #TODO discovery agent locate the location of api in launch file e.g. "api": "testAPI.classAPI_RadioThermostat", # apiLib = importlib.import_module(api) # print(apiLib) apiLib = importlib.import_module("testAPI." + api) print("testAPI." + api) #4.1 initialize plugload device object if vendor == 'SmartThings': Plugload = apiLib.API(model=model, device_type=device_type, api=api, auth_header=auth_header, smt_username=smt_username, smt_password=smt_password, address=address, address_get=address_get, address_put=address_put, agent_id=agent_id) print( "{0}agent is initialized for SmartThings {1} using API={2} at {3}". format(agent_id, Plugload.get_variable('model'), Plugload.get_variable('api'), Plugload.get_variable('address'))) else: Plugload = apiLib.API(model=model, device_type=device_type, api=api, address=address, agent_id=agent_id) print("{0}agent is initialized for {1} using API={2} at {3}".format( agent_id, Plugload.get_variable('model'), Plugload.get_variable('api'), Plugload.get_variable('address'))) #4.2 initialize values of a Plugload # Plugload.set_variable('power', 0) # Plugload.set_variable('energy', 0) #5. @params notification_info send_notification = False email_fromaddr = settings.NOTIFICATION['email']['fromaddr'] email_recipients = settings.NOTIFICATION['email']['recipients'] email_username = settings.NOTIFICATION['email']['username'] email_password = settings.NOTIFICATION['email']['password'] email_mailServer = settings.NOTIFICATION['email']['mailServer'] notify_heartbeat = settings.NOTIFICATION['heartbeat'] class Agent(PublishMixin, BaseAgent): """Agent for querying WeatherUndergrounds API""" #1. agent initialization def __init__(self, **kwargs): #1. initialize all agent variables super(Agent, self).__init__(**kwargs) self.variables = kwargs self.valid_data = False self._keep_alive = True self.flag = 1 self.topic = topic self.event_ids = list() self.time_sent_notifications = {} self.notify_heartbeat = notify_heartbeat self.ip_address = ip_address if ip_address != None else None #2. setup connection with db -> Connect to bemossdb database try: self.con = psycopg2.connect(host=db_host, port=db_port, database=db_database, user=db_user, password=db_password) self.cur = self.con.cursor( ) # open a cursor to perfomm database operations print( "{} connects to the database name {} successfully".format( agent_id, db_database)) except: print("ERROR: {} fails to connect to the database name {}". format(agent_id, db_database)) #3. send notification to notify building admin self.send_notification = send_notification # self.send_notification_status = send_notification_status # if self.send_notification: self.subject = 'Message from ' + agent_id # self.text = 'Now an agent device_type {} for {} with API {} at address {} is launched!'.format( # Plugload.get_variable('device_type'), Plugload.get_variable('model'), # Plugload.get_variable('api'), Plugload.get_variable('address')) # emailService = EmailService() # emailService.sendEmail(email_fromaddr, email_recipients, email_username, email_password, self.subject, # self.text, email_mailServer) #These set and get methods allow scalability def set_variable(self, k, v): #k=key, v=value self.variables[k] = v def get_variable(self, k): return self.variables.get(k, None) #default of get_variable is none #2. agent setup method def setup(self): super(Agent, self).setup() #Do a one time push when we start up so we don't have to wait for the periodic self.timer(1, self.deviceMonitorBehavior) if identifiable == "True": Plugload.identifyDevice() #3. deviceMonitorBehavior (TickerBehavior) @periodic(device_monitor_time) def deviceMonitorBehavior(self): #step1: get current status of a thermostat, then map keywords and variables to agent knowledge try: Plugload.getDeviceStatus() #mapping variables from API to Agent's knowledge for APIKeyword, APIvariable in Plugload.variables.items(): if debug_agent: print(APIKeyword, APIvariable) self.set_variable( self.getKeyword(APIKeyword), APIvariable ) # set variables of agent from API variables agentAPImapping[self.getKeyword( APIKeyword )] = APIKeyword # map keyword of agent and API except: print "device connection for {} is not successful".format( agent_id) # step3: send notification to a user if required if self.send_notification: self.track_event_send_notification() #step4: update PostgresQL (meta-data) database try: self.cur.execute( "UPDATE " + db_table_plugload + " SET status=%s " "WHERE plugload_id=%s", (self.get_variable('status'), agent_id)) self.con.commit() if self.get_variable('power') != None: self.set_variable('power', int(self.get_variable('power'))) self.cur.execute( "UPDATE " + db_table_plugload + " SET power=%s " "WHERE plugload_id=%s", (self.get_variable('power'), agent_id)) self.con.commit() #TODO calculate energy #TODO check ip_address if self.ip_address != None: psycopg2.extras.register_inet() _ip_address = psycopg2.extras.Inet(self.ip_address) self.cur.execute( "UPDATE " + db_table_plugload + " SET ip_address=%s WHERE plugload_id=%s", (_ip_address, agent_id)) self.con.commit() #TODO check nickname #TODO check zone_id #TODO check network_status #TODO check other_parameters #TODO last_scanned_time _time_stamp_last_scanned = datetime.datetime.now() self.cur.execute( "UPDATE " + db_table_plugload + " SET last_scanned_time=%s " "WHERE plugload_id=%s", (_time_stamp_last_scanned, agent_id)) self.con.commit() # #TODO last_offline_time FIXXXXX # _time_stamp_last_offline = datetime.datetime.now() # self.cur.execute("UPDATE "+db_table_plugload+" SET last_offline_time=%s " # "WHERE plugload_id=%s", # (_time_stamp_last_offline, agent_id)) # self.con.commit() print( "{} updates database name {} during deviceMonitorBehavior successfully" .format(agent_id, db_database)) except: print("ERROR: {} fails to update the database name {}".format( agent_id, db_database)) #step5: update PostgresQL (meta-data) database try: if self.get_variable('status') is not None: self.publish_logdata1() if self.get_variable('power') is not None: self.publish_logdata2() if self.get_variable('energy') is not None: self.publish_logdata3() print "success update sMAP database" except: print( "ERROR: {} fails to update sMAP database".format(agent_id)) #step6: debug agent knowledge if debug_agent == True: print("printing agent's knowledge") for k, v in self.variables.items(): print(k, v) print('') if debug_agent == True: print("printing agentAPImapping's fields") for k, v in agentAPImapping.items(): print(k, v) def getKeyword(self, APIKeyword): for k, v in agentknowledge.items(): if APIKeyword in agentknowledge[k]: return k flag = 1 break else: flag = 0 pass if flag == 0: # if flag still 0 means that a APIKeyword is not in an agent knowledge, then add it to agent knowledge return APIKeyword #4. updateUIBehavior (generic behavior) @matching.match_exact('/ui/agent/' + _topic_Agent_UI + 'device_status') def updateUIBehavior(self, topic, headers, message, match): print "{} agent got\nTopic: {topic}".format( self.get_variable("agent_id"), topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}\n".format(message=message) #reply message topic = '/agent/ui/' + _topic_Agent_UI + 'device_status/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, # headers_mod.DATE: now, } if self.get_variable('power') is not None: _data = { 'device_id': agent_id, 'status': self.get_variable('status'), 'power': self.get_variable('power') } else: _data = { 'device_id': agent_id, 'status': self.get_variable('status') } message = json.dumps(_data) message = message.encode(encoding='utf_8') self.publish(topic, headers, message) #5. deviceControlBehavior (generic behavior) @matching.match_exact('/ui/agent/' + _topic_Agent_UI + 'update') def deviceControlBehavior(self, topic, headers, message, match): print "{} agent got\nTopic: {topic}".format( self.get_variable("agent_id"), topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}\n".format(message=message) #step1: change device status according to the receive message if self.isPostmsgValid(message[0]): # check if the data is valid setDeviceStatusResult = Plugload.setDeviceStatus( json.loads(message[0])) # Plugload.setDeviceStatus(self.get_variable('address'), json.loads(message[0])) #convert received message from string to JSON #TODO need to do additional checking whether the device setting is actually success!!!!!!!! #step2: update agent's knowledge on this device Plugload.getDeviceStatus() # Plugload.getDeviceStatus(self.get_variable('address')) #step3: send reply message back to the UI topic = '/agent/ui/' + _topic_Agent_UI + 'update/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT, # headers_mod.DATE: now, } if setDeviceStatusResult: message = 'success' else: message = 'failure' else: print( "The POST message is invalid, check status setting and try again\n" ) message = 'failure' self.publish(topic, headers, message) def isPostmsgValid(self, postmsg): # check validity of postmsg dataValidity = False try: _data = json.dumps(postmsg) _data = json.loads(_data) for k, v in _data.items(): if k == 'status': dataValidity = True break except: dataValidity = True print("dataValidity failed to validate data comes from UI") return dataValidity #6. deviceIdentifyBehavior (generic behavior) @matching.match_exact('/ui/agent/' + _topic_Agent_UI + 'identify') def deviceIdentifyBehavior(self, topic, headers, message, match): print "{} agent got\nTopic: {topic}".format( self.get_variable("agent_id"), topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}\n".format(message=message) #step1: change device status according to the receive message identifyDeviceResult = Plugload.identifyDevice() #TODO need to do additional checking whether the device setting is actually success!!!!!!!! #step2: send reply message back to the UI topic = '/agent/ui/' + _topic_Agent_UI + 'identify/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT, # headers_mod.DATE: now, } if identifyDeviceResult: message = 'success' else: message = 'failure' self.publish(topic, headers, message) #7. Update data to sMAP def publish_logdata1(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) content = { "status": { "Readings": [[ mytime, float(1 if self.get_variable("status") == "ON" else 0) ]], "Units": "N/A", "data_type": "double" } } print("{} published status to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata2(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) content = { "power": { "Readings": [[mytime, float(self.get_variable("power"))]], "Units": "N/A", "data_type": "double" } } print("{} published power to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) def publish_logdata3(self): headers = { headers_mod.FROM: agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, } mytime = int(time.time()) content = { "energy": { "Readings": [[mytime, float(self.get_variable("energy"))]], "Units": "N/A", "data_type": "double" } } print("{} published energy to an IEB".format(agent_id)) self.publish(_topic_Agent_sMAP, headers, json.dumps(content)) @matching.match_exact('/ui/agent/' + _topic_Agent_UI + 'add_notification_event') def add_notification_event(self, topic, headers, message, match): print agent_id + " got\nTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}".format(message=message) #reply message topic = '/agent/ui/' + _topic_Agent_UI + 'add_notification_event/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, # headers_mod.DATE: now, headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } #add event_id to self.event_ids _data = json.loads(message[0]) event_id = _data['event_id'] print "{} added notification event_id: {}".format( agent_id, event_id) self.event_ids.append(event_id) _data = "success" message = _data # message = json.dumps(_data) # message = message.encode(encoding='utf_8') self.publish(topic, headers, message) @matching.match_exact('/ui/agent/' + _topic_Agent_UI + 'remove_notification_event') def remove_notification_event(self, topic, headers, message, match): print agent_id + " got\nTopic: {topic}".format(topic=topic) print "Headers: {headers}".format(headers=headers) print "Message: {message}".format(message=message) #reply message topic = '/agent/ui/' + _topic_Agent_UI + 'remove_notification_event/response' # now = datetime.utcnow().isoformat(' ') + 'Z' headers = { 'AgentID': agent_id, headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, # headers_mod.DATE: now, headers_mod.FROM: agent_id, headers_mod.TO: 'ui' } #add event_id to self.event_ids _data = json.loads(message[0]) event_id = _data['event_id'] print "{} removed notification event_id: {}".format( agent_id, event_id) self.event_ids.remove(event_id) _data = "success" message = _data # message = json.dumps(_data) # message = message.encode(encoding='utf_8') self.publish(topic, headers, message) def track_event_send_notification(self): for event_id in self.event_ids: print "{} is monitoring event_id: {}\n".format( agent_id, event_id) # collect information about event from notification_event table self.cur.execute( "SELECT event_name, notify_device_id, triggered_parameter, comparator," "threshold, notify_channel, notify_address, notify_heartbeat FROM " + db_table_notification_event + " WHERE event_id=%s", (event_id, )) if self.cur.rowcount != 0: row = self.cur.fetchone() event_name = str(row[0]) notify_device_id = str(row[1]) triggered_parameter = str(row[2]) comparator = str(row[3]) threshold = row[4] notify_channel = str(row[5]) notify_address = row[6] notify_heartbeat = row[7] if row[ 7] is not None else self.notify_heartbeat _event_has_triggered = False print "{} triggered_parameter:{} self.get_variable(triggered_parameter):{} comparator:{} threshold:{}"\ .format(agent_id, triggered_parameter, self.get_variable(triggered_parameter), comparator, threshold) #check whether message is already sent try: if (datetime.datetime.now() - self.time_sent_notifications[event_id] ).seconds > notify_heartbeat: if notify_device_id == agent_id: if comparator == "<": threshold = float(threshold) if self.get_variable( triggered_parameter) < threshold: _event_has_triggered = True elif comparator == ">": threshold = float(threshold) if self.get_variable( triggered_parameter) > threshold: _event_has_triggered = True print "{} triggered_parameter:{} self.get_variable(triggered_parameter):{} comparator:{} threshold:{}"\ .format(agent_id, triggered_parameter, self.get_variable(triggered_parameter), comparator, threshold) print "{} _event_has_triggerered {}".format( agent_id, _event_has_triggered) elif comparator == "<=": threshold = float(threshold) if self.get_variable( triggered_parameter) <= threshold: _event_has_triggered = True elif comparator == ">=": threshold = float(threshold) if self.get_variable( triggered_parameter) >= threshold: _event_has_triggered = True elif comparator == "is": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable( triggered_parameter) is threshold: _event_has_triggered = True elif comparator == "isnot": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable(triggered_parameter ) is not threshold: _event_has_triggered = True else: pass if _event_has_triggered: # notify the user if triggered #step2 notify user to notify_channel at notify_address with period notify_heartbeat if notify_channel == 'email': _email_text = '{} notification event triggered_parameter: {}, comparator: {}, ' \ 'threshold: {}\n now the current status of triggered_parameter: {} is {}' \ .format(agent_id, triggered_parameter, comparator, threshold, triggered_parameter, self.get_variable(triggered_parameter)) emailService = EmailService() emailService.sendEmail( email_fromaddr, notify_address, email_username, email_password, self.subject, _email_text, email_mailServer) # self.send_notification_status = True #TODO store time_send_notification for each event self.time_sent_notifications[ event_id] = datetime.datetime.now( ) print "time_sent_notifications is {}".format( self. time_sent_notifications[event_id]) print( '{} >> sent notification message for {}' .format(agent_id, event_name)) print( '{} notification event triggered_parameter: {}, comparator: {}, threshold: {}' .format(agent_id, triggered_parameter, comparator, threshold)) else: print "{} >> notification channel: {} is not supported yet".format( agent_id, notify_channel) else: print "{} >> Event is not triggered".format( agent_id) else: "{} >> this event_id {} is not for this device".format( agent_id, event_id) else: "{} >> Email is already sent, waiting for another heartbeat period".format( agent_id) except: #step1 compare triggered_parameter with comparator to threshold #step1.1 classify comparator <,>,<=,>=,is,isnot #case1 comparator < print "{} >> first time trigger notification".format( agent_id) if notify_device_id == agent_id: if comparator == "<": threshold = float(threshold) if self.get_variable( triggered_parameter) < threshold: _event_has_triggered = True elif comparator == ">": threshold = float(threshold) if self.get_variable( triggered_parameter) > threshold: _event_has_triggered = True print "{} triggered_parameter:{} self.get_variable(triggered_parameter):{} comparator:{} threshold:{}"\ .format(agent_id, triggered_parameter, self.get_variable(triggered_parameter), comparator, threshold) print "{} _event_has_triggerered {}".format( agent_id, _event_has_triggered) elif comparator == "<=": threshold = float(threshold) if self.get_variable( triggered_parameter) <= threshold: _event_has_triggered = True elif comparator == ">=": threshold = float(threshold) if self.get_variable( triggered_parameter) >= threshold: _event_has_triggered = True elif comparator == "is": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable( triggered_parameter) is threshold: _event_has_triggered = True elif comparator == "isnot": if threshold == "True": threshold = True elif threshold == "False": threshold = False else: threshold = str(threshold) if self.get_variable( triggered_parameter) is not threshold: _event_has_triggered = True else: pass print "{} >> _event_has_triggered {}".format( agent_id, _event_has_triggered) if _event_has_triggered: # notify the user if triggered #step2 notify user to notify_channel at notify_address with period notify_heartbeat if notify_channel == 'email': _email_text = '{} notification event triggered_parameter: {}, comparator: {}, ' \ 'threshold: {}\n now the current status of triggered_parameter: {} is {}' \ .format(agent_id, triggered_parameter, comparator, threshold, triggered_parameter, self.get_variable(triggered_parameter)) emailService = EmailService() emailService.sendEmail( email_fromaddr, notify_address, email_username, email_password, self.subject, _email_text, email_mailServer) # self.send_notification_status = True #store time_send_notification for each event self.time_sent_notifications[ event_id] = datetime.datetime.now() print "{} >> time_sent_notifications is {}".format( agent_id, self.time_sent_notifications[event_id]) print( '{} >> sent notification message for {}' .format(agent_id, event_name)) print( '{} notification event triggered_parameter: {}, comparator: {}, threshold: {}' .format(agent_id, triggered_parameter, comparator, threshold)) else: print "{} >> notification channel: {} is not supported yet".format( agent_id, notify_channel) else: print "{} >> Event is not triggered".format( agent_id) else: "{} >> this event_id {} is not for this device".format( agent_id, event_id) else: pass Agent.__name__ = 'PlugloadAgent' return Agent(**kwargs)
def ActuatorAgent(config_path, **kwargs): config = utils.load_config(config_path) url = config['url'] schedule_publish_interval = int(config.get('schedule_publish_interval', 60)) heartbeat_interval = int(config.get('heartbeat_interval', 60)) points = config.get('points', {}) schedule_state_file = config.get('schedule_state_file') preempt_grace_time = config.get('preempt_grace_time', 60) minimum_slot_time = config.get('preempt_grace_time', preempt_grace_time * 2) class Agent(PublishMixin, BaseAgent): '''Agent to listen for requests to talk to the sMAP driver.''' def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) self._update_event = None self._update_event_time = None self._device_states = {} self.setup_schedule() self.setup_heartbeats() def setup_heartbeats(self): for point in points: heartbeat_point = points[point].get("heartbeat_point") if heartbeat_point is None: continue heartbeat_handler = self.heartbeat_factory( point, heartbeat_point) self.periodic_timer(heartbeat_interval, heartbeat_handler) def heartbeat_factory(self, point, actuator): #Stupid lack on nonlocal in 2.x value = [False] request_url = '/'.join([url, point, ACTUATOR_COLLECTION, actuator]) publish_topic = '/'.join([point, actuator]) def update_heartbeat_value(): value[0] = not value[0] payload = {'state': str(int(value[0]))} try: r = requests.put(request_url, params=payload) self.process_smap_request_result(r, publish_topic, None) except requests.exceptions.ConnectionError: print "Warning: smap driver not running." return update_heartbeat_value def setup_schedule(self): now = datetime.datetime.now() self._schedule_manager = ScheduleManager( preempt_grace_time, now=now, state_file_name=schedule_state_file) self.update_device_state_and_schedule(now) def update_device_state_and_schedule(self, now): self._device_states = self._schedule_manager.get_schedule_state( now) schedule_next_event_time = self._schedule_manager.get_next_event_time( now) new_update_event_time = self._get_ajusted_next_event_time( now, schedule_next_event_time) for device, state in self._device_states.iteritems(): header = self.get_headers(state.agent_id, time=str(now), task_id=state.task_id) header['window'] = state.time_remaining topic = topics.ACTUATOR_SCHEDULE_ANNOUNCE_RAW.replace( '{device}', device) self.publish_json(topic, header, {}) if self._update_event is not None: #This won't hurt anything if we are canceling ourselves. self._update_event.cancel() self._update_event_time = new_update_event_time self._update_event = EventWithTime(self._update_schedule_state) self.schedule(self._update_event_time, self._update_event) def _get_ajusted_next_event_time(self, now, next_event_time): latest_next = now + datetime.timedelta( seconds=schedule_publish_interval) #Round to the next second to fix timer goofyness in agent timers. if latest_next.microsecond: latest_next = latest_next.replace( microsecond=0) + datetime.timedelta(seconds=1) if next_event_time is None or latest_next < next_event_time: return latest_next return next_event_time def _update_schedule_state(self, unix_time): #Find the current slot and update the state now = datetime.datetime.fromtimestamp(unix_time) self.update_device_state_and_schedule(now) @matching.match_regex(topics.ACTUATOR_GET() + '/(.+)') def handle_get(self, topic, headers, message, match): point = match.group(1) collection_tokens, point_name = point.rsplit('/', 1) requester = headers.get('requesterID') if self.check_lock(collection_tokens, requester): request_url = '/'.join( [url, collection_tokens, ACTUATOR_COLLECTION, point_name]) try: r = requests.get(request_url) self.process_smap_request_result(r, point, requester) except (requests.exceptions.ConnectionError) as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) else: error = { 'type': 'LockError', 'value': 'does not have this lock' } self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) @matching.match_regex(topics.ACTUATOR_SET() + '/(.+)') def handle_set(self, topic, headers, message, match): point = match.group(1) collection_tokens, point_name = point.rsplit('/', 1) requester = headers.get('requesterID') headers = self.get_headers(requester) if not message: error = {'type': 'ValueError', 'value': 'missing argument'} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) return else: try: message = jsonapi.loads(message[0]) if isinstance(message, bool): message = int(message) except ValueError as ex: # Could be ValueError of JSONDecodeError depending # on if simplesjson was used. JSONDecodeError # inherits from ValueError error = {'type': 'ValueError', 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) return if self.check_lock(collection_tokens, requester): request_url = '/'.join( [url, collection_tokens, ACTUATOR_COLLECTION, point_name]) payload = {'state': str(message)} try: r = requests.put(request_url, params=payload) self.process_smap_request_result(r, point, requester) except (requests.exceptions.ConnectionError) as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) else: error = { 'type': 'LockError', 'value': 'does not have this lock' } self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) def check_lock(self, device, requester): device = device.strip('/') if device in self._device_states: device_state = self._device_states[device] return device_state.agent_id == requester return False @matching.match_exact(topics.ACTUATOR_SCHEDULE_REQUEST()) def handle_schedule_request(self, topic, headers, message, match): request_type = headers.get('type') now = datetime.datetime.now() if request_type == SCHEDULE_ACTION_NEW: self.handle_new(headers, message, now) elif request_type == SCHEDULE_ACTION_CANCEL: self.handle_cancel(headers, now) else: self.publish_json( topics.ACTUATOR_SCHEDULE_RESULT(), headers, { 'result': SCHEDULE_RESPONSE_FAILURE, 'data': {}, 'info': 'INVALID_REQUEST_TYPE' }) def handle_new(self, headers, message, now): requester = headers.get('requesterID') taskID = headers.get('taskID') priority = headers.get('priority') try: requests = jsonapi.loads(message[0]) except (ValueError, IndexError) as ex: # Could be ValueError of JSONDecodeError depending # on if simplesjson was used. JSONDecodeError # inherits from ValueError #We let the schedule manager tell us this is a bad request. requests = [] result = self._schedule_manager.request_slots( requester, taskID, requests, priority, now) success = SCHEDULE_RESPONSE_SUCCESS if result.success else SCHEDULE_RESPONSE_FAILURE #If we are successful we do something else with the real result data data = result.data if not result.success else {} topic = topics.ACTUATOR_SCHEDULE_RESULT() headers = self.get_headers(requester, task_id=taskID) headers['type'] = SCHEDULE_ACTION_NEW self.publish_json(topic, headers, { 'result': success, 'data': data, 'info': result.info_string }) #Dealing with success and other first world problems. if result.success: self.update_device_state_and_schedule(now) for preempted_task in result.data: topic = topics.ACTUATOR_SCHEDULE_RESULT() headers = self.get_headers(preempted_task[0], task_id=preempted_task[1]) headers['type'] = SCHEDULE_ACTION_CANCEL self.publish_json( topic, headers, { 'result': SCHEDULE_CANCEL_PREEMPTED, 'info': '', 'data': { 'agentID': requester, 'taskID': taskID } }) def handle_cancel(self, headers, now): requester = headers.get('requesterID') taskID = headers.get('taskID') result = self._schedule_manager.cancel_task(requester, taskID, now) success = SCHEDULE_RESPONSE_SUCCESS if result.success else SCHEDULE_RESPONSE_FAILURE topic = topics.ACTUATOR_SCHEDULE_RESULT() self.publish_json(topic, headers, { 'result': success, 'info': result.info_string, 'data': {} }) if result.success: self.update_device_state_and_schedule(now) def get_headers(self, requester, time=None, task_id=None): headers = {} if time is not None: headers['time'] = time else: headers = {'time': str(datetime.datetime.utcnow())} if requester is not None: headers['requesterID'] = requester if task_id is not None: headers['taskID'] = task_id return headers def process_smap_request_result(self, request, point, requester): headers = self.get_headers(requester) try: request.raise_for_status() results = request.json() readings = results['Readings'] reading = readings[0][1] self.push_result_topic_pair(VALUE_RESPONSE_PREFIX, point, headers, reading) except requests.exceptions.HTTPError as ex: error = { 'type': ex.__class__.__name__, 'value': str(request.text) } self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) except (ValueError, IndexError, KeyError, requests.exceptions.ConnectionError) as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) def push_result_topic_pair(self, prefix, point, headers, *args): topic = normtopic('/'.join([prefix, point])) self.publish_json(topic, headers, *args) Agent.__name__ = 'ActuatorAgent' return Agent(**kwargs)
def OpenADRAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: value = kwargs.pop(name) except KeyError: return config[name] agent_id = get_config('agentid') ven_id = get_config('ven_id') vtn_uri = get_config('vtn_uri') poll_interval = get_config('poll_interval') vtn_ids = get_config('vtn_ids') control_interval = get_config('control_interval') smap_uri = get_config('smap_uri') smap_source_name = get_config('smap_source_name') smap_series_uuid = get_config('smap_series_uuid') smap_source_location = get_config('smap_source_location') event_db = database.DBHandler() class Agent(PublishMixin, BaseAgent): ''' This agent acts as an OpenADR VEN and publishes oadrDistributeEvent messages to the bus ''' def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) self.events = dict() self.oadr_client = poll.OpenADR2(vtn_base_uri=vtn_uri, vtn_poll_interval=poll_interval, event_config={ "ven_id": ven_id, "vtn_ids": vtn_ids, "event_callback": self.oadr_event }, control_opts={ "signal_changed_callback": self.signal_changed_callback, "control_loop_interval": control_interval }) def signal_changed_callback(self, old_level, new_level): ''' This is called whenever the `oadr2.control.EventController` detects a change in "current signal level" based on active OpenADR2 events. ''' log.debug("stubbed signal change callback") def oadr_event(self, updated={}, canceled={}): '''Callback following VTN poll updated and cancelled events are passed in when present''' log.debug("Updated & new event(s): %s", updated) log.debug("Canceled event(s): %s", canceled) headers = { headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.JSON, 'requesterID': agent_id } for event_id, payload in updated.iteritems(): event_data = self.build_event_info(payload) self.publish_json(topics.OPENADR_EVENT, headers, event_data) for event_id, payload in canceled.iteritems(): event_data = self.build_event_info(payload) event_data['status'] = "cancelled" self.publish_json(topics.OPENADR_EVENT, headers, event_data) current_event_state = self.get_event_state() self.post_to_smap(current_event_state) self.publish_json(topics.OPENADR_STATUS, headers, {"active": (current_event_state == 'active')}) # ----- temporary ----- # # a get_event_state method should probably be added to oadr2 def get_event_state(self): active_event = None active_events = event_db.get_active_events() for event_id in active_events.iterkeys(): event_data = self.get_event(event_id) e_start = event.get_active_period_start(event_data).replace( tzinfo=pytz.utc) now = dt.datetime.now(pytz.utc) if e_start < now: active_event = event_data return ("active" if active_event is not None else "inactive") # --------------------- # # ----- temporary ----- # # copied from oadr.event to provide support for get_event_state above def get_event(self, e_id): evt = event_db.get_event(e_id) if evt is not None: evt = etree.XML(evt) return evt # --------------------- # def build_smap_event_status_object(self, status): """Generate payload for POSTing current event status to sMAP""" status = (1 if status == 'active' else 0) current_time = int(time.time()) * 1000 return { smap_source_location: { "Metadata": { "SourceName": smap_source_name }, "Properties": { "UnitofMeasure": "Active/Inactive" }, "Readings": [[current_time, status]], "uuid": smap_series_uuid } } def post_to_smap(self, status): """POST updated event status info to sMAP series""" smap_payload = self.build_smap_event_status_object(status) log.debug("Updating sMAP event status: %s", status) r = requests.post(smap_uri, data=json.dumps(smap_payload)) if r.status_code != requests.codes.ok: logging.warn("sMAP update unsuccessful: %s", r.status_code) logging.debug("sMAP update unsuccessful: %s", r.text) def build_event_info(self, event_xml): e_id = event.get_event_id(event_xml) e_mod_num = event.get_mod_number(event_xml) e_status = event.get_status(event_xml) # TODO get target & resource info event_start_dttm = event.get_active_period_start(event_xml) event_start_dttm = schedule.dttm_to_str(event_start_dttm) signals = event.get_signals(event_xml) # get start/end time start_at = event_xml.findtext( 'ei:eiActivePeriod/xcal:properties/xcal:dtstart/xcal:date-time', namespaces=event.NS_A) start_at = schedule.str_to_datetime(start_at) duration = event_xml.findtext( 'ei:eiActivePeriod/xcal:properties/xcal:duration/xcal:duration', namespaces=event.NS_A) start_at, end_at = schedule.durations_to_dates( start_at, [duration]) event_info = { "id": e_id, "mod_num": e_mod_num, "status": e_status, "start_at": start_at.strftime("%Y-%m-%d %H:%M:%S"), "end_at": end_at.strftime("%Y-%m-%d %H:%M:%S"), } if signals: event_info["signals"] = signals return event_info Agent.__name__ = 'OpenADRAgent' return Agent(**kwargs)
def DemandResponseAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: csp = kwargs.pop(name) except KeyError: return config[name] agent_id = get_config('agentid') rtu_path = { 'campus': get_config('campus'), 'building': get_config('building'), 'unit': get_config('unit'), } class Agent(PublishMixin, BaseAgent): def setup(self): super(Agent, self).setup() headers = { 'Content-Type': 'text/plain', 'requesterID': agent_id, } #DT=datetime.datetime.now().replace(hour=0,minute=0,second=0, microsecond=0) #signal=settings.signal #self.schedule(DT,sched.Event(self.check_signal,[signal])) self.start_timer=self.periodic_timer(10,self.get_signal) @matching.match_exact(topics.RTU_VALUE(point='MixedAirTemperature', **rtu_path)) def on_new_data(self, topic, headers, message, match): data = jsonapi.loads(message[0]) mixed_air_temperature=data print(mixed_air_temperature) def __init__(self, **kwargs): super(Agent,self).__init__(**kwargs) self.after_timer = None def pre_cpp_timer(self,csp_normal): print("Pre-cooling for CPP Event") #pre-cool change cooling set point self.pre_timer = self.periodic_timer(5, self.pre_cpp_cooling,{'csp':settings.csp_norm}) def pre_cpp_cooling(self,csp): if csp['csp']> settings.csp_pre: csp['csp']=csp['csp']-1 print(csp) elif csp['csp']<=settings.csp_pre: csp['csp']=settings.csp_pre self.pre_timer.cancel() print(csp) def accelerated_pre_cooling_timer(self,pre_slope, csp): print("Accelerated pre-cooling for CPP Event") self.pre_timer = self.periodic_timer(5, self.accelerated_pre_cooling,pre_slope,{'csp':csp}) def accelerated_pre_cooling(self,pre_slope,csp): if csp['csp']> settings.csp_pre: csp['csp']=csp['csp']-1*pre_slope print(csp) elif csp['csp']<=settings.csp_pre: csp['csp']=settings.csp_pre print(csp) self.pre_timer.cancel() def during_cpp(self): print("During CPP Event") def after_cpp_timer(self,csp_normal): #Pull current cooling setpoint from controller CSP #CSP= PULL FROM CONTROLLER (GET NEW DATA) print(csp_normal) print("After CPP Event, returning to normal operations") self.after_timer = self.periodic_timer(3, self.after_cpp_cooling, csp_normal,{'csp':80}) #set cooling setpoint down by 1 degree every 30 minutes until it reaches normal def after_cpp_cooling(self,csp_normal,csp): print("After_CPP_COOLING") if csp['csp'] > csp_normal: csp['csp']=csp['csp']-1 print(csp) print(datetime.datetime.now()) elif csp['csp'] <= csp_normal: self.after_timer.cancel() csp['csp']=csp_normal print(csp) self.setup() def get_signal(self): #Pull signal from source 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=51,second=0, microsecond=0).timetuple()) time_after=time.mktime(datetime.datetime.now().replace(hour=settings.after_cpp_hour,minute=54,second=0, microsecond=0).timetuple()) print(time_now) print(time_event) #PULL NORMAL COOLING SETPOINT csp_normal=settings.csp_norm if (settings.signal and time_now<time_pre): print ("Scheduling") pre_cpp_time=datetime.datetime.now().replace(hour=settings.pre_cpp_hour,minute=25,second=10, microsecond=0) self.schedule(pre_cpp_time,sched.Event(self.pre_cpp_timer, (csp_normal,))) during_cpp_time=datetime.datetime.now().replace(hour=settings.during_cpp_hour,minute=26,second=20, microsecond=0) self.schedule(during_cpp_time,sched.Event(self.during_cpp)) after_cpp_time=datetime.datetime.now().replace(hour=settings.after_cpp_hour,minute=27,second=30, microsecond=0) self.schedule(after_cpp_time,sched.Event(self.after_cpp_timer, (csp_normal,))) self.start_timer.cancel() elif(settings.signal and time_now>time_pre and time_now<time_event): print("Scheduling") self.start_timer.cancel() pre_slope=(time_event-time_now)/(3600) during_cpp_time=datetime.datetime.now().replace(hour=settings.during_cpp_hour,minute=46,second=20, microsecond=0) self.schedule(during_cpp_time,sched.Event(self.during_cpp)) after_cpp_time=datetime.datetime.now().replace(hour=settings.after_cpp_hour,minute=47,second=10, microsecond=0) self.schedule(after_cpp_time,sched.Event(self.after_cpp_timer, (csp_normal,))) self.accelerated_pre_cooling_timer(pre_slope,csp_normal) elif(settings.signal and time_now>time_event and time_now<time_after): print("Too late to pre-cool!") self.start_timer.cancel() after_cpp_time=datetime.datetime.now().replace(hour=settings.after_cpp_hour,minute=54,second=10, microsecond=0) self.schedule(after_cpp_time,sched.Event(self.after_cpp_timer, (csp_normal,))) self.during_cpp() print("CPP Event Missed") self.setup() Agent.__name__ = 'DemandResponseAgent' return Agent(**kwargs)
def ControllerAgent(config_path, **kwargs): config = utils.load_config(config_path) def get_config(name): try: value = kwargs.pop(name) except KeyError: return config[name] agent_id = get_config('agentid') rtu_path = { 'campus': get_config('campus'), 'building': get_config('building'), 'unit': get_config('unit'), } fan_point = get_config('fan_point') class Agent(PublishMixin, BaseAgent): '''Agent to control cool fan speed with outside air temperature. This agent listens for outdoor temperature readings then changes the cool fan speed. It demonstrates pub/sub interaction with the RTU Controller. Requirements for running this agent (or any agent wishing to interact with the RTU: * Edit the driver.ini file to reflect the sMAP key, UUID, and other settings for your installation. * Activate the project Python from the project dir: . bin/activate. * Launch the sMAP driver by starting (from the project directory): twistd -n smap your_driver.ini. * Launch the ActuatorAgent just as you would launch any other agent. With these requirements met: * Subscribe to the outside air temperature topic. * If the new reading is higher than the old reading then * Request the actuator lock for the rtu * If it receives a lock request success it randomly sets the cool supply fan to a new reading. * If it does not get the lock, it will try again the next time the temperature rises. * If the set result is a success, it releases the lock. ''' def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) self.prev_temp = 0 def change_coolspeed(self): '''Setup our request''' headers = { headers_mod.CONTENT_TYPE: headers_mod.CONTENT_TYPE.PLAIN_TEXT, 'requesterID': agent_id } self.publish(topics.ACTUATOR_LOCK_ACQUIRE(**rtu_path), headers) @matching.match_exact( topics.RTU_VALUE(point='OutsideAirTemperature', **rtu_path)) def on_outside_temp(self, topic, headers, message, match): '''Respond to the outside air temperature events.''' print "Topic: {topic}, {headers}, Message: {message}".format( topic=topic, headers=headers, message=message) #Content type is json, load it for use cur_temp = jsonapi.loads(message[0]) #If temp has risen, attempt to set cool supply fan if cur_temp > self.prev_temp: self.change_coolspeed() self.prev_temp = cur_temp @matching.match_exact(topics.ACTUATOR_LOCK_RESULT(**rtu_path)) def on_lock_result(self, topic, headers, message, match): '''Respond to lock result events.''' print "Topic: {topic}, {headers}, Message: {message}".format( topic=topic, headers=headers, message=message) if headers['requesterID'] != agent_id: #If we didn't request this lock, we don't care about the result print "Not me, don't care." return mess = jsonapi.loads(message[0]) #If we got a success then set it at a random value if mess == 'SUCCESS': setting = random.randint(10, 90) headers[headers_mod.CONTENT_TYPE] = ( headers_mod.CONTENT_TYPE.PLAIN_TEXT) headers['requesterID'] = agent_id self.publish(topics.ACTUATOR_SET(point=fan_point, **rtu_path), headers, agent_id) elif mess == 'RELEASE': #Our lock release result was a success print "Let go of lock" @matching.match_exact( topics.ACTUATOR_VALUE(point=fan_point, **rtu_path)) def on_set_result(self, topic, headers, message, match): '''Result received, release the lock''' print "Topic: {topic}, {headers}, Message: {message}".format( topic=topic, headers=headers, message=message) self.publish(topics.ACTUATOR_LOCK_RELEASE(**rtu_path), headers, agent_id) Agent.__name__ = 'ControllerAgent' return Agent(**kwargs)
def __init__(self, config_path, **kwargs): super(ScheduleExampleAgent, self).__init__(**kwargs) self.config = utils.load_config(config_path)
def __init__(self, config_path, **kwargs): super(AgentSimulator, self).__init__(**kwargs) self.config = utils.load_config(config_path)