def _on_error_result(self, topic, headers, message, match): """ERROR result""" point = match.group(1) msg = jsonapi.loads(message[0]) point = match.group(1) today = datetime.datetime.now().date() for key, schedule in self.device_schedule.items(): if schedule["date"] == today: schedule_start = schedule["schedule_start"] schedule_end = schedule["schedule_end"] task_id = key break self._log.info('Error Results: ' + str(point) + ' ' + str(msg)) if msg.get('type', 0) == 'LockError': headers = { 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': task_id, 'priority': 'HIGH' } self.task_timer = self.periodic_timer( 20, self.publish_json, topics.ACTUATOR_SCHEDULE_REQUEST(), headers, [[ "{campus}/{building}/{unit}".format(**rtu_path), str(schedule_start), str(schedule_end) ]]) elif self.error_handler is not None: self._log.info('Running error handler') self.error_handler()
def command_error_handler(self, error_type): ''' Handle actuator error for attempted set of RTU actuation point ''' if error_type.lower() =='lockerror': headers1 = { 'type': 'CANCEL_SCHEDULE', 'requesterID': self._agent.task_id, 'taskID': self._agent.task_id } headers2 = { 'type': 'NEW_SCHEDULE', 'requesterID': self._agent.task_id, 'taskID': self._agent.task_id, 'priority': 'LOW_PREEMPT' } self._log.debug('Handling Actuator set/get error') self._agent.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers1,[["{campus}/{building}/{unit}".format(**self._agent.rtu_path)]]) self._agent.sleep(15) self._agent.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers2,[["{campus}/{building}/{unit}".format(**self._agent.rtu_path),self._agent.start,self._agent.end]]) return
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 schedule_task(self): '''Schedule access to modify device controls.''' _log.debug('Schedule Device Access') headers = { 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': actuator_id, 'priority': 'LOW' } start = datetime.now() end = start + td(seconds=30) start = str(start) end = str(end) self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers, [["{campus}/{building}/{unit}".format(**device), start, end]])
def actuator_request(self, results): """Make actuaor request for modification of device set points.""" _now = dt.now() str_now = _now.strftime(DATE_FORMAT) _end = _now + td(minutes=1) str_end = _end.strftime(DATE_FORMAT) for _device in command_devices: actuation_device = base_actuator_path(unit=_device, point='') schedule_request = [[actuation_device, str_now, str_end]] # # try: # result = self.vip.rpc.call('platform.actuator', # 'request_new_schedule', # agent_id, _device, 'HIGH', # schedule_request).get(timeout=4) # except RemoteError as ex: # _log.warning("Failed to schedule device {} (RemoteError): {}".format(_device, str(ex))) # request_error = True # # if result['result'] == 'FAILURE': # _log.warn('Failed to schedule device (unavailable) ' + _device) # request_error = True # else: # request_error = False # _log.debug('@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@') # _log.debug(str(result)) # _log.debug(str_now) # _log.debug(str_end) headers = { 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': actuator_id, 'priority': 'HIGH' } device_path = "{campus}/{building}/".format(**campus_building) self.vip.pubsub.publish( peer='pubsub', topic=topics.ACTUATOR_SCHEDULE_REQUEST(), headers=headers, message=[[device_path + _device, str_now, str_end]]) # return results, False
def on_set_error(self, topic, headers, message, match): '''Setting of point on device failed, log failure message.''' print 'Set ERROR' msg = jsonapi.loads(message[0]) msg = msg['type'] _log.debug('Actuator Error: ({}, {}, {})'.format( msg, self.current_key, self.commands[self.current_key])) self.keys.remove(self.current_key) if self.keys: self.command_equip() else: headers = { 'type': 'CANCEL_SCHEDULE', 'requesterID': agent_id, 'taskID': actuator_id } self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers, {}) self.keys = None
def scheduled_task(self): ''' Schedule re-occuring diagnostics ''' _log.debug('Schedule Dx') headers = { 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': agent_id, 'priority': 'LOW_PREEMPT' } min_run_hour = math.floor(min_run_window / 3600) min_run_minute = int((min_run_window / 3600 - min_run_hour) * 60) self.start = datetime.datetime.now().replace(hour=start_hour, minute=start_minute) self.end = self.start + datetime.timedelta(hours=2, minutes=30) run_start = self.end - datetime.datetime.now() required_diagnostic_time = datetime.timedelta( hours=min_run_hour, minutes=min_run_minute) if run_start < required_diagnostic_time: self.start = self.start + datetime.timedelta(days=1) self.end = self.start + datetime.timedelta(hours=2, minutes=30) sched_time = datetime.datetime.now() + datetime.timedelta( days=day_run_interval + 1) sched_time = sched_time.replace(hour=0, minute=1) else: sched_time = datetime.datetime.now() + datetime.timedelta( days=day_run_interval) self.start = str(self.start) self.end = str(self.end) self.task_timer = self.periodic_timer( 60, self.publish_json, topics.ACTUATOR_SCHEDULE_REQUEST(), headers, [[ "{campus}/{building}/{unit}".format(**rtu_path), self.start, self.end ]]) event = sched.Event(self.scheduled_task) self.next = self.schedule(sched_time, event)
def on_set_result(self, topic, headers, message, match): '''Setting of point on device was successful.''' print('Set Success: {point} - {value}'.format( point=self.current_key, value=str(self.commands[self.current_key]))) _log.debug('set_point({}, {})'.format( self.current_key, self.commands[self.current_key])) self.keys.remove(self.current_key) if self.keys: self.command_equip() else: print 'Done with Commands - Release device lock.' headers = { 'type': 'CANCEL_SCHEDULE', 'requesterID': agent_id, 'taskID': actuator_id } self.publish_json(topics.ACTUATOR_SCHEDULE_REQUEST(), headers, {}) self.keys = None
def schedule_result(self, topic, headers, message, match): msg = jsonapi.loads(message[0]) self._log.info('Schedule Request Acknowledged') self.task_timer.cancel() task_id = headers.get('taskID', 0) response_type = headers.get('type', 0) schedule_start = self.device_schedule[task_id]["schedule_start"] event_start = schedule_start + datetime.timedelta(minutes=1) schedule_end = self.device_schedule[task_id]["schedule_end"] e_start = self.device_schedule[task_id]["event_start"] e_end = self.device_schedule[task_id]["event_end"] if response_type == 'NEW_SCHEDULE' and self.error_handler == None: if msg.get('result', 0) == 'SUCCESS': event = sched.Event(self.pre_cool_setup, args=[e_start, e_end]) self.schedule(event_start, event) self.all_scheduled_events[e_start] = event elif msg.get('result', 0) == 'FAILURE' and schedule_start < schedule_end: schedule_start = schedule_start + datetime.timedelta( minutes=10) headers = { 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': task_id, 'priority': 'High' } self.task_timer = self.periodic_timer( 20, self.publish_json, topics.ACTUATOR_SCHEDULE_REQUEST(), headers, [[ "{campus}/{building}/{unit}".format(**rtu_path), str(schedule_start), schedule_end ]]) elif schedule_start >= schedule_end: return if self.error_handler is not None: self.error_handler()
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 _on_dr_event(self, topic, headers, message, match): if self.state == 'STARTUP': self._log.info('DR event ignored because of startup.') return """handle openADR events""" msg = jsonapi.loads(message[0]) self._log.info('EVENT Received: ' + str(msg)) e_id = msg['id'] e_status = msg['status'] e_start = msg['start_at'] task_id = msg['id'] #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) dr_date = e_start.date() current_datetime = datetime.datetime.now() if current_datetime > e_end: self._log.info('Too Late Event is Over') return if e_status == 'cancelled': if e_start in self.all_scheduled_events: self._log.info('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: if e_start.date() == item.date(): if e_start.time() != item.time(): self._log.info('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(): self._log.info("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 schedule_start = e_start - datetime.timedelta( hours=max_precool_hours) schedule_end = e_end + datetime.timedelta( seconds=self.restore_window) schedule_end = schedule_end + datetime.timedelta(minutes=10) self.device_schedule[task_id] = { "date": dr_date, "schedule_start": schedule_start, "schedule_end": schedule_end, "event_start": e_start, "event_end": e_end } headers = { 'type': 'NEW_SCHEDULE', 'requesterID': agent_id, 'taskID': task_id, 'priority': 'HIGH' } self.task_timer = self.periodic_timer( 20, self.publish_json, topics.ACTUATOR_SCHEDULE_REQUEST(), headers, [[ "{campus}/{building}/{unit}".format(**rtu_path), str(schedule_start), str(schedule_end) ]])
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)
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)
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
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
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)