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.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): """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): '''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)
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'))
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)