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])
import time from collections import OrderedDict from dateutil.rrule import DAILY, rruleset, rrule import requests from volttron.lite.agent import BaseAgent, PublishMixin from volttron.lite.agent import matching, utils from volttron.lite.agent.utils import jsonapi from volttron.lite.messaging import topics from volttron.lite.messaging.utils import normtopic from volttron.lite.agent.sched import EventWithTime from scheduler import ScheduleManager VALUE_RESPONSE_PREFIX = topics.ACTUATOR_VALUE() ERROR_RESPONSE_PREFIX = topics.ACTUATOR_ERROR() SCHEDULE_ACTION_NEW = 'NEW_SCHEDULE' SCHEDULE_ACTION_CANCEL = 'CANCEL_SCHEDULE' SCHEDULE_RESPONSE_SUCCESS = 'SUCCESS' SCHEDULE_RESPONSE_FAILURE = 'FAILURE' SCHEDULE_CANCEL_PREEMPTED = 'PREEMPTED' ACTUATOR_COLLECTION = 'actuators' def ActuatorAgent(config_path, **kwargs): config = utils.load_config(config_path)
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()
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)
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()
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)
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
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
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)