Software Architecture

The rationale behind this library is also simplifying the use of Soar via ROS2 while the API should remain as simple as possible. The challenges behind connecting Soar and ROS2 lies in the software architecture of Soar: synchronous callbacks and events. A detailed explanation of the Soar architecture is provided on their website in the Soar manual or the Soar Threading Model.

The user should only be required to do two things: Initialize the Soar kernel with an Agent and add adapted publisher, subscriber, services and clients afterwards. For each message/ topic, the conversion between Soar working memory elements (WMEs) and ROS2 message, or vice versa, types must be implemented manually.

  • The kernel is wrapped in a single class called SoarRunner. The main responsibility is to create, start and maintain the Soar kernel.

  • Publisher, Subscribers, Service and Clients are added via a separate functions to the ROS2 node.

        sequenceDiagram
box Soar
participant SoarRunner
participant SoarRunner-RunThread
participant SoarKernel
end

box ROS2
participant Client
participant Client-RunThread
participant ClientInputQueue
participant ClientOutputQueue
end

SoarRunner ->> SoarKernel: CreateKernelInNewThread()
activate SoarKernel;
activate SoarRunner

%% INITIALIZE CLIENT
SoarRunner ->> Client : AddClient(client)
activate Client
Client ->> Client-RunThread: InitializeRun
deactivate Client
activate Client-RunThread
par ClientRun
    note right of Client: Start client worker thread.
    loop Client run thread
        Client-RunThread ->>+ ClientInputQueue: try read
        ClientInputQueue ->>- Client-RunThread: Element
        Client-RunThread ->> Client-RunThread: sent ROS2 request
        Client-RunThread ->> Client-RunThread: await ROS2 response
        Client-RunThread ->> Client-RunThread: future.get()
        Client-RunThread ->> ClientOutputQueue: push(response)
        deactivate Client-RunThread
    end
and Agent run
    note right of SoarRunner: Start agent worker thread.
    SoarRunner ->> SoarRunner-RunThread: Run()
    deactivate SoarRunner
    activate SoarRunner-RunThread
    loop continue == true
        SoarRunner-RunThread ->> SoarRunner-RunThread: RunAllAgentsForever()
    end
    deactivate SoarRunner-RunThread
and Kernel run
    note right of SoarRunner: Callback for Event is called.
    SoarKernel ->> SoarRunner: smlEVENT_AFTER_ALL_OUTPUT_PHASES callback to UpdateWorld()
    deactivate SoarKernel
    activate SoarRunner
    SoarRunner ->> SoarRunner: processOutputLinkChanges()
    SoarRunner ->> SoarRunner: processInput()
    SoarRunner ->> SoarKernel: UpdateWorld() complete
    activate SoarKernel
    deactivate SoarRunner
end

note right of SoarRunner: Example for Client call from Soar: Process output
SoarKernel ->>+ SoarRunner: smlEVENT_AFTER_ALL_OUTPUT_PHASES callback to UpdateWorld()
deactivate SoarKernel;
activate SoarRunner
SoarRunner ->> SoarRunner: outputs[output-link.command] = shared_ptr output
SoarRunner ->>+ Client: process_s2r()
    deactivate SoarRunner
    Client ->> ClientInputQueue: queue.push(parse(sml:Identifier *))
    note right of ClientInputQueue: Client run thread reads and processes queue.
    Client ->>- SoarRunner: void
    activate SoarRunner
SoarRunner ->> SoarRunner: output-link.command.AddStatusComplete()
SoarRunner ->> SoarRunner: processInput()
SoarRunner ->>- SoarKernel: UpdateWorld() complete
activate SoarKernel;

loop SoarKernel and SoarRunner not blocked
SoarKernel ->>+ SoarRunner: smlEVENT_AFTER_ALL_OUTPUT_PHASES callback to UpdateWorld()
SoarRunner ->>- SoarKernel: UpdateWorld() complete
end

SoarKernel ->> SoarRunner: smlEVENT_AFTER_ALL_OUTPUT_PHASES callback to UpdateWorld()
activate SoarRunner;
deactivate SoarKernel;
SoarRunner ->> SoarRunner: processOutputLinkChanges()
note right of Client: Check if response is available.
SoarRunner ->>+ Client: Process input
deactivate SoarRunner;
    Client ->>+ ClientOutputQueue: Read Queue
    ClientOutputQueue ->>- Client: Element
    Client ->> Client: parse()
    Client ->>- SoarRunner: void
activate SoarRunner
SoarRunner ->> SoarRunner: agent.commit()
SoarRunner ->> SoarKernel: UpdateWorld() complete
activate SoarKernel;

deactivate SoarKernel;
deactivate SoarRunner;