# The SLACM Application Model A SLACM application consists of *components* that run inside *actors* that execute on *hosts*. A component is an object: an instance of a class in Python, that has a single execution thread that executes all component operations. An actor is a process that contains components, each running its own thread. A host is a node on the network that is capable of running SLACM applications, i.e. it has all SLACM packages installed. The SLACM application developer creates components and creates an *application model* that represents how to application is assembled from components and how it is installed on the hosts. Optionally, a file containing the parameter settings for the components can be supplied, as well as configuration files that sets specific system configuration parameters. This package is handed to the SLACM tool (called 'slacm_run') that runs the application. ## Components A SLACM component is an object that has an execution thread, created by the framework. Components are *reactive*: they react to messages that are sent to the component. When the framework creates a component it runs its constructor then it launches the component's thread that waits for messages sent to the component. A component interacts with other components in the application via a messages, which are routed through *ports* that belong to components. A component 'owns' its port, but its port are 'wired' to ports of other components as described by the application model. A port can be of type input, output, or both. Input and 'both' type ports can be used to receive a message, output and 'both' type ports can be used to send a message. The component code calls the appropriate 'send' or 'receive' operation on the port object it owns. A component is always reactive: it reacts to messages arriving at its input ports. Each input port must have a corresponding *message handler* -- a method of the component object, that will be called by the framework when a message arrives at a component. This handler **must** read (receive) the message from the port, and *can* send messages to output (or 'both' type) ports. ### Port types and component interactions #### Timer port This is an input port that sends *timer messages* to the component. It is either periodic (with a fixed periodicity) or aperiodic (with a programmable delay). The message received on the timer port is a floating point number as generated by the `time.time()` function of Python. #### Component interactions SLACM is based on a subset of the communication patterns provided by ZeroMQ. A communication pattern specifies how messages are passed from component port to component port, and what protocol should the developer follow when using them. ##### Publish/subscribe In this scheme, *publisher* components send messages through their 'publish' (`pub`) ports that get delivered to all 'subscriber' (`sub`) ports of *subscriber* components connected to the source ports. The communication pattern is many-to-many (i.e. one publisher can send to many subscribers, and one subscriber can receive from many publishers) and uni-directional (i.e. messages always from `pub`-s to `sub`-s. ##### Request/reply This is a bi-directional scheme where a *client* component sends a message through its 'request' (`req`) port that goes to a *server* component's 'reply' (`rep`) port. The server's input handler should read this message, and generate a response message and send it through the **same** `rep` port, so that the message will be routed to to the **same** `req` port of the client, whose handler will be triggered, and this handler must read the message. This approach implements a full round trip but it has a limitation. The client cannot send a new message *until it has read the response message to a previous request message*. In other words, the client/server must operate in a lock-step: client -> server -> client -> server, etc. As consequence, the server will always process (and respond to) the incoming messages in the order of their arrival. ##### Query/answer This scheme is similar to *request/reply* scheme except it has no restriction: the client can send messages without waiting for the responses. Furthermore, the server can process and respond to messages in arbitrary order. The correspoding port types are called 'query' (`qry`) and 'answer' (`ans`). Note that the developer creates the code for the component message handlers, one for each input (or 'both') type ports. The handler is called by the framework. The port objects are also created by the framework, and they provide the send and receive operations that the handler should use. The figure below shows (1) all the possible ports of a component, (2) the handlers that must be implemented by the component, (3) the port types that support send and receive operations, and (4) the allowed connections among the ports. ![SLACM Component](figs/slacm-comp.png "SLACM Component") ## Applications The model of a SLACM application represents how the application is composed. The model specifies - the name of the application - the message topics the application uses - the components the application is built from - the actors that contain components - the hosts the application is to be run on The example below shows a simple application model. ``` app App: // Application call 'App' msg PubTopic // A message topic component PubComponent: // A component that publishes messages timer t_port 1000 // has a timer port pub p_port : PubTopic // has publisher port component SubComponent: // Another component tat subscribes to messages sub s_port : PubTopic // has a subscriber port actor AppActor: // An actor that contains both components thePublisher : PubComponent theSubscriber: SubComponent host (rpi4car) AppActor // The actor will run on host 'rpi4car' ``` The components communicate via messages, and each message belongs to a *topic*. This is just a label used in wiring up the components. The components can send any Python objects via the ports, and the developer is responsible for constructing and interpreting thos data structures. The topics are merely used for connecting the ports, but the content and formatting of the messages is the responsibility of the developer. Note that ZeroMQ provides operations for send/receiving Python objects (`send_pyobj`/`recv_pyobj`, as well as byte arrays (`send`/`recv`). The wiring of component ports happens via matching the topics and following the connection rules between ports (as indicated on the component illustration above). The simple application model introduces an application (`App`), and a message topic called `PubTopic`. Next, defines two components. The first, `PubComponent` has a timer port, called `t_port` which is attached to a periodic timer that fires every 1000 msec. It also has a `pub` port called `p_port` tha publishes messages of topic `PubTopic`. The second component, `SubComponent` has a `sub` port called `s_port` that subscribes to messages of topic `SubTopic`. The two components are packaged into a single actor called `AppActor`, and the component instancess are called `thePublisher` and `theSubscriber`, respectively. Finally, the application is to be run on a host on the network called `rpi4car` that will execute a copy of the `AppActor`. This last depoyment specification can be missing, in which case the all actors of the application will run on the host the application files are located. If a deployment is specified, the SLAC will copy the specified application actor(s) to the remote host(s) and runs them there. For the latter, the remote hosts (1) must be accesible via public/private key, no password SSH logins, and (2) must have SLACM installed. The figure below shows the architecture of the application described by the model. ![SLACM Component](figs/slacm-app0.png "SLACM Simple App Architecture") SLACM determines the 'wiring' (i.e. connections) between the component ports based on the message topic the port handled. In the above example the `p_port` published 'PubTopic' messages, hence it got connected to the `s_port` because that subscribed to such topics. Note that the matching by name is *complete*: if a component publishes topic `X` and subscribes to the same topic `X` then it will receive (and will be triggered by) its own messages. The following sample shows how to use other port types. ``` ... message SensorReady message SensorQuery message SensorValue ... component Sensor: timer clock 1000 // Periodic timer trigger to trigger sensor every 1 sec pub ready : SensorReady // Publish port for SensorReady messages rep request : ( SensorQuery , SensorValue ) // Reply port to query the sensor and retrieve its value component LocalEstimator: sub ready : SensorReady // Subscriber port to trigger component with SensorReady messages req query : (SensorQuery , SensorValue ) // Request port to query the sensor and retrieve its value ... ``` In the above example the `req` and `rep` ports of the `Sensor` and the `LocalEstimator` components are connected becaus ethey have matching message topics (`SensorQuery` and `SensorValue`).NOte that for thise case there two message topics needed: one for the message going from the `req` port to the `rep` port (`SensorQuery`), and another one for the message coming back (`SensorValue`). The `pub`/`sub` ports are wired as before. The above example implements a useful architectural template: the `Sensor` components is triggered periodically, and it publishes a `SensorReady` message when a new block of data is ready. This is only a *notification* event that triggers the `LocalEstimator` component, which, in turn, can request the actual data itself via the `query` port. Note that `query` port serves both for sending and receiving messages, just like the `request` port of the `Sensor`. Note that the '`req`/`rep` interaction pattern follows a strict protocol as discussed above. This is relaxed to for the `qry`/`ans` that can be used as a replacement. ``` ... message MsgReq message MsgRep component HelloQuery: timer clock 1000 qry port : (MsgReq,MsgRep) component HelloAnswer: ans port : (MsgReq,MsgRep) ... ``` However, in this case, there is more work to be done on the `answer` side: the 'server' must keep track of the identity of the 'client' (see subsequent example).