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 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 actuator_set(self, results): """Set point on device.""" for device, point_value_dict in results.devices.items(): for point, value in point_value_dict.items(): point_path = base_actuator_path(unit=device, point=point) try: # result = self.vip.rpc.call('platform.actuator', 'set_point', # agent_id, point_path, # new_value).get(timeout=4) headers = { 'Content-Type': 'text/plain', 'requesterID': agent_id, } self.vip.pubsub.publish(peer="pubsub", topic=topics.ACTUATOR_SET( point=point, unit=device, **campus_building), headers=headers, message=str(value)) _log.debug("Set point {} to {}".format( point_path, value)) except RemoteError as ex: _log.warning("Failed to set {} to {}: {}".format( point_path, value, str(ex))) continue for _device in command_devices: for point, new_value in results.commands.items(): point_path = base_actuator_path(unit=_device, point=point) try: # result = self.vip.rpc.call('platform.actuator', 'set_point', # agent_id, point_path, # new_value).get(timeout=4) headers = { 'Content-Type': 'text/plain', 'requesterID': agent_id, } self.vip.pubsub.publish(peer="pubsub", topic=topics.ACTUATOR_SET( point=point, unit=_device, **campus_building), headers=headers, message=str(new_value)) _log.debug("Set point {} to {}".format( point_path, new_value)) except RemoteError as ex: _log.warning("Failed to set {} to {}: {}".format( point_path, new_value, str(ex))) continue
def handle_set(self, peer, sender, bus, topic, headers, message): point = topic.replace(topics.ACTUATOR_SET() + '/', '', 1) requester = headers.get('requesterID') headers = self.get_headers(requester) if not message: error = {'type': 'ValueError', 'value': 'missing argument'} _log.debug('ValueError: ' + str(error)) self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) return else: try: message = 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 _log.debug('ValueError: ' + message) error = {'type': 'ValueError', 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) return try: self.set_point(requester, point, message) except StandardError as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) _log.debug('Actuator Agent Error: ' + str(error))
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.DEVICES_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 command_equip(self): '''Execute commands on configured device.''' self.current_key = self.keys[0] value = self.commands[self.current_key] headers = { 'Content-Type': 'text/plain', 'requesterID': agent_id, } self.publish(topics.ACTUATOR_SET(point=self.current_key, **device), headers, str(value))
def actuate(self, topic, headers, message, match): '''Match the announce for our fake device with our ID Then take an action. Note, this command will fail since there is no actual device''' headers = { 'requesterID': agent_id, } self.publish_json(topics.ACTUATOR_SET(campus='campus', building='building',unit='unit', point='point'), headers, str(0.0))
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_restore_event(self, csp, hsp): self.state = 'RESTORE' self._log.info('Restore: Begin restoring normal operations') self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path), self.headers, str(csp)) self.publish(topics.ACTUATOR_SET(point=heating_stpt, **rtu_path), self.headers, str(hsp)) #heating self.publish( topics.ACTUATOR_SET(point=cooling_fan_sp1, **rtu_path), self.headers, str(self.normal_firststage_fanspeed)) self.publish( topics.ACTUATOR_SET(point=cooling_fan_sp2, **rtu_path), self.headers, str(self.normal_secondstage_fanspeed)) self.publish( topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path), self.headers, str(self.normal_damper_stpt)) self.publish( topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path), self.headers, str(self.default_cooling_stage_differential)) def backup_run(): self.start_restore_event(csp, hsp) self.error_handler = None self.error_handler = backup_run
def modify_temp_set_point(self, csp, hsp): self.publish(topics.ACTUATOR_SET(point=volttron_flag, **rtu_path), self.headers, str(3.0)) self.publish( topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path), self.headers, str(self.normal_damper_stpt)) self.publish( topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path), self.headers, str(self.default_cooling_stage_differential)) self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path), self.headers, str(csp)) self.publish(topics.ACTUATOR_SET(point=heating_stpt, **rtu_path), self.headers, str(hsp)) if self.pre_cool_idle == True: self.pre_cool_idle = False def backup_run(): self.modify_temp_set_point(csp, hsp) self.error_handler = None self.error_handler = backup_run
def on_start(self, sender, **kwargs): self.setup_schedule() self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_GET(), callback=self.handle_get) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_SET(), callback=self.handle_set) self.vip.pubsub.subscribe( peer='pubsub', prefix=topics.ACTUATOR_SCHEDULE_REQUEST(), callback=self.handle_schedule_request)
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 command_equip(self): '''Execute commands on configured device.''' for key in self.keys: self.current_key = key value = self.commands[self.current_key] headers = { 'Content-Type': 'text/plain', 'requesterID': agent_id, } device.update({'unit':''}) self.vip.pubsub.publish(peer="pubsub", topic=topics.ACTUATOR_SET(point=self.current_key, **device), headers=headers, message=str(value))
def handle_set(self, peer, sender, bus, topic, headers, message): """ Set the value of a point. To set a value publish a message to the following topic: ``devices/actuators/set/<device path>/<actuation point>`` with the fallowing header: .. code-block:: python { 'requesterID': <Agent ID> } The ActuatorAgent will reply on the **value** topic for the actuator: ``devices/actuators/value/<full device path>/<actuation point>`` with the message set to the value the point. Errors will be published on ``devices/actuators/error/<full device path>/<actuation point>`` with the same header as the request. """ if sender == 'pubsub.compat': message = compat.unpack_legacy_message(headers, message) point = topic.replace(topics.ACTUATOR_SET() + '/', '', 1) requester = headers.get('requesterID') headers = self._get_headers(requester) if not message: error = {'type': 'ValueError', 'value': 'missing argument'} _log.debug('ValueError: ' + str(error)) self._push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) return try: self.set_point(requester, point, message) except RemoteError as ex: self._handle_remote_error(ex, point, headers) except StandardError as ex: self._handle_standard_error(ex, point, headers)
def actuate(self, peer, sender, bus, topic, headers, message): print ("response:",topic,headers,message) if headers[headers_mod.REQUESTER_ID] != agent_id: return '''Match the announce for our fake device with our ID Then take an action. Note, this command will fail since there is no actual device''' headers = { 'requesterID': agent_id, } self.vip.pubsub.publish( 'pubsub', topics.ACTUATOR_SET(campus='campus', building='building',unit='unit', point='point'), headers, str(0.0))
def on_new_data(self, topic, headers, message, match): data = jsonapi.loads(message[0]) #Check override status if int(data["VoltronPBStatus"]) == 1: if self.is_running: _log.debug("User override is initiated...") headers = { 'Content-Type': 'text/plain', 'requesterID': agent_id, } self.publish( topics.ACTUATOR_SET(point="VoltronFlag", **rtu_path), headers, str(0.0)) self.cancel_greenlet() else: self.data_queue.notify_all(data)
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 _on_start(self, sender, **kwargs): self._setup_schedule() self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_GET(), callback=self.handle_get) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_SET(), callback=self.handle_set) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_SCHEDULE_REQUEST(), callback=self.handle_schedule_request) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_REVERT_POINT(), callback=self.handle_revert_point) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_REVERT_DEVICE(), callback=self.handle_revert_device) self.core.periodic(self.heartbeat_interval, self._heart_beat)
def start_dr_event(self): self.state = 'DR_EVENT' self.publish(topics.ACTUATOR_SET(point=volttron_flag, **rtu_path), self.headers, str(3.0)) self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path), self.headers, str(self.csp_cpp)) new_fan_speed = self.normal_firststage_fanspeed - ( self.normal_firststage_fanspeed * fan_reduction) new_fan_speed = max(new_fan_speed, 0) self.publish( topics.ACTUATOR_SET(point=cooling_fan_sp1, **rtu_path), self.headers, str(new_fan_speed)) new_fan_speed = self.normal_secondstage_fanspeed - ( self.normal_firststage_fanspeed * fan_reduction) new_fan_speed = max(new_fan_speed, 0) self.publish( topics.ACTUATOR_SET(point=cooling_fan_sp2, **rtu_path), self.headers, str(new_fan_speed)) self.publish( topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path), self.headers, str(damper_cpp)) self.publish( topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path), self.headers, str(cooling_stage_differential)) mytime = int(time.time()) content = { "Demand Response Event": { "Readings": [[mytime, 1.0]], "Units": "TU", "data_type": "double" } } self.publish(self.smap_path, self.headers, jsonapi.dumps(content)) def backup_run(): self.start_dr_event() self.error_handler = None self.error_handler = backup_run
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(): _log.debug('update_heartbeat') value[0] = not value[0] payload = {'state': str(int(value[0]))} try: _log.debug('About to publish actuation') r = requests.put(request_url, params=payload, timeout=connection_timeout) self.process_smap_request_result(r, publish_topic, None) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as ex: print "Warning: smap driver not running." _log.error("Connection error: "+str(ex)) 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): _log.debug("update_device_state_and schedule") 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): _log.debug("_get_adjusted_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, timeout=connection_timeout) self.process_smap_request_result(r, point, requester) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) 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): _log.debug('handle_set: {topic},{headers}, {message}'. format(topic=topic, headers=headers, message=message)) 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'} _log.debug('ValueError: '+str(error)) 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 _log.debug('ValueError: '+message) 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, timeout=connection_timeout) self.process_smap_request_result(r, point, requester) except (requests.exceptions.ConnectionError, requests.exceptions.Timeout) as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) _log.debug('ConnectionError: '+str(error)) else: error = {'type': 'LockError', 'value': 'does not have this lock'} _log.debug('LockError: '+str(error)) self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) def check_lock(self, device, requester): _log.debug('check_lock: {device}, {requester}'.format(device=device, requester=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() _log.debug('handle_schedule_request: {topic}, {headers}, {message}'. format(topic=topic, headers=str(headers), message=str(message))) if request_type == SCHEDULE_ACTION_NEW: self.handle_new(headers, message, now) elif request_type == SCHEDULE_ACTION_CANCEL: self.handle_cancel(headers, now) else: _log.debug('handle-schedule_request, invalid request type') 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') _log.debug("Got new schedule request: {headers}, {message}". format(headers = str(headers), message = str(message))) 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. _log.error('bad request: {request}, {error}'.format(request=requests, error=str(ex))) 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): _log.debug('Start of process_smap: \n{request}, \n{point}, \n{requester}'. format(request=request,point=point,requester=requester)) headers = self.get_headers(requester) try: request.raise_for_status() results = request.json() readings = results['Readings'] _log.debug('Readings: {readings}'.format(readings=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)} _log.error('process_smap HTTPError: '+str(error)) 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)} _log.error('process_smap RequestError: '+str(error)) self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error) _log.debug('End of process_smap: \n{request}, \n{point}, \n{requester}'. format(request=request,point=point,requester=requester)) def push_result_topic_pair(self, prefix, point, headers, *args): topic = normtopic('/'.join([prefix, point])) self.publish_json(topic, headers, *args)
def configure(self, config_name, action, contents): config = self.default_config.copy() config.update(contents) _log.debug("Configuring Actuator Agent") try: driver_vip_identity = str(config["driver_vip_identity"]) schedule_publish_interval = float( config["schedule_publish_interval"]) heartbeat_interval = float(config["heartbeat_interval"]) preempt_grace_time = float(config["preempt_grace_time"]) except ValueError as e: _log.error("ERROR PROCESSING CONFIGURATION: {}".format(e)) #TODO: set a health status for the agent return self.driver_vip_identity = driver_vip_identity self.schedule_publish_interval = schedule_publish_interval _log.debug("MasterDriver VIP IDENTITY: {}".format( self.driver_vip_identity)) _log.debug("Schedule publish interval: {}".format( self.schedule_publish_interval)) #Only restart the heartbeat if it changes. if (self.heartbeat_interval != heartbeat_interval or action == "NEW" or self.heartbeat_greenlet is None): if self.heartbeat_greenlet is not None: self.heartbeat_greenlet.kill() self.heartbeat_interval = heartbeat_interval self.heartbeat_greenlet = self.core.periodic( self.heartbeat_interval, self._heart_beat) _log.debug("Heartbeat interval: {}".format(self.heartbeat_interval)) _log.debug("Preemption grace period: {}".format(preempt_grace_time)) if self._schedule_manager is None: try: config = self.default_config.copy() config.update(contents) state_string = self.vip.config.get(self.schedule_state_file) preempt_grace_time = float(config["preempt_grace_time"]) self._setup_schedule(preempt_grace_time, state_string) except KeyError: state_string = None else: self._schedule_manager.set_grace_period(preempt_grace_time) if not self.subscriptions_setup and self._schedule_manager is not None: #Do this after the scheduler is setup. self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_GET(), callback=self.handle_get) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_SET(), callback=self.handle_set) self.vip.pubsub.subscribe( peer='pubsub', prefix=topics.ACTUATOR_SCHEDULE_REQUEST(), callback=self.handle_schedule_request) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_REVERT_POINT(), callback=self.handle_revert_point) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_REVERT_DEVICE(), callback=self.handle_revert_device) self.subscriptions_setup = True
def _request_new_schedule(self, sender, task_id, priority, requests): now = self.volttime topic = topics.ACTUATOR_SCHEDULE_RESULT() headers = self._get_headers(sender, task_id=task_id) headers['type'] = SCHEDULE_ACTION_NEW local_tz = get_localzone() try: if requests and isinstance(requests[0], basestring): requests = [requests] tmp_requests = requests requests = [] for r in tmp_requests: device, start, end = r device = device.strip('/') start = utils.parse_timestamp_string(start) end = utils.parse_timestamp_string(end) if start.tzinfo is None: start = local_tz.localize(start) if end.tzinfo is None: end = local_tz.localize(end) requests.append([device, start, end]) except StandardError as ex: return self._handle_unknown_schedule_error(ex, headers, requests) _log.debug("Got new schedule request: {}, {}, {}, {}".format( sender, task_id, priority, requests)) if self._schedule_manager is None: #config = self.default_config.copy() # config.update(contents) #state_string = self.vip.config.get(self.schedule_state_file) #preempt_grace_time = float(config["preempt_grace_time"]) #self._setup_schedule(preempt_grace_time, state_string) try: config = self.default_config.copy() # config.update(contents) state_string = self.vip.config.get(self.schedule_state_file) preempt_grace_time = float(config["preempt_grace_time"]) self._setup_schedule(preempt_grace_time, state_string) except KeyError as e: state_string = None print "ERROR :::: ", e print "This is STILL being NONE" if not self.subscriptions_setup and self._schedule_manager is not None: #Do this after the scheduler is setup. self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_GET(), callback=self.handle_get) self.vip.pubsub.subscribe(peer='pubsub', prefix=topics.ACTUATOR_SET(), callback=self.handle_set) self.vip.pubsub.subscribe( peer='pubsub', prefix=topics.ACTUATOR_SCHEDULE_REQUEST(), callback=self.handle_schedule_request) self.vip.pubsub.subscribe( peer='pubsub', prefix=topics.ACTUATOR_REVERT_POINT(), callback=self.handle_revert_point) self.vip.pubsub.subscribe( peer='pubsub', prefix=topics.ACTUATOR_REVERT_DEVICE(), callback=self.handle_revert_device) self.subscriptions_setup = True result = self._schedule_manager.request_slots(sender, task_id, requests, priority, now) success = SCHEDULE_RESPONSE_SUCCESS if result.success else \ SCHEDULE_RESPONSE_FAILURE # Dealing with success and other first world problems. if result.success: self._update_device_state_and_schedule(now) for preempted_task in result.data: preempt_headers = self._get_headers(preempted_task[0], task_id=preempted_task[1]) preempt_headers['type'] = SCHEDULE_ACTION_CANCEL self.vip.pubsub.publish('pubsub', topic, headers=preempt_headers, message={ 'result': SCHEDULE_CANCEL_PREEMPTED, 'info': '', 'data': { 'agentID': sender, 'taskID': task_id } }) # If we are successful we do something else with the real result data data = result.data if not result.success else {} results = {'result': success, 'data': data, 'info': result.info_string} self.vip.pubsub.publish('pubsub', topic, headers=headers, message=results) return results
class Agent(PublishMixin, BaseAgent): '''Simulate real device. Publish csv data to message bus. Configuration consists of csv file and device path (campus/building/device) ''' def __init__(self, **kwargs): super(Agent, self).__init__(**kwargs) path = os.path.abspath(settings.source_file) print path self._src_file_handle = open(path) header_line = self._src_file_handle.readline().strip() self._headers = header_line.split(',') self.end_time = None self.start_time = None self.task_id = None utils.setup_logging() self._log = logging.getLogger(__name__) logging.basicConfig(level=logging.debug, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%m-%d-%y %H:%M:%S') def setup(self): '''This function is called imediately after initialization''' super(Agent, self).setup() self._agent_id = settings.publisherid @periodic(settings.check_4_new_data_time) def publish_data_or_heartbeat(self): published_data = {} now = datetime.datetime.now().isoformat(' ') if not self._src_file_handle.closed: line = self._src_file_handle.readline() line = line.strip() data = line.split(',') if (line): # Create 'all' message for i in xrange(0, len(self._headers)): published_data[self._headers[i]] = data[i] all_data = json.dumps(published_data) print all_data # Pushing out the data self.publish(topics.DEVICES_VALUE(point='all', **rtu_path), {HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now}, all_data) else: self._src_file_handle.close() else: # file is closed -> publish heartbeat self.publish('heartbeat/DataPublisher', { 'AgentID': self._agent_id, HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now, }, now) @matching.match_regex(topics.ACTUATOR_SET() + '/(.+)') def handle_set(self, topic, headers, message, match): print 'set actuator' point = match.group(1) discard1, discard2, discard3, point_name = point.rsplit('/', 4) requester = headers.get('requesterID') headers = self.get_headers(requester) value = jsonapi.loads(message[0]) self.push_result_topic_pair(point_name, headers, value) @matching.match_exact(topics.ACTUATOR_SCHEDULE_REQUEST()) def handle_schedule_request(self, topic, headers, message, match): print 'request received' request_type = headers.get('type') now = datetime.datetime.now() if request_type == SCHEDULE_ACTION_NEW: self.handle_new(headers, message, now) else: self._log.debug('handle-schedule_request, invalid request type') self.publish_json(topics.ACTUATOR_SCHEDULE_RESULT(), headers, { 'result': SCHEDULE_RESPONSE_FAILURE, 'data': {}, 'info': 'INVALID_REQUEST_TYPE' }) def handle_new(self, headers, message, now): print 'handle new' requester = headers.get('requesterID') self.task_id = headers.get('taskID') # priority = headers.get('priority') try: requests = jsonapi.loads(message[0]) requests = requests[0] except (ValueError, IndexError) as ex: # Could be ValueError of JSONDecodeError depending # on if simples json was used. JSONDecodeError # inherits from ValueError # We let the schedule manager tell us this is a bad request. self._log.error('bad request: {request}, {error}' .format(request=requests, error=str(ex))) requests = [] device, start, end = requests self.start_time = parser.parse(start, fuzzy=True) self.end_time = parser.parse(end, fuzzy=True) event = sched.Event(self.announce) self.schedule(self.start_time, event) topic = topics.ACTUATOR_SCHEDULE_RESULT() headers = self.get_headers(requester, task_id=self.task_id) headers['type'] = SCHEDULE_ACTION_NEW self.publish_json(topic, headers, { 'result': 'SUCCESS', 'data': 'NONE', 'info': 'NONE' }) 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 push_result_topic_pair(self, point, headers, *args): self.publish_json(topics.ACTUATOR_VALUE(point=point, **rtu_path), headers, *args) def announce(self): print 'announce' now = datetime.datetime.now() header = self.get_headers(settings.agent_id, time=str(now), task_id=self.task_id) header['window'] = str(self.end_time - now) topic = topics.ACTUATOR_SCHEDULE_ANNOUNCE_RAW.replace('{device}', settings.device) self.publish_json(topic, header, {}) next_time = now + datetime.timedelta(seconds=60)
def cancel_event(self, cancel_type='NORMAL'): if cancel_type == 'OVERRIDE': self.state = 'OVERRIDE' smap_input = 3.0 elif cancel_type != 'UPDATING': self.state = 'CLEANUP' smap_input = 2.0 self.publish(topics.ACTUATOR_SET(point=cooling_stpt, **rtu_path), self.headers, str(self.normal_coolingstpt)) self.publish(topics.ACTUATOR_SET(point=heating_stpt, **rtu_path), self.headers, str(self.normal_heatingstpt)) self.publish( topics.ACTUATOR_SET(point=cooling_fan_sp1, **rtu_path), self.headers, str(self.normal_firststage_fanspeed)) self.publish( topics.ACTUATOR_SET(point=cooling_fan_sp2, **rtu_path), self.headers, str(self.normal_secondstage_fanspeed)) self.publish( topics.ACTUATOR_SET(point=min_damper_stpt, **rtu_path), self.headers, str(self.normal_damper_stpt)) self.publish( topics.ACTUATOR_SET(point=cooling_stage_diff, **rtu_path), self.headers, str(self.default_cooling_stage_differential)) self.publish(topics.ACTUATOR_SET(point=volttron_flag, **rtu_path), self.headers, str(0)) for event in self.currently_running_dr_event_handlers: event.cancel() if cancel_type != 'UPDATING': mytime = int(time.time()) content = { "Demand Response Event": { "Readings": [[mytime, smap_input]], "Units": "TU", "data_type": "double" } } self.publish(self.smap_path, self.headers, jsonapi.dumps(content)) self.device_schedule = {} self.all_scheduled_events = {} self.currently_running_dr_event_handlers = [] def backup_run(): self.cancel_event() self.error_handler = None self.error_handler = backup_run expected_values = { cooling_stpt: self.normal_coolingstpt, heating_stpt: self.normal_heatingstpt, cooling_fan_sp1: self.normal_firststage_fanspeed, cooling_fan_sp2: self.normal_secondstage_fanspeed, min_damper_stpt: self.normal_damper_stpt, cooling_stage_diff: self.default_cooling_stage_differential } EPSILON = 0.5 #allowed difference from expected value def result_handler(point, value): #print "actuator point being handled:", point, value expected_value = expected_values.pop(point, None) if expected_value is not None: diff = abs(expected_value - value) if diff > EPSILON: self._log.info( "Did not get back expected value for: " + str(point)) if not expected_values: self.actuator_handler = None self.error_handler = None self.state = 'IDLE' if not cancel_type == 'OVERRIDE' else 'OVERRIDE' if cancel_type != 'UPDATING': self.actuator_handler = result_handler else: self.actuator_handler = None if cancel_type == 'OVERRIDE': def on_reset(): self.error_handler = None self.state = 'IDLE' today = datetime.datetime.now() reset_time = today + datetime.timedelta(days=1) reset_time = reset_time.replace(hour=0, minute=0, second=0) event = sched.Event(on_reset) self.schedule(reset_time, event)
class Agent(PublishMixin, BaseAgent): '''Simulate real device. Publish csv data to message bus. Configuration consists of csv file and publish topic ''' def __init__(self, **kwargs): '''Initialize data publisher class attributes.''' super(Agent, self).__init__(**kwargs) self._agent_id = conf.get('publisherid') self._src_file_handle = open(path) header_line = self._src_file_handle.readline().strip() self._headers = header_line.split(',') self.end_time = None self.start_time = None self.task_id = None utils.setup_logging() self._log = logging.getLogger(__name__) self.scheduled_event = None logging.basicConfig( level=logging.debug, format='%(asctime)s %(levelname)-8s %(message)s', datefmt='%m-%d-%y %H:%M:%S') self._log.info('DATA PUBLISHER ID is PUBLISHER') def setup(self): '''This function is called immediately after initialization''' super(Agent, self).setup() @periodic(pub_interval) def publish_data_or_heartbeat(self): '''Publish data from file to message bus.''' _data = {} now = datetime.datetime.now().isoformat(' ') if not self._src_file_handle.closed: line = self._src_file_handle.readline() line = line.strip() data = line.split(',') if line: # Create 'all' message for i in xrange(0, len(self._headers)): _data[self._headers[i]] = data[i] if custom_topic: # data_dict = jsonapi.dumps(_data) self.publish_json( custom_topic, {HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now}, _data) return sub_dev = {} device_dict = {} for _k, _v in dev_list.items(): for k, val in _data.items(): if k.startswith(_k): pub_k = k[len(_k):] device_dict.update({pub_k.split('_')[1]: val}) cur_top = (''.join([BASETOPIC, '/', device_path, _k, '/', pub_k.split('_')[1]])) self.publish_json( cur_top, {HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now}, val) # device_dict = jsonapi.dumps(device_dict) if device_dict: self.publish_json( BASETOPIC + '/' + device_path + _k + '/all', {HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now}, device_dict) for sub in dev_list[_k][dev_list[_k].keys()[0]]: for k, val in _data.items(): if k.startswith(sub): pub_k = k[len(sub):] sub_dev.update({pub_k.split('_')[1]: val}) cur_top = (''.join([BASETOPIC, '/', device_path, _k, '/', sub, '/', pub_k.split('_')[1]])) self.publish_json( cur_top, {HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now}, val) # device_dict = jsonapi.dumps(device_dict) if sub_dev: topic = (''.join([BASETOPIC, '/', device_path, _k, '/', sub, '/all'])) self.publish_json( topic, {HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now}, sub_dev) sub_dev = {} device_dict = {} else: self._src_file_handle.close() else: self.publish_json( 'heartbeat/DataPublisher', { 'AgentID': self._agent_id, HEADER_NAME_CONTENT_TYPE: MIME_PLAIN_TEXT, HEADER_NAME_DATE: now, }, now) @matching.match_regex(topics.ACTUATOR_SET() + '/(.+)') def handle_set(self, topic, headers, message, match): '''Respond to ACTUATOR_SET topic.''' self._log.info('set actuator') point = match.group(1) _, _, _, point_name = point.rsplit('/', 4) requester = headers.get('requesterID') headers = self.get_headers(requester) value = jsonapi.loads(message[0]) value_path = topic.replace('actuator/set', '') self.push_result_topic_pair(point_name, headers, value_path, value) @matching.match_exact(topics.ACTUATOR_SCHEDULE_REQUEST()) def handle_schedule_request(self, topic, headers, message, match): '''Handle device schedule request.''' self._log.info('request received') request_type = headers.get('type') now = datetime.datetime.now() if request_type == SCHEDULE_ACTION_NEW: self.handle_new(headers, message) elif request_type == SCHEDULE_ACTION_CANCEL: self.handle_cancel(headers, now) else: self._log.debug('handle-schedule_request, invalid request') self.publish_json(topics.ACTUATOR_SCHEDULE_RESULT(), headers, {'result': SCHEDULE_RESPONSE_FAILURE, 'data': {}, 'info': 'INVALID_REQUEST_TYPE'}) def handle_new(self, headers, message): '''Send schedule request response.''' self._log.info('handle new schedule request') requester = headers.get('requesterID') self.task_id = headers.get('taskID') # priority = headers.get('priority') requests = [] try: requests = jsonapi.loads(message[0]) requests = requests[0] except (ValueError, IndexError) as ex: self._log.info('error, message not in expected format (json)') self._log.error('bad request: {request}, {error}' .format(request=requests, error=str(ex))) requests = [] _, start, end = requests self.start_time = parser.parse(start, fuzzy=True) self.end_time = parser.parse(end, fuzzy=True) event = sched.Event(self.announce, args=[requests, requester]) self.scheduled_event = event self.schedule(self.start_time, event) topic = topics.ACTUATOR_SCHEDULE_RESULT() headers = self.get_headers(requester, task_id=self.task_id) headers['type'] = SCHEDULE_ACTION_NEW self.publish_json(topic, headers, { 'result': 'SUCCESS', 'data': 'NONE', 'info': 'NONE' }) def handle_cancel(self, headers, now): '''Handle schedule request cancel.''' task_id = headers.get('taskID') success = SCHEDULE_RESPONSE_SUCCESS self.scheduled_event.cancel() topic = topics.ACTUATOR_SCHEDULE_RESULT() self.publish_json(topic, headers, {'result': success, 'info': task_id, 'data': {}}) def get_headers(self, requester, time=None, task_id=None): '''Construct headers for responses to schedule requests and device sets. ''' 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 push_result_topic_pair(self, point, headers, value_path, *args): '''Send set success response.''' self.publish_json(topics.ACTUATOR_VALUE(point=point, **value_path), headers, *args) def announce(self, device_path, requester): '''Emulate Actuator agent schedule announce.''' self._log.info('announce') now = datetime.datetime.now() header = self.get_headers(requester, time=str(now), task_id=self.task_id) header['window'] = str(self.end_time - now) topic = topics.ACTUATOR_SCHEDULE_ANNOUNCE_RAW.replace('{device}', device_path) self.publish_json(topic, header, {}) next_time = now + datetime.timedelta(seconds=60) event = sched.Event(self.announce) self.scheduled_event = event self.schedule(next_time, event)