Skip to content

Conversation

@FlavioFoxes
Copy link
Member

Cosa raggiunge questa PR

Questa PR aggiunge il meccanismo di comunicazione tra bridge (c'è un'istanza di bridge dentro ogni istanza di docker, quindi un bridge per robot) e simulatore.
La comunicazione tra bridge e simulatore è stata divisa in due step (dovuto alla necessità di avere un determinato workflow necessario al funzionamento di booster-motion):

  • Quando il docker di un robot parte, viene mandato un messaggio dal bridge al simulatore contenente solo il nome del robot. Si può considerare come se il robot si fosse connesso al simulatore.

  • Il simulatore risponde a questo messaggio con dati dei joints + imu che vengono inviati al bridge

  • Nei successivi scambi di messaggi tra bridge e simulatore, il bridge manda al simulatore un messaggio contenente nome del robot + joint commands (torque). Il simulatore manda al bridge sempre dati dei joints + imu.

Ho anche aggiunto booster-motion nella cartella resources/booster-motion, in modo che lo abbiamo tutti e nello stesso path. Ci sono cose in più da quella cartella che si possono eliminare, poi si farà.

Come far partire la simulazione

Il funzionamento della simulazione, al momento, è strettamente correlato al branch sim-bridge di spqrbooster2026. Con altri branch non funziona.

Requisiti

Spqrbooster2026

Sulla repo spqrbooster2026, assicuratevi di essere sul branch sim-bridge di spqrbooster2026.
Una volta che siete su questo branch, dovete buildare il bridge:

cd src/SimBridge/bridge
mkdir build
cd build
cmake ..
make

Non serve fare altro su spqrbooster2026.

Circus

Per prima cosa bisogna creare l'immagine docker, usando il Dockerfile nella cartella dockerfiles:

cd dockerfiles
docker build -t spqr:booster .

Il nome spqr:booster potremmo poi cambiarlo con spqr:boosterT1 per il T1 e spqr:boosterK1 per il K1, e più in generale spqr:<nome_robot> visto che ogni robot avrà presumibilmente un diverso Dockerfile.

Una volta creata l'immagine docker, dovete modificare il file resources/config/framework_config.yaml:

image: spqr:booster
volumes:
  - "PATH_TO/circus/resources/booster_motion:/app/booster_motion"
  - "PATH_TO/spqrbooster2026/src/SimBridge/bridge/build:/app/bridge"
  

inserendo i vostri path delle repo (ATTENZIONE: non modificare i path /app/booster_motion e /app/bridge).
Fatto questo, dovreste essere in grado di lanciare la simulazione con:

pixi run circus

Simulazione

Una volta aperta la scena, la simulazione parte in pausa. Se guardate sul terminale, comparirà una stampa con scritto: Connected robot: <nome_robot>. Prima di premere il pulsante Start, aspettate che sul terminale compaia anche la stampa Received message (PS: compariranno infinite stampe, lo so è una cosa da cambiare in modo più fancy :) ). A questo punto, avviando la simulazione, vedrete che i robot rimangono in piedi, oscillando leggermente. Al momento non si possono far muovere perché dentro al docker non c'è un framework che fornisce i comandi. Metterò nei prossimi giorni una cosa dummy per farlo muovere.

Video.del.02-12-2025.12.20.42.webm

NEL CASO IL ROBOT CASCASSE
Significa che o il bridge o booster-motion sono crashati. Per capire qual è il problema, lasciate il simulatore aperto e aprite un altro terminale. Cercate l'id del container con docker ps -a ed entrate nel container:

docker exec -it <id_container> bash

Aperto il terminale del container, dovreste poter vedere il problema facendo:

cat /var/log/supervisor/supervisord.log

In caso servisse, potete vedere anche i file di errore e di log /var/log/booster-motion.err, /var/log/booster-motion.log, /var/log/bridge.err, /var/log/bridge.log.

Dettagli booster-motion

Una carrellata di roba su booster-motion, da lasciare ai posteri (sono tutte già fatte dentro al container, sono solo per informazione):

  • molte delle dipendenze si trovano in resources/booster-motion nelle cartelle lib, lib-usr-local, lib-x86_64-linux-gnu.
  • alcune dipendenze non sono in quelle cartelle, e sono installate manualmente dentro il dockerfile ( libpulse0, libpulse-mainloop-glib0, libasound2)
  • per avviarlo, richiede che nel path /opt/booster/configs ci sia il file system_settings_config.yaml (l'ho messo nella cartella dockerfiles/configs)
  • al suo interno, booster-motion fa delle chiamate per disabilitare il paging della memoria. Per questo sono stati aggiunti i seguenti parametri nella creazione del container (dentro Container.cpp):
        {"IpcMode", "host"},                      
        {"CapAdd", {"SYS_NICE", "IPC_LOCK"}},     
        {"SecurityOpt", {"seccomp=unconfined"}},  
        {"Ulimits", nlohmann::json::array({
            {
                {"Name", "memlock"},
                {"Soft", -1},
                {"Hard", -1}
            }
        })}
  • serve l'esportazione del profile dds resources/booster-motion/fastdds_profile.xml per la comunicazione dei topic

Prossimi Step

  • Aggiungere un framework dummy (da mettere dentro al container) per farlo camminare/girare la testa/...
  • Sostituire le stampe infinite Received Message con una stmapa sola che dice "Robot X è pronto"
  • Sistema di avvio della simulazione (avviare la simulazione solo quando tutti i container di tutti i robot sono pronti per comunicare effettivamente). Altrimenti se si avvia la simulazione prima che siano tutti pronti, alcuni potrebbero cascare a peso morto perché il suo booster-motion non è ancora partito

@DaniAffCH
Copy link
Member

DaniAffCH commented Dec 3, 2025

grazie della descrizione molto dettagliata e bel lavoro. Prima che guardo il codice vero e proprio, perche vuoi mettere tutta la roba di boostermotion dentro la repo del simulatore?

@FlavioFoxes
Copy link
Member Author

se non sbaglio sul robot già ci dovrebbe essere booster-motion (non so neanche se è lo stesso identico o cambia per qualche motivo), quindi questo sarebbe circoscritto solo al simulatore. Però invoco gli altri a correggere se sbagliato, si può tranquillamente mettere dentro spqrbooster in realtà, non cambia nulla.
La cosa su cui spingerei più che altro è averlo dentro una delle due repo, in modo che lo abbiamo tutti senza doverlo andare a scaricare a parte, e poi in questo modo lo abbiamo tutti allo stesso path per la copia dentro al container

@ValerioSpagnoli
Copy link
Member

booster motion sul robot c'è
su quelli con due schede sta nella motion board, per quelli con una sola scheda direttamente sulla jetson immagino
Comunque ottimo

Non so dove può essere meglio mettere la roba di booster motion, forse un'idea può essere anche fare una repo separata con il bridge da inserire come sub-module in circus

Comunque se puoi runna il pre-commit così risolvi i problemi di clang

@DaniAffCH
Copy link
Member

Secondo me booster motion deve stare dentro il framework, perchè è una cosa che riguarda il framework stesso, non il simulatore. Penso che puoi metterlo direttamente li. Anche la repo esterna andrebbe bene

@neverorfrog
Copy link
Contributor

Scusa, ma per funzionare il bridge richiede di avere ROS sulla macchina?

@ValerioSpagnoli
Copy link
Member

Scusa, ma per funzionare il bridge richiede di avere ROS sulla macchina?

nel docker credo

@FlavioFoxes
Copy link
Member Author

FlavioFoxes commented Dec 4, 2025

dentro al docker già c'è ros, però vi serve anche a voi perché il bridge va compilato a parte (con cmake, non colcon). Volendo posso mettere la compilazione direttamente dentro al docker

@ValerioSpagnoli
Copy link
Member

Secondo me booster motion deve stare dentro il framework, perchè è una cosa che riguarda il framework stesso, non il simulatore. Penso che puoi metterlo direttamente li. Anche la repo esterna andrebbe bene

Però effettivamente se booster motion sta sul robot non dovrebbe essere messo nel framework
Diciamo che dovrebbe stare nell'immagine docker usata dal simulatore

@DaniAffCH
Copy link
Member

Però effettivamente se booster motion sta sul robot non dovrebbe essere messo nel framework
Diciamo che dovrebbe stare nell'immagine docker usata dal simulatore

Si ma l'immagine docker è specifica del framework. Diversi framework hanno dipendenze diverse e quindi immagini diverse.

@ValerioSpagnoli
Copy link
Member

Però effettivamente se booster motion sta sul robot non dovrebbe essere messo nel framework
Diciamo che dovrebbe stare nell'immagine docker usata dal simulatore

Si ma l'immagine docker è specifica del framework. Diversi framework hanno dipendenze diverse e quindi immagini diverse.

eh si esatto, ma la stessa cosa vale per il simulatore, non tutti i framework lo usano nello stesso modo. Diciamo che il bridge è nel mezzo, non dovrebbe sare direttamente nel framework, ma neanche nel simulatore
Nella nostra immagine docker possiamo metterci il bridge, un ipotetico altro team che non usa boostermotion avrà un altro modo di connettersi a circus

@DaniAffCH
Copy link
Member

Però effettivamente se booster motion sta sul robot non dovrebbe essere messo nel framework
Diciamo che dovrebbe stare nell'immagine docker usata dal simulatore

Si ma l'immagine docker è specifica del framework. Diversi framework hanno dipendenze diverse e quindi immagini diverse.

eh si esatto, ma la stessa cosa vale per il simulatore, non tutti i framework lo usano nello stesso modo. Diciamo che il bridge è nel mezzo, non dovrebbe sare direttamente nel framework, ma neanche nel simulatore Nella nostra immagine docker possiamo metterci il bridge, un ipotetico altro team che non usa boostermotion avrà un altro modo di connettersi a circus

Vero, forse mettere il bridge in una repo separata ed aggiungerlo come submodule è la cosa migliore

@FlavioFoxes
Copy link
Member Author

per ora ho messo la compilazione del bridge dentro al docker, cosi non dovete avere ros per buildarlo. Appena spostiamo su una repo a parte la togliamo

@FlavioFoxes
Copy link
Member Author

nella nuova repo simbridge ho spostato sia il bridge che stava su spqrbooster sia booster-motion.
Per compilarlo potete usare pixi (ci sono le istruzioni nel readme)

A parte la compilazione del bridge, stessi passaggi di prima per provare il tutto (quindi crea l'immagine, modifica framework_config.yaml , ...)

Comment on lines 73 to 101
// Add host library paths to binds
std::vector<std::string> all_binds = binds;
all_binds.push_back("/usr/lib/x86_64-linux-gnu:/host/usr/lib/x86_64-linux-gnu:ro");
all_binds.push_back("/lib/x86_64-linux-gnu:/host/lib/x86_64-linux-gnu:ro");

payload["HostConfig"] = {
{"Binds", all_binds},
{"IpcMode", "host"},
{"CapAdd", {"SYS_NICE", "IPC_LOCK"}},
{"SecurityOpt", {"seccomp=unconfined"}},
{"Ulimits", nlohmann::json::array({
{
{"Name", "memlock"},
{"Soft", -1},
{"Hard", -1}
}
})}
};

payload["Env"] = {
"ROBOT_NAME=" + robot_name,
"SERVER_IP=172.17.0.1",
"CIRCUS_PORT=" + std::to_string(frameworkCommunicationPort),
"LD_LIBRARY_PATH=/app/booster_motion/lib:/app/booster_motion/lib-usr-local:/app/booster_motion/lib-x86_64-linux-gnu:/host/usr/lib/x86_64-linux-gnu:/host/lib/x86_64-linux-gnu",
"FASTRTPS_DEFAULT_PROFILES_FILE=/app/booster_motion/fastdds_profile.xml"
};

payload["Env"]
= {"ROBOT_NAME=" + robot_name, "CIRCUS_PORT=" + std::to_string(frameworkCommunicationPort)};
payload["Tty"] = true;
payload["OpenStdin"] = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Queste modifiche devono essere fatte a livello di immagine, non a livello di codice dentro la simulazione, altrimenti stiamo facendo qualcosa su misura

Copy link
Member Author

@FlavioFoxes FlavioFoxes Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha senso, però alcune cose da chiarire:

{"Binds", all_binds},
{"IpcMode", "host"},                      
{"CapAdd", {"SYS_NICE", "IPC_LOCK"}},     
{"SecurityOpt", {"seccomp=unconfined"}},  
{"Ulimits", nlohmann::json::array({
    {
        {"Name", "memlock"},
        {"Soft", -1},
        {"Hard", -1}
    }
})}

non credo possano essere messe nel dockerfile perché sono cose da specificare al lancio del docker, non nel dockerfile. In caso potrebbero essere messe in un dockercompose, se può servire posso vedere come poi chiamarlo da codice perché non ne ho idea.

Gli altri binds sono riuscito a toglierli perché ho trovato le ultime librerie mancanti che servivano a booster-motion, le scarico direttamente nel dockerfile.

Questi invece:

"LD_LIBRARY_PATH=/app/booster_motion/lib:/app/booster_motion/lib-usr-local:/app/booster_motion/lib-x86_64-linux-gnu:/host/usr/lib/x86_64-linux-gnu:/host/lib/x86_64-linux-gnu",
"FASTRTPS_DEFAULT_PROFILES_FILE=/app/booster_motion/fastdds_profile.xml"

possono essere messi nel dockerfile senza problemi, però per farlo devo caricare booster-motion dentro al container direttamente nell'immagine e non come volume. Il che ha anche senso perché non è da compilare ma già pronto, però ognuno che si crea l'immagine deve andare a modificare nel dockerfile il path rispetto a dove ha messo booster-motion. Per me si può fare, però ditemi anche voi che ne pensate

Comment on lines 105 to 124
void AppWindow::startSimulation() {
if (sim && !sim->isRunning()) {
sim = std::make_unique<SimulationThread>(mujContext->model, mujContext->data);
sim->start();
}
}

void AppWindow::stopSimulation() {
if (sim && sim->isRunning()) {
sim->stop();
}
}

void AppWindow::stepSimulation() {
if (mujContext) {
mj_step(mujContext->model, mujContext->data);
viewport->update();
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Io toglierei i bottoni, e lascerei l'avvio della simulazione come era prima.
Lasciamolo fare ai frontendisti.

Immagino ti serva stoppare la simulazione fin quando i robot non si connettono giusto? Puoi semplicemente usare qualsiasi tecnica di thread communication per far comunicare il server e il main thread. Quando il server dice che i robot sono connessi, il main thread fa partire sim->start

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ho fatto il meccanismo per far partire la simulazione automaticamente (è l'ultima commit, ti richiedo la review cosi la vedi). Devo per forza distringuere il caso connected da ready perché l'essere connessi non implica essere pronti (causa booster-motion).

L'altra strada che si poteva seguire è questa: usare un array dentro RobotManager grande quanto l'array robots dove ogni elemento i-esimo è un booleano che dice se il robot i-esimo è pronto per essere lanciato. Questo rende le computazioni più leggere ma non associa intrinsecamente l'essere connessi e l'essere pronti al robot.
Pensavo potesse essere comodo per il futuro avere l'informazione direttamente nel robot.

Comment on lines 189 to 224

// Receive initial message with robot name
char buffer[MAX_MSG_SIZE];
int n = read(client_fd, buffer, sizeof(buffer) - 1);

if (n <= 0) {
std::cerr << "Error reading the initial message.\n";
// close(client_fd);
continue;
}

// unpack of the MsgPack message
msgpack::object_handle oh = msgpack::unpack(buffer, n);
msgpack::object obj = oh.get();

// First message is the robot name as a string
if (obj.type != msgpack::type::STR) {
std::cerr << "First message must be a string. Ignore it...\n";
continue;
}

std::string robotName = obj.as<std::string>();
std::cout << "Connected Robot: " << robotName << "\n";

// Send message with initial state
for (auto& r : robots_) {
if (r->name == robotName) {
std::cout << "Sending initial message to " << robotName << std::endl;
auto answ = r->sendMessage();
msgpack::sbuffer sbuf;
msgpack::pack(sbuf, answ);
send(client_fd, sbuf.data(), sbuf.size(), 0);
break;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client_fd >= 0 significa che qualcuno si è appena connesso? Perchè ci sta tutta questa logica ripetuta se tanto ti comunica tutto nel primo messaggio che ti manda? Sembra ridondante.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

riguardo client_fd>=0 si.
La logica è ripetuta tranne che per la ricezione del messaggio (rispetto a sotto se guardi non c'è r->receiveMessage(), ma direttamente r->sendMessage()), questo perché dentro di sé receiveMessage applica le torque che riceve (cosa che nel caso ideale nel primo messaggio noi non vogliamo).
Il motivo è il seguente: booster-motion, per pubblicare i comandi da applicare, deve avere già a disposizione i joint states e l'imu del robot corrispondente. Questo significa che la pipeline corretta nella prima iterazione dovrebbe:

  1. simulatore manda joint e imu al bridge
  2. bridge pubblica joint e imu
  3. booster-motion legge joint e imu e pubblica torque
  4. bridge legge torque e le manda al simulatore

Per questo motivo ho differenziato il caso del primo messaggio da tutti gli altri (infatti nel primo messaggio mando solo il nome del robot e non le torque).
Per togliere questo codice il primo messaggio deve contenere anche delle torque fasulle (che possono essere tutte 0 ad esempio), che però verranno applicate nel primo step. Non penso possa creare problemi, però ufficialmente questa è la pipeline corretta in teoria

@FlavioFoxes FlavioFoxes requested a review from DaniAffCH December 9, 2025 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants