def handle_get(self, peer, sender, bus, topic, headers, message): """ Requests up to date value of a point. To request a value publish a message to the following topic: ``devices/actuators/get/<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. """ point = topic.replace(topics.ACTUATOR_GET() + '/', '', 1) requester = headers.get('requesterID') headers = self._get_headers(requester) try: value = self.get_point(point) self._push_result_topic_pair(VALUE_RESPONSE_PREFIX, point, headers, value) except RemoteError as ex: self._handle_remote_error(ex, point, headers) except StandardError as ex: self._handle_standard_error(ex, point, headers)
def handle_get(self, peer, sender, bus, topic, headers, message): point = topic.replace(topics.ACTUATOR_GET() + '/', '', 1) requester = headers.get('requesterID') headers = self.get_headers(requester) try: value = self.get_point(point) self.push_result_topic_pair(VALUE_RESPONSE_PREFIX, point, headers, value) except StandardError as ex: error = {'type': ex.__class__.__name__, 'value': str(ex)} self.push_result_topic_pair(ERROR_RESPONSE_PREFIX, point, headers, error)
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 _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)
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 _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