class TestThreadSafeFSM(PyonTestCase): def setUp(self): self.fsm = ThreadSafeFSM(MyStates, MyEvents, MyEvents.ENTER, MyEvents.EXIT) self.fsm.add_handler(MyStates.STATE_1, MyEvents.EVENT_1, self._handler_s1_e1) self.fsm.add_handler(MyStates.STATE_1, MyEvents.EVENT_2, self._handler_s1_e2) self.fsm.add_handler(MyStates.STATE_2, MyEvents.EVENT_1, self._handler_s2_e1) self.fsm.add_handler(MyStates.STATE_2, MyEvents.EVENT_2, self._handler_s2_e2) self.test_value = None self.fsm.start(MyStates.STATE_1) def _handler_s1_e1(self, value, *args, **kwargs): start_time = time.time() gevent.sleep(0) self.test_value = value end_time = time.time() return (None, (start_time, end_time)) def _handler_s1_e2(self, *args, **kwargs): return (None, None) def _handler_s2_e1(self, *args, **kwargs): return (None, None) def _handler_s2_e2(self, *args, **kwargs): return (None, None) def test_state(self): gl1 = gevent.spawn(self.fsm.on_event, MyEvents.EVENT_1, 100) gl2 = gevent.spawn(self.fsm.on_event, MyEvents.EVENT_1, 200) (g1_start, g1_end) = gl1.get() print str(self.test_value) self.assertEquals(self.test_value, 100) (g2_start, g2_end) = gl2.get() print str(self.test_value) self.assertEquals(self.test_value, 200) self.assertGreater(g2_start, g1_end)
class PlatformDriver(object): """ A platform driver handles a particular platform in a platform network. This base class provides a common interface and supporting functionality. """ def __init__(self, pnode, event_callback, create_event_subscriber, destroy_event_subscriber): """ Creates a PlatformDriver instance. @param pnode Root PlatformNode defining the platform network rooted at this platform. @param event_callback Callback to notify platform agent about events generated by this driver. This is captured in self._send_event for this class and subclasses to call as needed. @param create_event_subscriber @param destroy_event_subscriber functions to create/destroy any needed EventSubscriber's, in particular regarding the Managed Endpoint API. """ self._pnode = pnode self._send_event = event_callback self._create_event_subscriber = create_event_subscriber self._destroy_event_subscriber = destroy_event_subscriber self._platform_id = self._pnode.platform_id if self._pnode.parent: self._parent_platform_id = self._pnode.parent.platform_id else: self._parent_platform_id = None self._driver_config = None self._resource_schema = {} # The parameter dictionary. self._param_dict = {} # construct FSM and start it with initial state UNCONFIGURED: self._construct_fsm() self._fsm.start(PlatformDriverState.UNCONFIGURED) def get_platform_driver_event_class(self): """ Returns PlatformDriverEvent in this base class, but this is typically overwritten. """ return PlatformDriverEvent def get_platform_driver_capability_class(self): """ Returns PlatformDriverCapability in this base class, but this is typically overwritten. """ return PlatformDriverCapability def get_resource_capabilities(self, current_state=True, cmd_attrs=False): """ @param current_state @param cmd_attrs If true, the returned commands will be the actual attributes of the associated capability class (or subclass) instead of the associated values. """ res_cmds = self._fsm.get_events(current_state) res_cmds = self._filter_capabilities(res_cmds, cmd_attrs=cmd_attrs) res_params = self._param_dict.keys() return [res_cmds, res_params] def _filter_capabilities(self, events, cmd_attrs=False): """ @param events the events to filter @param cmd_attrs If true, then the actual attributes of the PlatformDriverCapability class (or subclass) are returned instead of the associated values. """ capability_class = self.get_platform_driver_capability_class() event_values = [x for x in events if capability_class.has(x)] if not cmd_attrs: return event_values # map event_values to the actual enum attributes: event_attrs = [] for attr in dir(capability_class): # first two checks below similar to BaseEnum.list() if attr.startswith('__'): continue val = getattr(capability_class, attr) if callable(val): continue if val in event_values: event_attrs.append(attr) return event_attrs def get_resource_state(self, *args, **kwargs): """ Return the current state of the driver. @retval str current driver state. """ return self._fsm.get_current_state() def get_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.GET, *args, **kwargs) def set_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.SET, *args, **kwargs) def execute_resource(self, resource_cmd, *args, **kwargs): """ Platform agent calls this directly to trigger the execution of a resource command. The actual action occurs in execute. """ return self._fsm.on_event(PlatformDriverEvent.EXECUTE, resource_cmd, *args, **kwargs) def validate_driver_configuration(self, driver_config): """ Called by configure so a subclass can perform any needed additional validation of the provided configuration. Nothing is done in this base class. Note that basic validation is done by PlatformAgent prior to creating/configuring the driver. @param driver_config Driver configuration. @raise PlatformDriverException Error in driver configuration. """ pass def configure(self, driver_config): """ Configures this driver. In this base class it basically calls validate_driver_configuration and then assigns the given config to self._driver_config. @param driver_config Driver configuration. """ if log.isEnabledFor(logging.DEBUG): log.debug("%r: configure: %s" % (self._platform_id, str(driver_config))) self.validate_driver_configuration(driver_config) self._driver_config = driver_config #self._param_dict = deepcopy(self._driver_config.get('attributes',{})) def get_config_metadata(self): """ """ return deepcopy(self._resource_schema) def connect(self, recursion=None): """ To be implemented by subclass. Establishes communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect(self, recursion=None): """ To be implemented by subclass. Ends communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def ping(self): """ To be implemented by subclass. Verifies communication with external platform returning "PONG" if this verification completes OK. @retval "PONG" @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def get_attributes(self): """ To be implemented by subclass. Returns the attributes of this platform. This is used by the agent for attribute monitoring purposes. @retval {attr_id: dict, ...} dict indexed by attribute ID with associated properties. attr_id is in particular used during get_attribute_values calls to retrieve values during resource monitoring. The dict for each attribute should contain the following properties: - monitor_cycle_seconds: nominal period in seconds for monitoring @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def get_attribute_values(self, attrs): """ To be implemented by subclass. Returns the values for specific attributes since a given time for each attribute. @param attrs [(attr_id, from_time), ...] desired attributes. from_time Assummed to be in the format basically described by pyon's get_ion_ts function, "a str representing an integer number, the millis in UNIX epoch." @retval {attr_id : [(attrValue, timestamp), ...], ...} dict indexed by attribute name with list of (value, timestamp) pairs. Timestamps in same format as from_time. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def supports_set_operation(self): """ @return True only if the SET operation is supported by this driver. """ return False def set_attribute_values(self, attrs): """ Sets values for writable attributes in this platform. Only called by SET handler when supports_set_operation() returns True. @param attrs [(attr_id, attrValue), ...] List of attribute values @retval {attr_id : [(attrValue, timestamp), ...], ...} dict with a list of (value,timestamp) pairs for each attribute indicated in the input. Returned timestamps indicate the time when the value was set. Each timestamp is "a str representing an integer number, the millis in UNIX epoch" to align with description of pyon's get_ion_ts function. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def execute(self, cmd, *args, **kwargs): """ Executes the given command. Subclasses can override to execute particular commands or delegate to its super class. However, note that this base class raises NotImplementedError. @param cmd command @param args command's args @param kwargs command's kwargs @return result of the execution @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() # pragma: no cover def get(self, *args, **kwargs): """ Gets the values of the requested attributes. Subclasses can override to get particular attributes and delegate to this base implementation to handle common attributes. @param args get's args @param kwargs get's kwargs @return result of the retrieval. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() # pragma: no cover def destroy(self): """ Stops all activity done by the driver. Nothing done in this class. """ pass def get_driver_state(self): """ Returns the current FSM state. """ return self._fsm.get_current_state() ##################################################################### # Supporting method for handling connection lost in CONNECT handlers ##################################################################### def _connection_lost(self, cmd, args, kwargs, exc=None): """ Supporting method to be called by any CONNECTED handler right after detecting that the connection with the external platform device has been lost. It does a regular disconnect() and notifies the agent about the lost connection. Note that the call to disconnect() itself may throw some additional exception very likely caused by the fact that the connection is lost--this exception is just logged out but ignored. All parameters are for logging purposes. @param cmd string indicating the command that was attempted @param args args of the command that was attempted @param kwargs kwargs of the command that was attempted @param exc associated exception (if any), @return (next_state, result) suitable as the return of the FSM handler where the connection lost was detected. The next_state will always be PlatformDriverState.DISCONNECTED. """ log.debug("%r: (LC) _connection_lost: cmd=%s, args=%s, kwargs=%s, exc=%s", self._platform_id, cmd, args, kwargs, exc) result = None try: result = self.disconnect() except Exception as e: # just log a message log.debug("%r: (LC) ignoring exception while calling disconnect upon" " lost connection: %s", self._platform_id, e) # in any case, notify the agent about the lost connection and # transition to DISCONNECTED: self._send_event(AsyncAgentEvent(PlatformAgentEvent.LOST_CONNECTION)) next_state = PlatformDriverState.DISCONNECTED return next_state, result ############################################################## # FSM event handlers. ############################################################## def _common_state_enter(self, *args, **kwargs): """ Common work upon every state entry. """ state = self.get_driver_state() log.debug('%r: driver entering state: %s', self._platform_id, state) self._send_event(StateChangeDriverEvent(state)) def _common_state_exit(self, *args, **kwargs): """ Common work upon every state exit. Nothing done in this base class. """ ############################################################## # UNCONFIGURED event handlers. ############################################################## def _handler_unconfigured_configure(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) driver_config = kwargs.get('driver_config', None) if driver_config is None: raise FSMError('configure: missing driver_config argument') try: result = self.configure(driver_config) next_state = PlatformDriverState.DISCONNECTED except PlatformDriverException as e: result = None next_state = None log.error("%r: Error in platform driver configuration", self._platform_id, e) return next_state, result ############################################################## # DISCONNECTED event handlers. ############################################################## def _handler_disconnected_connect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) recursion = kwargs.get('recursion', None) self.connect(recursion=recursion) result = next_state = PlatformDriverState.CONNECTED return next_state, result def _handler_disconnected_disconnect(self, *args, **kwargs): """ We allow the DISCONNECT event in DISCONNECTED state for convenience, in particular it facilitates the overall handling of the connection_lost event, which is processed by a subsequent call to disconnect from the platform agent. The handler here does nothing. """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) return None, None ########################################################################### # CONNECTED event handlers. # Except for the explicit disconnect and connection_lost handlers, the # CONNECTED handlers (here and in subclasses) should directly catch any # PlatformConnectionException to call _connection_lost. ########################################################################### def _handler_connected_disconnect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) recursion = kwargs.get('recursion', None) result = self.disconnect(recursion=recursion) next_state = PlatformDriverState.DISCONNECTED return next_state, result def _handler_connected_connection_lost(self, *args, **kwargs): """ The connection was lost (as opposed to a normal disconnect request). Here we do the regular disconnect but also notify the platform agent about the lost connection. NOTE: this handler in the FSM is provided in case there is a need to directly trigger the associated transition along with the associated notification to the agent. However, the typical case is that a CONNECTED handler dealing with commands will catch any PlatformConnectionException to call _connection_lost directly. """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) # just use our supporting method: return self._connection_lost(PlatformDriverEvent.CONNECTION_LOST, args, kwargs) def _handler_connected_ping(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) try: result = self.ping() return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.PING, args, kwargs, e) def _handler_connected_get(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) try: result = self.get(*args, **kwargs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.GET, args, kwargs, e) def _handler_connected_set(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) if not self.supports_set_operation(): raise FSMError('Unsupported operation: %s' % PlatformDriverEvent.SET) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('set_attribute_values: missing attrs argument') try: result = self.set_attribute_values(attrs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.SET, args, kwargs, e) def _handler_connected_execute(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) if len(args) == 0: raise FSMError('execute_resource: missing resource_cmd argument') try: result = self.execute(*args, **kwargs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.EXECUTE, args, kwargs, e) ############################################################## # Platform driver FSM setup ############################################################## def _construct_fsm(self, states=PlatformDriverState, events=PlatformDriverEvent, enter_event=PlatformDriverEvent.ENTER, exit_event=PlatformDriverEvent.EXIT): """ Constructs the FSM for the driver. The preparations here are mostly related with the UNCONFIGURED, DISCONNECTED, and CONNECTED state transitions, with some common handlers for the CONNECTED state. Subclasses can override to indicate specific parameters and add new handlers (typically for the CONNECTED state). """ log.debug("%r: constructing base platform driver FSM", self._platform_id) self._fsm = ThreadSafeFSM(states, events, enter_event, exit_event) for state in PlatformDriverState.list(): self._fsm.add_handler(state, enter_event, self._common_state_enter) self._fsm.add_handler(state, exit_event, self._common_state_exit) # UNCONFIGURED state event handlers: self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure) # DISCONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect) self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_disconnected_disconnect) # CONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute)
class PlatformDriver(object): """ A platform driver handles a particular platform in a platform network. This base class provides a common interface and supporting functionality. """ def __init__(self, pnode, event_callback): """ Creates a PlatformDriver instance. @param pnode Root PlatformNode defining the platform network rooted at this platform. @param event_callback Listener of events generated by this driver """ # # NOTE the "pnode" parameter may be not very "standard" but it is the # current convenient mechanism that captures the overall definition # of the corresponding platform (most of which coming from configuration) # assert pnode, "pnode must be given" assert event_callback, "event_callback parameter must be given" self._pnode = pnode self._send_event = event_callback self._platform_id = self._pnode.platform_id if self._pnode.parent: self._parent_platform_id = self._pnode.parent.platform_id else: self._parent_platform_id = None self._platform_attributes = \ dict((a.attr_id, a.defn) for a in self._pnode.attrs.itervalues()) if log.isEnabledFor(logging.DEBUG): log.debug("%r: PlatformDriver constructor called: pnode:\n%s\n" "_platform_attributes=%s", self._platform_id, NetworkUtil._dump_pnode(self._pnode, include_subplatforms=False), self._platform_attributes) self._driver_config = None self._resource_schema = {} # The parameter dictionary. self._param_dict = {} # construct FSM and start it with initial state UNCONFIGURED: self._construct_fsm() self._fsm.start(PlatformDriverState.UNCONFIGURED) def get_resource_capabilities(self, current_state=True): """ """ res_cmds = self._fsm.get_events(current_state) res_cmds = self._filter_capabilities(res_cmds) res_params = self._param_dict.keys() return [res_cmds, res_params] def _filter_capabilities(self, events): """ Typically overwritten in subclass. """ events_out = [x for x in events if PlatformDriverCapability.has(x)] return events_out def get_resource_state(self, *args, **kwargs): """ Return the current state of the driver. @retval str current driver state. """ return self._fsm.get_current_state() def get_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.GET, *args, **kwargs) def set_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.SET, *args, **kwargs) def execute_resource(self, resource_cmd, *args, **kwargs): """ Platform agent calls this directly to trigger the execution of a resource command. The actual action occurs in execute. """ return self._fsm.on_event(PlatformDriverEvent.EXECUTE, resource_cmd, *args, **kwargs) def _get_platform_attributes(self): """ Gets a dict of the attribute definitions in this platform as given at construction time (from pnode parameter). """ return self._platform_attributes def validate_driver_configuration(self, driver_config): """ Called by configure so a subclass can perform any needed additional validation of the provided configuration. Nothing is done in this base class. Note that basic validation is done by PlatformAgent prior to creating/configuring the driver. @param driver_config Driver configuration. @raise PlatformDriverException Error in driver configuration. """ pass def configure(self, driver_config): """ Configures this driver. In this base class it basically calls validate_driver_configuration and then assigns the given config to self._driver_config. @param driver_config Driver configuration. """ if log.isEnabledFor(logging.DEBUG): log.debug("%r: configure: %s" % (self._platform_id, str(driver_config))) self.validate_driver_configuration(driver_config) self._driver_config = driver_config #self._param_dict = deepcopy(self._driver_config.get('attributes',{})) def get_config_metadata(self): """ """ return deepcopy(self._resource_schema) def connect(self): """ To be implemented by subclass. Establishes communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect(self): """ To be implemented by subclass. Ends communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def ping(self): """ To be implemented by subclass. Verifies communication with external platform returning "PONG" if this verification completes OK. @retval "PONG" @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def get_attribute_values(self, attrs): """ To be implemented by subclass. Returns the values for specific attributes since a given time for each attribute. @param attrs [(attrName, from_time), ...] desired attributes. from_time Assummed to be in the format basically described by pyon's get_ion_ts function, "a str representing an integer number, the millis in UNIX epoch." @retval {attrName : [(attrValue, timestamp), ...], ...} dict indexed by attribute name with list of (value, timestamp) pairs. Timestamps in same format as from_time. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def set_attribute_values(self, attrs): """ To be implemented by subclass. Sets values for writable attributes in this platform. @param attrs [(attrName, attrValue), ...] List of attribute values @retval {attrName : [(attrValue, timestamp), ...], ...} dict with a list of (value,timestamp) pairs for each attribute indicated in the input. Returned timestamps indicate the time when the value was set. Each timestamp is "a str representing an integer number, the millis in UNIX epoch" to align with description of pyon's get_ion_ts function. @raise PlatformConnectionException If the connection to the external platform is lost. """ # # TODO Any needed alignment with the instrument case? # raise NotImplementedError() #pragma: no cover def execute(self, cmd, *args, **kwargs): """ Executes the given command. Subclasses can override to execute particular commands or delegate to its super class. However, note that this base class raises NotImplementedError. @param cmd command @param args command's args @param kwargs command's kwargs @return result of the execution @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() # pragma: no cover def get(self, *args, **kwargs): """ Gets the values of the requested attributes. Subclasses can override to get particular attributes and delegate to this base implementation to handle common attributes. @param args get's args @param kwargs get's kwargs @return result of the retrieval. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() # pragma: no cover def destroy(self): """ Stops all activity done by the driver. Nothing done in this class. """ pass def _notify_driver_event(self, driver_event): """ Convenience method for subclasses to send a driver event to corresponding platform agent. @param driver_event a DriverEvent object. """ log.debug("%r: _notify_driver_event: %s", self._platform_id, driver_event) assert isinstance(driver_event, DriverEvent) self._send_event(driver_event) def get_external_checksum(self): """ To be implemented by subclass. Returns the checksum of the external platform associated with this driver. @return SHA1 hash value as string of hexadecimal digits. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def get_driver_state(self): """ Returns the current FSM state. """ return self._fsm.get_current_state() ##################################################################### # Supporting method for handling connection lost in CONNECT handlers ##################################################################### def _connection_lost(self, cmd, args, kwargs, exc=None): """ Supporting method to be called by any CONNECTED handler right after detecting that the connection with the external platform device has been lost. It does a regular disconnect() and notifies the agent about the lost connection. Note that the call to disconnect() itself may throw some additional exception very likely caused by the fact that the connection is lost--this exception is just logged out but ignored. All parameters are for logging purposes. @param cmd string indicating the command that was attempted @param args args of the command that was attempted @param kwargs kwargs of the command that was attempted @param exc associated exception (if any), @return (next_state, result) suitable as the return of the FSM handler where the connection lost was detected. The next_state will always be PlatformDriverState.DISCONNECTED. """ log.debug("%r: (LC) _connection_lost: cmd=%s, args=%s, kwargs=%s, exc=%s", self._platform_id, cmd, args, kwargs, exc) # NOTE I have this import here as a quick way to avoid circular imports # (note that platform_agent also imports elements in this module) # TODO better to move some basic definitions to separate modules. from ion.agents.platform.platform_agent import PlatformAgentEvent result = None try: result = self.disconnect() except Exception as e: # just log a message log.debug("%r: (LC) ignoring exception while calling disconnect upon" " lost connection: %s", self._platform_id, e) # in any case, notify the agent about the lost connection and # transition to DISCONNECTED: self._notify_driver_event(AsyncAgentEvent(PlatformAgentEvent.LOST_CONNECTION)) next_state = PlatformDriverState.DISCONNECTED return next_state, result ############################################################## # FSM event handlers. ############################################################## def _common_state_enter(self, *args, **kwargs): """ Common work upon every state entry. """ state = self.get_driver_state() log.debug('%r: driver entering state: %s', self._platform_id, state) self._notify_driver_event(StateChangeDriverEvent(state)) def _common_state_exit(self, *args, **kwargs): """ Common work upon every state exit. Nothing done in this base class. """ ############################################################## # UNCONFIGURED event handlers. ############################################################## def _handler_unconfigured_configure(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) driver_config = kwargs.get('driver_config', None) if driver_config is None: raise FSMError('configure: missing driver_config argument') try: result = self.configure(driver_config) next_state = PlatformDriverState.DISCONNECTED except PlatformDriverException as e: result = None next_state = None log.error("Error in platform driver configuration", e) return next_state, result ############################################################## # DISCONNECTED event handlers. ############################################################## def _handler_disconnected_connect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) self.connect() result = next_state = PlatformDriverState.CONNECTED return next_state, result def _handler_disconnected_disconnect(self, *args, **kwargs): """ We allow the DISCONNECT event in DISCONNECTED state for convenience, in particular it facilitates the overall handling of the connection_lost event, which is processed by a subsequent call to disconnect from the platform agent. The handler here does nothing. """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) return None, None ########################################################################### # CONNECTED event handlers. # Except for the explicit disconnect and connection_lost handlers, the # CONNECTED handlers (here and in subclasses) should directly catch any # PlatformConnectionException to call _connection_lost. ########################################################################### def _handler_connected_disconnect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.disconnect() next_state = PlatformDriverState.DISCONNECTED return next_state, result def _handler_connected_connection_lost(self, *args, **kwargs): """ The connection was lost (as opposed to a normal disconnect request). Here we do the regular disconnect but also notify the platform agent about the lost connection. NOTE: this handler in the FSM is provided in case there is a need to directly trigger the associated transition along with the associated notification to the agent. However, the typical case is that a CONNECTED handler dealing with commands will catch any PlatformConnectionException to call _connection_lost directly. """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) # just use our supporting method: return self._connection_lost(PlatformDriverEvent.CONNECTION_LOST, args, kwargs) def _handler_connected_ping(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) try: result = self.ping() return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.PING, args, kwargs, e) def _handler_connected_get(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) try: result = self.get(*args, **kwargs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.GET, args, kwargs, e) def _handler_connected_set(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('set_attribute_values: missing attrs argument') try: result = self.set_attribute_values(attrs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.SET, args, kwargs, e) def _handler_connected_execute(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) if len(args) == 0: raise FSMError('execute_resource: missing resource_cmd argument') try: result = self.execute(*args, **kwargs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.EXECUTE, args, kwargs, e) ############################################################## # Platform driver FSM setup ############################################################## def _construct_fsm(self, states=PlatformDriverState, events=PlatformDriverEvent, enter_event=PlatformDriverEvent.ENTER, exit_event=PlatformDriverEvent.EXIT): """ Constructs the FSM for the driver. The preparations here are mostly related with the UNCONFIGURED, DISCONNECTED, and CONNECTED state transitions, with some common handlers for the CONNECTED state. Subclasses can override to indicate specific parameters and add new handlers (typically for the CONNECTED state). """ log.debug("constructing base platform driver FSM") self._fsm = ThreadSafeFSM(states, events, enter_event, exit_event) for state in PlatformDriverState.list(): self._fsm.add_handler(state, enter_event, self._common_state_enter) self._fsm.add_handler(state, exit_event, self._common_state_exit) # UNCONFIGURED state event handlers: self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure) # DISCONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect) self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_disconnected_disconnect) # CONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute)
class PlatformDriver(object): """ A platform driver handles a particular platform in a platform network. """ def __init__(self, pnode, event_callback): """ Creates a PlatformDriver instance. @param pnode Root PlatformNode defining the platform network rooted at this platform. @param event_callback Listener of events generated by this driver """ assert pnode, "pnode must be given" assert event_callback, "event_callback parameter must be given" self._pnode = pnode self._send_event = event_callback self._platform_id = self._pnode.platform_id if self._pnode.parent: self._parent_platform_id = self._pnode.parent.platform_id else: self._parent_platform_id = None self._platform_attributes = \ dict((a.attr_id, a.defn) for a in self._pnode.attrs.itervalues()) if log.isEnabledFor(logging.DEBUG): log.debug("%r: PlatformDriver constructor called: pnode:\n%s\n" "_platform_attributes=%s", self._platform_id, NetworkUtil._dump_pnode(self._pnode, include_subplatforms=False), self._platform_attributes) self._driver_config = None # The parameter dictionary. self._param_dict = {} # construct FSM and start it with initial state UNCONFIGURED: self._construct_fsm() self._fsm.start(PlatformDriverState.UNCONFIGURED) def get_resource_capabilities(self, current_state=True): """ """ res_cmds = self._fsm.get_events(current_state) res_cmds = self._filter_capabilities(res_cmds) res_params = self._param_dict.keys() return [res_cmds, res_params] def _filter_capabilities(self, events): """ """ events_out = [x for x in events if PlatformDriverCapability.has(x)] return events_out def get_resource_state(self, *args, **kwargs): """ Return the current state of the driver. @retval str current driver state. """ return self._fsm.get_current_state() def get_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.GET, *args, **kwargs) def set_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.SET, *args, **kwargs) def execute_resource(self, resource_cmd, *args, **kwargs): """ Platform agent calls this directly to trigger the execution of a resource command. The actual action occurs in _execute. """ return self._fsm.on_event(PlatformDriverEvent.EXECUTE, resource_cmd, *args, **kwargs) def _get_platform_attributes(self): """ Gets a dict of the attribute definitions in this platform as given at construction time (from pnode parameter). """ return self._platform_attributes def _validate_driver_configuration(self, driver_config): """ Called by configure so a subclass can perform any needed additional validation of the provided configuration. Nothing is done in this base class. Note that basic validation is done by PlatformAgent prior to creating/configuring the driver. @param driver_config Driver configuration. @raise PlatformDriverException Error in driver configuration. """ def configure(self, driver_config): """ Configures this driver. It first calls _validate_driver_configuration. @param driver_config Driver configuration. """ if log.isEnabledFor(logging.DEBUG): log.debug("%r: configure: %s" % (self._platform_id, str(driver_config))) self._validate_driver_configuration(driver_config) self._driver_config = driver_config def connect(self): """ To be implemented by subclass. Establishes communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect(self): """ To be implemented by subclass. Ends communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def ping(self): """ To be implemented by subclass. Verifies communication with external platform returning "PONG" if this verification completes OK. @retval "PONG" @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_metadata(self): """ To be implemented by subclass. Returns the metadata associated to the platform. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_attribute_values(self, attrs): """ To be implemented by subclass. Returns the values for specific attributes since a given time for each attribute. @param attrs [(attrName, from_time), ...] desired attributes. from_time Assummed to be in the format basically described by pyon's get_ion_ts function, "a str representing an integer number, the millis in UNIX epoch." @retval {attrName : [(attrValue, timestamp), ...], ...} dict indexed by attribute name with list of (value, timestamp) pairs. Timestamps in same format as from_time. """ raise NotImplementedError() #pragma: no cover def set_attribute_values(self, attrs): """ To be implemented by subclass. Sets values for writable attributes in this platform. @param attrs [(attrName, attrValue), ...] List of attribute values @retval {attrName : [(attrValue, timestamp), ...], ...} dict with a list of (value,timestamp) pairs for each attribute indicated in the input. Returned timestamps indicate the time when the value was set. Each timestamp is "a str representing an integer number, the millis in UNIX epoch" to align with description of pyon's get_ion_ts function. """ # # TODO Any needed alignment with the instrument case? # raise NotImplementedError() #pragma: no cover def _execute(self, cmd, *args, **kwargs): """ Executes the given command. Subclasses can override to execute particular commands or delegate to this base implementation to handle common commands. @param cmd command @return """ if cmd == PlatformDriverEvent.GET_CHECKSUM: result = self.get_checksum() elif cmd == PlatformDriverEvent.GET_PORTS: result = self._get_ports() elif cmd == PlatformDriverEvent.TURN_ON_PORT: result = self.turn_on_port(*args, **kwargs) elif cmd == PlatformDriverEvent.TURN_OFF_PORT: result = self.turn_off_port(*args, **kwargs) elif cmd == PlatformDriverEvent.CONNECT_INSTRUMENT: result = self.connect_instrument(*args, **kwargs) elif cmd == PlatformDriverEvent.DISCONNECT_INSTRUMENT: result = self.disconnect_instrument(*args, **kwargs) elif cmd == PlatformDriverEvent.GET_CONNECTED_INSTRUMENTS: result = self.get_connected_instruments(*args, **kwargs) elif cmd == PlatformDriverEvent.GET_METADATA: result = self.get_metadata() else: # TODO do actual execution result = "!! TODO !!: result of _execute: cmd=%s args=%s kwargs=%s" % ( cmd, str(args), str(kwargs)) return result def _get_ports(self): ports = {} for port_id, port in self._pnode.ports.iteritems(): ports[port_id] = {'network': port.network} log.debug("%r: _get_ports: %s", self._platform_id, ports) return ports def connect_instrument(self, port_id, instrument_id, attributes): """ To be implemented by subclass. Connects an instrument to a port in this platform. @param port_id Port ID @param instrument_id Instrument ID @param attributes Attribute dictionary @retval The resulting configuration for the instrument. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect_instrument(self, port_id, instrument_id): """ To be implemented by subclass. Disconnects an instrument from a port in this platform. @param port_id Port ID @param instrument_id Instrument ID @retval @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_connected_instruments(self, port_id): """ To be implemented by subclass. Retrieves the IDs of the instruments connected to a port. @param port_id Port ID @retval @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def turn_on_port(self, port_id): """ To be implemented by subclass. Turns on a port in this platform. @param port_id Port ID @retval The resulting on/off of the port. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def turn_off_port(self, port_id): """ To be implemented by subclass. Turns off a port in this platform. @param port_id Port ID @retval The resulting on/off of the port. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def destroy(self): """ Stops all activity done by the driver. Nothing done in this class. (previously it stopped resource monitoring). """ def _notify_driver_event(self, driver_event): """ Convenience method for subclasses to send a driver event to corresponding platform agent. @param driver_event a DriverEvent object. """ log.debug("platform driver=%r: notify driver_event=%s", self._platform_id, driver_event) assert isinstance(driver_event, DriverEvent) self._send_event(driver_event) def get_checksum(self): """ To be implemented by subclass. Returns the checksum for this platform. @return SHA1 hash value as string of hexadecimal digits. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_driver_state(self): """ Returns the current FSM state. """ return self._fsm.get_current_state() ############################################################## # FSM event handlers. ############################################################## def _common_state_enter(self, *args, **kwargs): """ Common work upon every state entry. """ state = self.get_driver_state() log.debug('%r: driver entering state: %s', self._platform_id, state) self._notify_driver_event(StateChangeDriverEvent(state)) def _common_state_exit(self, *args, **kwargs): """ Common work upon every state exit. Nothing done in this base class. """ ############################################################## # UNCONFIGURED event handlers. ############################################################## def _handler_unconfigured_configure(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) driver_config = kwargs.get('driver_config', None) if driver_config is None: raise FSMError('configure: missing driver_config argument') try: result = self.configure(driver_config) next_state = PlatformDriverState.DISCONNECTED except PlatformDriverException as e: result = None next_state = None log.error("Error in platform driver configuration", e) return next_state, result ############################################################## # DISCONNECTED event handlers. ############################################################## def _handler_disconnected_connect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.connect() next_state = PlatformDriverState.CONNECTED return next_state, result ############################################################## # CONNECTED event handlers. ############################################################## def _handler_connected_disconnect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.disconnect(*args, **kwargs) next_state = PlatformDriverState.DISCONNECTED return next_state, result def _handler_connected_ping(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = None try: result = self.ping() next_state = None except PlatformConnectionException as e: next_state = PlatformDriverState.DISCONNECTED log.error("Ping failed", e) return next_state, result def _handler_connected_get_metadata(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.get_metadata() next_state = None return next_state, result def _handler_connected_get(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('get_attribute_values: missing attrs argument') result = self.get_attribute_values(attrs) next_state = None return next_state, result def _handler_connected_set(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('set_attribute_values: missing attrs argument') result = self.set_attribute_values(attrs) next_state = None return next_state, result def _handler_connected_execute(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) if len(args) == 0: raise FSMError('execute_resource: missing resource_cmd argument') result = self._execute(*args, **kwargs) next_state = None return next_state, result def _handler_connected_get_ports(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self._get_ports() next_state = None return next_state, result def _handler_connected_connect_instrument(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('connect_instrument: missing port_id argument') instrument_id = kwargs.get('instrument_id', None) if instrument_id is None: raise FSMError('connect_instrument: missing instrument_id argument') attributes = kwargs.get('attributes', None) if attributes is None: raise FSMError('connect_instrument: missing attributes argument') result = self.connect_instrument(port_id, instrument_id, attributes) next_state = None return next_state, result def _handler_disconnected_connect_instrument(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('disconnect_instrument: missing port_id argument') instrument_id = kwargs.get('instrument_id', None) if instrument_id is None: raise FSMError('disconnect_instrument: missing instrument_id argument') result = self.disconnect_instrument(port_id, instrument_id) next_state = None return next_state, result def _handler_connected_get_connected_instruments(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('get_connected_instruments: missing port_id argument') result = self.get_connected_instruments(port_id) next_state = None return next_state, result def _handler_connected_turn_on_port(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('turn_on_port: missing port_id argument') result = self.turn_on_port(port_id) next_state = None return next_state, result def _handler_connected_turn_off_port(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('turn_off_port: missing port_id argument') result = self.turn_off_port(port_id) next_state = None return next_state, result def _handler_connected_get_checksum(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.get_checksum() next_state = None return next_state, result ############################################################## # Platform driver FSM setup ############################################################## def _construct_fsm(self): """ """ log.debug("constructing fsm") self._fsm = ThreadSafeFSM(PlatformDriverState, PlatformDriverEvent, PlatformDriverEvent.ENTER, PlatformDriverEvent.EXIT) for state in PlatformDriverState.list(): self._fsm.add_handler(state, PlatformDriverEvent.ENTER, self._common_state_enter) self._fsm.add_handler(state, PlatformDriverEvent.EXIT, self._common_state_exit) # UNCONFIGURED state event handlers: self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure) # DISCONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect) # CONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_METADATA, self._handler_connected_get_metadata) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_PORTS, self._handler_connected_get_ports) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECT_INSTRUMENT, self._handler_connected_connect_instrument) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT_INSTRUMENT, self._handler_disconnected_connect_instrument) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_CONNECTED_INSTRUMENTS, self._handler_connected_get_connected_instruments) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.TURN_ON_PORT, self._handler_connected_turn_on_port) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.TURN_OFF_PORT, self._handler_connected_turn_off_port) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_CHECKSUM, self._handler_connected_get_checksum)
class PlatformDriver(object): """ A platform driver handles a particular platform in a platform network. This base class provides a common interface and supporting functionality. """ def __init__(self, pnode, event_callback): """ Creates a PlatformDriver instance. @param pnode Root PlatformNode defining the platform network rooted at this platform. @param event_callback Listener of events generated by this driver """ # # NOTE the "pnode" parameter may be not very "standard" but it is the # current convenient mechanism that captures the overall definition # of the corresponding platform (most of which coming from configuration) # assert pnode, "pnode must be given" assert event_callback, "event_callback parameter must be given" self._pnode = pnode self._send_event = event_callback self._platform_id = self._pnode.platform_id if self._pnode.parent: self._parent_platform_id = self._pnode.parent.platform_id else: self._parent_platform_id = None self._platform_attributes = \ dict((a.attr_id, a.defn) for a in self._pnode.attrs.itervalues()) if log.isEnabledFor(logging.DEBUG): log.debug( "%r: PlatformDriver constructor called: pnode:\n%s\n" "_platform_attributes=%s", self._platform_id, NetworkUtil._dump_pnode(self._pnode, include_subplatforms=False), self._platform_attributes) self._driver_config = None self._resource_schema = {} # The parameter dictionary. self._param_dict = {} # construct FSM and start it with initial state UNCONFIGURED: self._construct_fsm() self._fsm.start(PlatformDriverState.UNCONFIGURED) def get_resource_capabilities(self, current_state=True): """ """ res_cmds = self._fsm.get_events(current_state) res_cmds = self._filter_capabilities(res_cmds) res_params = self._param_dict.keys() return [res_cmds, res_params] def _filter_capabilities(self, events): """ Typically overwritten in subclass. """ events_out = [x for x in events if PlatformDriverCapability.has(x)] return events_out def get_resource_state(self, *args, **kwargs): """ Return the current state of the driver. @retval str current driver state. """ return self._fsm.get_current_state() def get_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.GET, *args, **kwargs) def set_resource(self, *args, **kwargs): """ """ return self._fsm.on_event(PlatformDriverEvent.SET, *args, **kwargs) def execute_resource(self, resource_cmd, *args, **kwargs): """ Platform agent calls this directly to trigger the execution of a resource command. The actual action occurs in execute. """ return self._fsm.on_event(PlatformDriverEvent.EXECUTE, resource_cmd, *args, **kwargs) def _get_platform_attributes(self): """ Gets a dict of the attribute definitions in this platform as given at construction time (from pnode parameter). """ return self._platform_attributes def validate_driver_configuration(self, driver_config): """ Called by configure so a subclass can perform any needed additional validation of the provided configuration. Nothing is done in this base class. Note that basic validation is done by PlatformAgent prior to creating/configuring the driver. @param driver_config Driver configuration. @raise PlatformDriverException Error in driver configuration. """ pass def configure(self, driver_config): """ Configures this driver. In this base class it basically calls validate_driver_configuration and then assigns the given config to self._driver_config. @param driver_config Driver configuration. """ if log.isEnabledFor(logging.DEBUG): log.debug("%r: configure: %s" % (self._platform_id, str(driver_config))) self.validate_driver_configuration(driver_config) self._driver_config = driver_config #self._param_dict = deepcopy(self._driver_config.get('attributes',{})) def get_config_metadata(self): """ """ return deepcopy(self._resource_schema) def connect(self): """ To be implemented by subclass. Establishes communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect(self): """ To be implemented by subclass. Ends communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def ping(self): """ To be implemented by subclass. Verifies communication with external platform returning "PONG" if this verification completes OK. @retval "PONG" @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def get_attribute_values(self, attrs): """ To be implemented by subclass. Returns the values for specific attributes since a given time for each attribute. @param attrs [(attrName, from_time), ...] desired attributes. from_time Assummed to be in the format basically described by pyon's get_ion_ts function, "a str representing an integer number, the millis in UNIX epoch." @retval {attrName : [(attrValue, timestamp), ...], ...} dict indexed by attribute name with list of (value, timestamp) pairs. Timestamps in same format as from_time. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def set_attribute_values(self, attrs): """ To be implemented by subclass. Sets values for writable attributes in this platform. @param attrs [(attrName, attrValue), ...] List of attribute values @retval {attrName : [(attrValue, timestamp), ...], ...} dict with a list of (value,timestamp) pairs for each attribute indicated in the input. Returned timestamps indicate the time when the value was set. Each timestamp is "a str representing an integer number, the millis in UNIX epoch" to align with description of pyon's get_ion_ts function. @raise PlatformConnectionException If the connection to the external platform is lost. """ # # TODO Any needed alignment with the instrument case? # raise NotImplementedError() #pragma: no cover def execute(self, cmd, *args, **kwargs): """ Executes the given command. Subclasses can override to execute particular commands or delegate to its super class. However, note that this base class raises NotImplementedError. @param cmd command @param args command's args @param kwargs command's kwargs @return result of the execution @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() # pragma: no cover def get(self, *args, **kwargs): """ Gets the values of the requested attributes. Subclasses can override to get particular attributes and delegate to this base implementation to handle common attributes. @param args get's args @param kwargs get's kwargs @return result of the retrieval. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() # pragma: no cover def destroy(self): """ Stops all activity done by the driver. Nothing done in this class. """ pass def _notify_driver_event(self, driver_event): """ Convenience method for subclasses to send a driver event to corresponding platform agent. @param driver_event a DriverEvent object. """ log.debug("%r: _notify_driver_event: %s", self._platform_id, driver_event) assert isinstance(driver_event, DriverEvent) self._send_event(driver_event) def get_external_checksum(self): """ To be implemented by subclass. Returns the checksum of the external platform associated with this driver. @return SHA1 hash value as string of hexadecimal digits. @raise PlatformConnectionException If the connection to the external platform is lost. """ raise NotImplementedError() #pragma: no cover def get_driver_state(self): """ Returns the current FSM state. """ return self._fsm.get_current_state() ##################################################################### # Supporting method for handling connection lost in CONNECT handlers ##################################################################### def _connection_lost(self, cmd, args, kwargs, exc=None): """ Supporting method to be called by any CONNECTED handler right after detecting that the connection with the external platform device has been lost. It does a regular disconnect() and notifies the agent about the lost connection. Note that the call to disconnect() itself may throw some additional exception very likely caused by the fact that the connection is lost--this exception is just logged out but ignored. All parameters are for logging purposes. @param cmd string indicating the command that was attempted @param args args of the command that was attempted @param kwargs kwargs of the command that was attempted @param exc associated exception (if any), @return (next_state, result) suitable as the return of the FSM handler where the connection lost was detected. The next_state will always be PlatformDriverState.DISCONNECTED. """ log.debug( "%r: (LC) _connection_lost: cmd=%s, args=%s, kwargs=%s, exc=%s", self._platform_id, cmd, args, kwargs, exc) # NOTE I have this import here as a quick way to avoid circular imports # (note that platform_agent also imports elements in this module) # TODO better to move some basic definitions to separate modules. from ion.agents.platform.platform_agent import PlatformAgentEvent result = None try: result = self.disconnect() except Exception as e: # just log a message log.debug( "%r: (LC) ignoring exception while calling disconnect upon" " lost connection: %s", self._platform_id, e) # in any case, notify the agent about the lost connection and # transition to DISCONNECTED: self._notify_driver_event( AsyncAgentEvent(PlatformAgentEvent.LOST_CONNECTION)) next_state = PlatformDriverState.DISCONNECTED return next_state, result ############################################################## # FSM event handlers. ############################################################## def _common_state_enter(self, *args, **kwargs): """ Common work upon every state entry. """ state = self.get_driver_state() log.debug('%r: driver entering state: %s', self._platform_id, state) self._notify_driver_event(StateChangeDriverEvent(state)) def _common_state_exit(self, *args, **kwargs): """ Common work upon every state exit. Nothing done in this base class. """ ############################################################## # UNCONFIGURED event handlers. ############################################################## def _handler_unconfigured_configure(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) driver_config = kwargs.get('driver_config', None) if driver_config is None: raise FSMError('configure: missing driver_config argument') try: result = self.configure(driver_config) next_state = PlatformDriverState.DISCONNECTED except PlatformDriverException as e: result = None next_state = None log.error("Error in platform driver configuration", e) return next_state, result ############################################################## # DISCONNECTED event handlers. ############################################################## def _handler_disconnected_connect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) self.connect() result = next_state = PlatformDriverState.CONNECTED return next_state, result def _handler_disconnected_disconnect(self, *args, **kwargs): """ We allow the DISCONNECT event in DISCONNECTED state for convenience, in particular it facilitates the overall handling of the connection_lost event, which is processed by a subsequent call to disconnect from the platform agent. The handler here does nothing. """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) return None, None ########################################################################### # CONNECTED event handlers. # Except for the explicit disconnect and connection_lost handlers, the # CONNECTED handlers (here and in subclasses) should directly catch any # PlatformConnectionException to call _connection_lost. ########################################################################### def _handler_connected_disconnect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.disconnect() next_state = PlatformDriverState.DISCONNECTED return next_state, result def _handler_connected_connection_lost(self, *args, **kwargs): """ The connection was lost (as opposed to a normal disconnect request). Here we do the regular disconnect but also notify the platform agent about the lost connection. NOTE: this handler in the FSM is provided in case there is a need to directly trigger the associated transition along with the associated notification to the agent. However, the typical case is that a CONNECTED handler dealing with commands will catch any PlatformConnectionException to call _connection_lost directly. """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) # just use our supporting method: return self._connection_lost(PlatformDriverEvent.CONNECTION_LOST, args, kwargs) def _handler_connected_ping(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) try: result = self.ping() return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.PING, args, kwargs, e) def _handler_connected_get(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) try: result = self.get(*args, **kwargs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.GET, args, kwargs, e) def _handler_connected_set(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('set_attribute_values: missing attrs argument') try: result = self.set_attribute_values(attrs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.SET, args, kwargs, e) def _handler_connected_execute(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % (self._platform_id, self.get_driver_state(), str(args), str(kwargs))) if len(args) == 0: raise FSMError('execute_resource: missing resource_cmd argument') try: result = self.execute(*args, **kwargs) return None, result except PlatformConnectionException as e: return self._connection_lost(PlatformDriverEvent.EXECUTE, args, kwargs, e) ############################################################## # Platform driver FSM setup ############################################################## def _construct_fsm(self, states=PlatformDriverState, events=PlatformDriverEvent, enter_event=PlatformDriverEvent.ENTER, exit_event=PlatformDriverEvent.EXIT): """ Constructs the FSM for the driver. The preparations here are mostly related with the UNCONFIGURED, DISCONNECTED, and CONNECTED state transitions, with some common handlers for the CONNECTED state. Subclasses can override to indicate specific parameters and add new handlers (typically for the CONNECTED state). """ log.debug("constructing base platform driver FSM") self._fsm = ThreadSafeFSM(states, events, enter_event, exit_event) for state in PlatformDriverState.list(): self._fsm.add_handler(state, enter_event, self._common_state_enter) self._fsm.add_handler(state, exit_event, self._common_state_exit) # UNCONFIGURED state event handlers: self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure) # DISCONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect) self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_disconnected_disconnect) # CONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_connection_lost) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET, self._handler_connected_get) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET, self._handler_connected_set) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.EXECUTE, self._handler_connected_execute)
class PlatformDriver(object): """ A platform driver handles a particular platform in a platform network. """ def __init__(self, pnode, evt_recv): """ Creates a PlatformDriver instance. @param pnode Root PlatformNode defining the platform network rooted at this platform. @param evt_recv Listener of events generated by this driver """ assert pnode, "pnode must be given" assert evt_recv, "evt_recv parameter must be given" self._pnode = pnode self._send_event = evt_recv self._platform_id = self._pnode.platform_id if self._pnode.parent: self._parent_platform_id = self._pnode.parent.platform_id else: self._parent_platform_id = None self._platform_attributes = \ dict((a.attr_id, a.defn) for a in self._pnode.attrs.itervalues()) if log.isEnabledFor(logging.DEBUG): log.debug("%r: PlatformDriver constructor called: pnode:\n%s\n" "_platform_attributes=%s", self._platform_id, NetworkUtil._dump_pnode(self._pnode, include_subplatforms=False), self._platform_attributes) self._driver_config = None # construct FSM and start it with initial state UNCONFIGURED: self._construct_fsm() self._fsm.start(PlatformDriverState.UNCONFIGURED) def _get_platform_attributes(self): """ Gets a dict of the attribute definitions in this platform as given at construction time (from pnode parameter). """ return self._platform_attributes def _validate_driver_configuration(self, driver_config): """ Called by configure so a subclass can perform any needed additional validation of the provided configuration. Nothing is done in this base class. Note that basic validation is done by PlatformAgent prior to creating/configuring the driver. @param driver_config Driver configuration. @raise PlatformDriverException Error in driver configuration. """ def configure(self, driver_config): """ Configures this driver. It first calls _validate_driver_configuration. @param driver_config Driver configuration. """ if log.isEnabledFor(logging.DEBUG): log.debug("%r: configure: %s" % (self._platform_id, str(driver_config))) self._validate_driver_configuration(driver_config) self._driver_config = driver_config def connect(self): """ To be implemented by subclass. Establishes communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect(self): """ To be implemented by subclass. Ends communication with the platform device. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def ping(self): """ To be implemented by subclass. Verifies communication with external platform returning "PONG" if this verification completes OK. @retval "PONG" @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_metadata(self): """ To be implemented by subclass. Returns the metadata associated to the platform. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_attribute_values(self, attrs): """ To be implemented by subclass. Returns the values for specific attributes since a given time for each attribute. @param attrs [(attrName, from_time), ...] desired attributes. from_time Assummed to be in the format basically described by pyon's get_ion_ts function, "a str representing an integer number, the millis in UNIX epoch." @retval {attrName : [(attrValue, timestamp), ...], ...} dict indexed by attribute name with list of (value, timestamp) pairs. Timestamps in same format as from_time. """ raise NotImplementedError() #pragma: no cover def set_attribute_values(self, attrs): """ To be implemented by subclass. Sets values for writable attributes in this platform. @param attrs [(attrName, attrValue), ...] List of attribute values @retval {platform_id: {attrName : [(attrValue, timestamp), ...], ...}} dict with a single entry for the requested platform ID and value as a list of (value,timestamp) pairs for each attribute indicated in the input. Returned timestamps indicate the time when the value was set. Each timestamp is "a str representing an integer number, the millis in UNIX epoch;" this is to be aligned with description of pyon's get_ion_ts function. """ raise NotImplementedError() #pragma: no cover def connect_instrument(self, port_id, instrument_id, attributes): """ To be implemented by subclass. Connects an instrument to a port in this platform. @param port_id Port ID @param instrument_id Instrument ID @param attributes Attribute dictionary @retval The resulting configuration for the instrument. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def disconnect_instrument(self, port_id, instrument_id): """ To be implemented by subclass. Disconnects an instrument from a port in this platform. @param port_id Port ID @param instrument_id Instrument ID @retval @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_connected_instruments(self, port_id): """ To be implemented by subclass. Retrieves the IDs of the instruments connected to a port. @param port_id Port ID @retval @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def turn_on_port(self, port_id): """ To be implemented by subclass. Turns on a port in this platform. @param port_id Port ID @retval The resulting on/off of the port. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def turn_off_port(self, port_id): """ To be implemented by subclass. Turns off a port in this platform. @param port_id Port ID @retval The resulting on/off of the port. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def destroy(self): """ Stops all activity done by the driver. Nothing done in this class. (previously it stopped resource monitoring). """ def _notify_driver_event(self, driver_event): """ Convenience method for subclasses to send a driver event to corresponding platform agent. @param driver_event a DriverEvent object. """ log.debug("platform driver=%r: notify driver_event=%s", self._platform_id, driver_event) assert isinstance(driver_event, DriverEvent) self._send_event(driver_event) def get_checksum(self): """ To be implemented by subclass. Returns the checksum for this platform. @return SHA1 hash value as string of hexadecimal digits. @raise PlatformConnectionException """ raise NotImplementedError() #pragma: no cover def get_driver_state(self): """ Returns the current FSM state. """ return self._fsm.get_current_state() ############################################################## # FSM event handlers. ############################################################## def _common_state_enter(self, *args, **kwargs): """ Common work upon every state entry. Nothing done in this base class. @todo determine what should be done, in particular regarding eventual notification of the platform driver state transition. """ state = self.get_driver_state() if log.isEnabledFor(logging.DEBUG): log.debug('%r: driver entering state: %s' % (self._platform_id, state)) # TODO: publish the platform driver FSM state transition? # event_data = { # 'state': state # } # result = self._event_publisher.publish_event( # event_type = ?????, # origin = ?????, # **event_data) # if log.isEnabledFor(logging.DEBUG): # log.debug('%r: PlatformDriver published state change: %s, ' # 'time: %s result: %s', # self._platform_id, state, get_ion_ts(), str(result)) def _common_state_exit(self, *args, **kwargs): """ Common work upon every state exit. Nothing done in this base class. """ ############################################################## # UNCONFIGURED event handlers. ############################################################## def _handler_unconfigured_configure(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) driver_config = kwargs.get('driver_config', None) if driver_config is None: raise FSMError('configure: missing driver_config argument') try: result = self.configure(driver_config) next_state = PlatformDriverState.DISCONNECTED except PlatformDriverException as e: result = None next_state = None log.error("Error in platform driver configuration", e) return next_state, result ############################################################## # DISCONNECTED event handlers. ############################################################## def _handler_disconnected_connect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.connect() next_state = PlatformDriverState.CONNECTED return next_state, result ############################################################## # CONNECTED event handlers. ############################################################## def _handler_connected_disconnect(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.disconnect(*args, **kwargs) next_state = PlatformDriverState.DISCONNECTED return next_state, result def _handler_connected_ping(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = None try: result = self.ping() next_state = None except PlatformConnectionException as e: next_state = PlatformDriverState.DISCONNECTED log.error("Ping failed", e) return next_state, result def _handler_connected_get_metadata(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.get_metadata() next_state = None return next_state, result def _handler_connected_get_attribute_values(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('get_attribute_values: missing attrs argument') result = self.get_attribute_values(attrs) next_state = None return next_state, result def _handler_connected_set_attribute_values(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) attrs = kwargs.get('attrs', None) if attrs is None: raise FSMError('set_attribute_values: missing attrs argument') result = self.set_attribute_values(attrs) next_state = None return next_state, result def _handler_connected_connect_instrument(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('connect_instrument: missing port_id argument') instrument_id = kwargs.get('instrument_id', None) if instrument_id is None: raise FSMError('connect_instrument: missing instrument_id argument') attributes = kwargs.get('attributes', None) if attributes is None: raise FSMError('connect_instrument: missing attributes argument') result = self.connect_instrument(port_id, instrument_id, attributes) next_state = None return next_state, result def _handler_disconnected_connect_instrument(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('disconnect_instrument: missing port_id argument') instrument_id = kwargs.get('instrument_id', None) if instrument_id is None: raise FSMError('disconnect_instrument: missing instrument_id argument') result = self.disconnect_instrument(port_id, instrument_id) next_state = None return next_state, result def _handler_connected_get_connected_instruments(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('get_connected_instruments: missing port_id argument') result = self.get_connected_instruments(port_id) next_state = None return next_state, result def _handler_connected_turn_on_port(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('turn_on_port: missing port_id argument') result = self.turn_on_port(port_id) next_state = None return next_state, result def _handler_connected_turn_off_port(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) port_id = kwargs.get('port_id', None) if port_id is None: raise FSMError('turn_off_port: missing port_id argument') result = self.turn_off_port(port_id) next_state = None return next_state, result def _handler_connected_get_checksum(self, *args, **kwargs): """ """ if log.isEnabledFor(logging.TRACE): # pragma: no cover log.trace("%r/%s args=%s kwargs=%s" % ( self._platform_id, self.get_driver_state(), str(args), str(kwargs))) result = self.get_checksum() next_state = None return next_state, result ############################################################## # Platform driver FSM setup ############################################################## def _construct_fsm(self): """ """ log.debug("constructing fsm") self._fsm = ThreadSafeFSM(PlatformDriverState, PlatformDriverEvent, PlatformDriverEvent.ENTER, PlatformDriverEvent.EXIT) for state in PlatformDriverState.list(): self._fsm.add_handler(state, PlatformDriverEvent.ENTER, self._common_state_enter) self._fsm.add_handler(state, PlatformDriverEvent.EXIT, self._common_state_exit) # UNCONFIGURED state event handlers: self._fsm.add_handler(PlatformDriverState.UNCONFIGURED, PlatformDriverEvent.CONFIGURE, self._handler_unconfigured_configure) # DISCONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.DISCONNECTED, PlatformDriverEvent.CONNECT, self._handler_disconnected_connect) # CONNECTED state event handlers: self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT, self._handler_connected_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECTION_LOST, self._handler_connected_disconnect) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.PING, self._handler_connected_ping) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_METADATA, self._handler_connected_get_metadata) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_ATTRIBUTE_VALUES, self._handler_connected_get_attribute_values) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.SET_ATTRIBUTE_VALUES, self._handler_connected_set_attribute_values) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.CONNECT_INSTRUMENT, self._handler_connected_connect_instrument) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.DISCONNECT_INSTRUMENT, self._handler_disconnected_connect_instrument) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_CONNECTED_INSTRUMENTS, self._handler_connected_get_connected_instruments) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.TURN_ON_PORT, self._handler_connected_turn_on_port) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.TURN_OFF_PORT, self._handler_connected_turn_off_port) self._fsm.add_handler(PlatformDriverState.CONNECTED, PlatformDriverEvent.GET_CHECKSUM, self._handler_connected_get_checksum)