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 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 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_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
class Agent(PublishMixin, BaseAgent): def setup(self): super(Agent, self).setup() self.damper = 0 @matching.match_regex(topics.ACTUATOR_LOCK_ACQUIRE() + '(/.*)') def on_lock_result(self, topic, headers, message, match): _log.debug( "Topic: {topic}, {headers}, Message: {message}".format( topic=topic, headers=headers, message=message)) self.publish(topics.ACTUATOR_LOCK_RESULT() + match.group(0), headers, jsonapi.dumps('SUCCESS')) @matching.match_regex(topics.ACTUATOR_SET() + '(/.*/([^/]+))') def on_new_data(self, topic, headers, message, match): _log.debug( "Topic: {topic}, {headers}, Message: {message}".format( topic=topic, headers=headers, message=message)) if match.group(2) == 'Damper': self.damper = int(message[0]) self.publish(topics.ACTUATOR_VALUE() + match.group(0), headers, message[0]) @periodic(5) def send_data(self): data = { 'ReturnAirTemperature': 55, 'OutsideAirTemperature': 50, 'MixedAirTemperature': 45, 'Damper': self.damper } self.publish_ex(topics.RTU_VALUE(point='all', **rtu_path), {}, ('application/json', jsonapi.dumps(data)))
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 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 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
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 _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 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
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 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"
class Agent(PublishMixin, BaseAgent): '''Agent to listen for requests to talk to the sMAP driver.''' def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) self._update_event = None self._update_event_time = None self._device_states = {} self.setup_schedule() self.setup_heartbeats() def setup_heartbeats(self): for point in points: heartbeat_point = points[point].get("heartbeat_point") if heartbeat_point is None: continue heartbeat_handler = self.heartbeat_factory( point, heartbeat_point) self.periodic_timer(heartbeat_interval, heartbeat_handler) def heartbeat_factory(self, point, actuator): #Stupid lack on nonlocal in 2.x value = [False] request_url = '/'.join([url, point, ACTUATOR_COLLECTION, actuator]) publish_topic = '/'.join([point, actuator]) def update_heartbeat_value(): value[0] = not value[0] payload = {'state': str(int(value[0]))} try: r = requests.put(request_url, params=payload) self.process_smap_request_result(r, publish_topic, None) except requests.exceptions.ConnectionError: print "Warning: smap driver not running." return update_heartbeat_value def setup_schedule(self): now = datetime.datetime.now() self._schedule_manager = ScheduleManager( preempt_grace_time, now=now, state_file_name=schedule_state_file) self.update_device_state_and_schedule(now) def update_device_state_and_schedule(self, now): self._device_states = self._schedule_manager.get_schedule_state( now) schedule_next_event_time = self._schedule_manager.get_next_event_time( now) new_update_event_time = self._get_ajusted_next_event_time( now, schedule_next_event_time) for device, state in self._device_states.iteritems(): header = self.get_headers(state.agent_id, time=str(now), task_id=state.task_id) header['window'] = state.time_remaining topic = topics.ACTUATOR_SCHEDULE_ANNOUNCE_RAW.replace( '{device}', device) self.publish_json(topic, header, {}) if self._update_event is not None: #This won't hurt anything if we are canceling ourselves. self._update_event.cancel() self._update_event_time = new_update_event_time self._update_event = EventWithTime(self._update_schedule_state) self.schedule(self._update_event_time, self._update_event) def _get_ajusted_next_event_time(self, now, next_event_time): latest_next = now + datetime.timedelta( seconds=schedule_publish_interval) #Round to the next second to fix timer goofyness in agent timers. if latest_next.microsecond: latest_next = latest_next.replace( microsecond=0) + datetime.timedelta(seconds=1) if next_event_time is None or latest_next < next_event_time: return latest_next return next_event_time def _update_schedule_state(self, unix_time): #Find the current slot and update the state now = datetime.datetime.fromtimestamp(unix_time) self.update_device_state_and_schedule(now) @matching.match_regex(topics.ACTUATOR_GET() + '/(.+)') def handle_get(self, topic, headers, message, match): point = match.group(1) collection_tokens, point_name = point.rsplit('/', 1) requester = headers.get('requesterID') if self.check_lock(collection_tokens, requester): request_url = '/'.join( [url, collection_tokens, ACTUATOR_COLLECTION, point_name]) try: r = requests.get(request_url) self.process_smap_request_result(r, point, requester) except (requests.exceptions.ConnectionError) as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) else: error = { 'type': 'LockError', 'value': 'does not have this lock' } self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) @matching.match_regex(topics.ACTUATOR_SET() + '/(.+)') def handle_set(self, topic, headers, message, match): point = match.group(1) collection_tokens, point_name = point.rsplit('/', 1) requester = headers.get('requesterID') headers = self.get_headers(requester) if not message: error = {'type': 'ValueError', 'value': 'missing argument'} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) return else: try: message = jsonapi.loads(message[0]) if isinstance(message, bool): message = int(message) except ValueError as ex: # Could be ValueError of JSONDecodeError depending # on if simplesjson was used. JSONDecodeError # inherits from ValueError error = {'type': 'ValueError', 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) return if self.check_lock(collection_tokens, requester): request_url = '/'.join( [url, collection_tokens, ACTUATOR_COLLECTION, point_name]) payload = {'state': str(message)} try: r = requests.put(request_url, params=payload) self.process_smap_request_result(r, point, requester) except (requests.exceptions.ConnectionError) as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) else: error = { 'type': 'LockError', 'value': 'does not have this lock' } self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) def check_lock(self, device, requester): device = device.strip('/') if device in self._device_states: device_state = self._device_states[device] return device_state.agent_id == requester return False @matching.match_exact(topics.ACTUATOR_SCHEDULE_REQUEST()) def handle_schedule_request(self, topic, headers, message, match): request_type = headers.get('type') now = datetime.datetime.now() if request_type == SCHEDULE_ACTION_NEW: self.handle_new(headers, message, now) elif request_type == SCHEDULE_ACTION_CANCEL: self.handle_cancel(headers, now) else: self.publish_json( topics.ACTUATOR_SCHEDULE_RESULT(), headers, { 'result': SCHEDULE_RESPONSE_FAILURE, 'data': {}, 'info': 'INVALID_REQUEST_TYPE' }) def handle_new(self, headers, message, now): requester = headers.get('requesterID') taskID = headers.get('taskID') priority = headers.get('priority') try: requests = jsonapi.loads(message[0]) except (ValueError, IndexError) as ex: # Could be ValueError of JSONDecodeError depending # on if simplesjson was used. JSONDecodeError # inherits from ValueError #We let the schedule manager tell us this is a bad request. requests = [] result = self._schedule_manager.request_slots( requester, taskID, requests, priority, now) success = SCHEDULE_RESPONSE_SUCCESS if result.success else SCHEDULE_RESPONSE_FAILURE #If we are successful we do something else with the real result data data = result.data if not result.success else {} topic = topics.ACTUATOR_SCHEDULE_RESULT() headers = self.get_headers(requester, task_id=taskID) headers['type'] = SCHEDULE_ACTION_NEW self.publish_json(topic, headers, { 'result': success, 'data': data, 'info': result.info_string }) #Dealing with success and other first world problems. if result.success: self.update_device_state_and_schedule(now) for preempted_task in result.data: topic = topics.ACTUATOR_SCHEDULE_RESULT() headers = self.get_headers(preempted_task[0], task_id=preempted_task[1]) headers['type'] = SCHEDULE_ACTION_CANCEL self.publish_json( topic, headers, { 'result': SCHEDULE_CANCEL_PREEMPTED, 'info': '', 'data': { 'agentID': requester, 'taskID': taskID } }) def handle_cancel(self, headers, now): requester = headers.get('requesterID') taskID = headers.get('taskID') result = self._schedule_manager.cancel_task(requester, taskID, now) success = SCHEDULE_RESPONSE_SUCCESS if result.success else SCHEDULE_RESPONSE_FAILURE topic = topics.ACTUATOR_SCHEDULE_RESULT() self.publish_json(topic, headers, { 'result': success, 'info': result.info_string, 'data': {} }) if result.success: self.update_device_state_and_schedule(now) def get_headers(self, requester, time=None, task_id=None): headers = {} if time is not None: headers['time'] = time else: headers = {'time': str(datetime.datetime.utcnow())} if requester is not None: headers['requesterID'] = requester if task_id is not None: headers['taskID'] = task_id return headers def process_smap_request_result(self, request, point, requester): headers = self.get_headers(requester) try: request.raise_for_status() results = request.json() readings = results['Readings'] reading = readings[0][1] self.push_result_topic_pair(VALUE_RESPONSE_PREFIX, point, headers, reading) except requests.exceptions.HTTPError as ex: error = { 'type': ex.__class__.__name__, 'value': str(request.text) } self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) except (ValueError, IndexError, KeyError, requests.exceptions.ConnectionError) as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) def push_result_topic_pair(self, prefix, point, headers, *args): topic = normtopic('/'.join([prefix, point])) self.publish_json(topic, headers, *args)
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)