Software Architecture - shabaj-ahmed/SAR_for_habit_formation GitHub Wiki
This project adopts a microservice architecture, where each service is designed to be stateless, storing its configuration and runtime state in a centralised database. Inter-service communication is handled using the Message Queuing Telemetry Transport (MQTT) protocol, which acts as a lightweight message broker supporting event-driven data exchange.
Figure 1 presents an overview of the system architecture, including all active services and the MQTT topics used for communication. This diagram serves to illustrate how loosely coupled services interact to create a cohesive robot behaviour control system.
Figure 1: Overview of the system architecture showing all stateless services and the MQTT messages exchanged between them. Each service communicates through specific topics to maintain a modular and loosely coupled system.
An event-driven communication model is employed to ensure the robot responds dynamically to internal and external events. A centralised control system governs the robot's behaviour, implemented as a hybrid state machine that combines reactive and deliberative components:
- Reactive layer using the subsumption architecture for handling critical events like emergency stops with priority-based arbitration.
- Deliberative layer using a Finite State Machine (FSM) to control state activation based on high-level states and a Behaviour Tree for structured high-level, complex behaviour orchestration while maintaining reactivity to new events.
This event-based architecture provides a clear separation between reactive and deliberative actions while ensuring near real-time responsiveness. Figure 2 illustrates the FSM used in the deliberative layer, showing its two high-level states: \textit{active} and \textit{error}, and the sub-states that can only be entered from the active state.
Figure 2: Hierarchy of states within the finite state machine (FSM). The system transitions between two high-level states: *active* and *error*. From the *active* state, it can enter sub-states such as *interacting*, *configuring*, and *reminding*.
This architecture facilitates systematic progress tracking (DR4) and supports the delivery of personalised feedback based on real-time participant data.
- Centralised Decision Making: The state machines act as a central point for decision-making, ensuring that the robot's state is always considered before any action is taken, preventing conflicts.
- Consistency in Behaviour: Ensures predictable and logical interactions. For every state transition, there is a cleanup and setup procedure to manage the transitions safely.
- Stateless: All service states and configurations are stored in a centralised SQL database, ensuring fault tolerance.
- Scalability: New states and complex behaviours are easier to add and manage.
This event-based architecture provides a clear separation between reactive and deliberative actions while ensuring near real-time responsiveness. While there is inherent latency involved, since this application is not safety critical, the small amount of latency experienced is acceptable.
All services are stateless, and the state is stored in the centralised database. This ensures:
- Resilience: Services can recover from failures without losing critical state.
- Consistency: The robot can resume interactions without losing context.
- Modularity: Independent services can be maintained and updated efficiently.
The database operates as a service and updates states through MQTT topic subscriptions. States are updated upon reboot, and in the case of shared states, if one service updates the state, all services receive the update.
MQTT facilitates service-to-service communication. The messaging patterns used in this project were:
- Request-Response: Wait for a response before proceeding, e.g. service commands.
- Fire and Forget: Send a message without waiting for an acknowledgement, e.g. service heartbeat.
Within individual services, a custom event dispatcher facilitates decoupled communication between the business logic and communication scripts. This dispatcher maintains a dictionary of event handlers keyed by unique event_name identifiers, where the value is the function to be called. Event and associated payload are dispatched synchronously to the registered handler function, ensuring predictable first-in-first-out (FIFO) message handling. This approach separates the core business logic from the messaging layer, providing flexibility to modify the underlying message broker with minimal code refactoring.
Additionally, Python event queues were used to communicate between state machines. These queues were used to request state transitions. The communication flow is as follows:
- The FSM subscribes to two event queues: one from the subsumption state machine (SS), which publishes priority-based reactive commands, and one from the behaviour tree (BT), which issues user-driven or goal-oriented events.
- The behaviour tree (BT) subscribes to a single queue from the FSM, receiving instructions that guide the execution of structured behaviours.
The communication flow between these layers is managed systematically:
- FSM listens to incoming events or transition requests from both the subsumption state machine and the BT.
- FSM processes these requests in priority order, first addressing events from the subsumption (reactive) layer due to their criticality, followed by deliberative requests from the BT.
- After evaluating the incoming messages and the robot's current state, FSM issues state transition commands to the BT, orchestrating structured and goal-oriented behaviours.
An illustration of this communication hierarchy and flow between the state machine layers is depicted in Figure 3.
Figure 3: Illustration of the communication flow between the subsumption state machine (SS), finite state machine (FSM), and behaviour tree (BT). The FSM subscribes to queues from both SS and BT and manages state transitions accordingly. The BT subscribes only to the FSM to receive high-level behaviour commands.
This hybrid architecture ensures:
- Scalability: Microservice design supports modular expansion.
- Robustness: Stateless services recover seamlessly from failures.
- Transparency: UI and summarised interactions keep participants informed.
- Flexibility: Event-driven messaging allows dynamic, responsive behaviour.