Hello World en Rust
Dans ce tutoriel, vous allez construire une application simple de capteur de temperature avec un Publisher et un Subscriber.
Prerequis : Rust installe
Ce que nous allons construire
Le Publisher simule un capteur de temperature envoyant des mesures chaque seconde. Le Subscriber les recoit et les affiche.
Etape 1 : Creer un nouveau projet
cargo new hdds-hello-world
cd hdds-hello-world
Etape 2 : Ajouter les dependances
Clonez d'abord HDDS si ce n'est pas deja fait :
git clone https://git.hdds.io/hdds/hdds.git
Editez Cargo.toml (adaptez le chemin vers votre clone de HDDS) :
[package]
name = "hdds-hello-world"
version = "0.1.0"
edition = "2021"
[dependencies]
hdds = { path = "../hdds/crates/hdds" }
[[bin]]
name = "publisher"
path = "src/bin/publisher.rs"
[[bin]]
name = "subscriber"
path = "src/bin/subscriber.rs"
Etape 3 : Definir le type de donnees
Creez src/lib.rs avec votre type de donnees :
use hdds::DDS;
/// Temperature reading from a sensor
#[derive(Debug, Clone, DDS)]
pub struct Temperature {
/// Unique sensor identifier (key field)
#[key]
pub sensor_id: u32,
/// Temperature in Celsius
pub value: f32,
/// Unix timestamp in nanoseconds
pub timestamp: u64,
}
#[key]L'attribut #[key] marque sensor_id comme la cle d'instance. Cela signifie :
- Chaque
sensor_idunique est suivi independamment - DDS maintient un historique separe par capteur
Etape 4 : Creer le Publisher
Creez src/bin/publisher.rs :
use hdds::{Participant, QoS, TransportMode};
use hdds_hello_world::Temperature;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
fn main() -> Result<(), hdds::Error> {
println!("Starting temperature publisher...");
// 1. Create a Participant on domain 0
let participant = Participant::builder("temp_publisher")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
println!("Joined domain 0");
// 2. Create a DataWriter for the topic with reliable QoS
let writer = participant
.topic::<Temperature>("temperature/room1")?
.writer()
.qos(QoS::reliable().keep_last(10).transient_local())
.build()?;
println!("DataWriter created on topic: temperature/room1");
// 3. Publish temperature readings
let sensor_id = 1u32;
for i in 0..10 {
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64;
let temperature = Temperature {
sensor_id,
value: 22.0 + (i as f32 * 0.5),
timestamp,
};
writer.write(&temperature)?;
println!("Published: {:?}", temperature);
std::thread::sleep(Duration::from_secs(1));
}
println!("Publisher finished");
Ok(())
}
Etape 5 : Creer le Subscriber
Creez src/bin/subscriber.rs :
use hdds::{Participant, QoS, TransportMode};
use hdds_hello_world::Temperature;
use std::time::Duration;
fn main() -> Result<(), hdds::Error> {
println!("Starting temperature subscriber...");
// 1. Create a Participant on domain 0
let participant = Participant::builder("temp_subscriber")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
println!("Joined domain 0");
// 2. Create a DataReader for the topic with reliable QoS
let reader = participant
.topic::<Temperature>("temperature/room1")?
.reader()
.qos(QoS::reliable().keep_last(100))
.build()?;
println!("DataReader created, waiting for data...");
// 3. Poll for samples in a loop
loop {
// Try to take available samples
while let Some(sample) = reader.try_take()? {
println!(
"Received: sensor={}, temp={:.1}C, time={}",
sample.sensor_id, sample.value, sample.timestamp
);
}
// Small delay to avoid busy-waiting
std::thread::sleep(Duration::from_millis(100));
}
}
Etape 6 : Compiler et executer
Ouvrez deux terminaux :
Terminal 1 - Lancer le Subscriber :
cargo run --bin subscriber
Terminal 2 - Lancer le Publisher :
cargo run --bin publisher
Sortie attendue
Subscriber :
Starting temperature subscriber...
Joined domain 0
DataReader created, waiting for data...
Received: sensor=1, temp=22.0C, time=1703001234567000000
Received: sensor=1, temp=22.5C, time=1703001235567000000
Received: sensor=1, temp=23.0C, time=1703001236567000000
...
Publisher :
Starting temperature publisher...
Joined domain 0
DataWriter created on topic: temperature/room1
Published: Temperature { sensor_id: 1, value: 22.0, timestamp: 1703001234567000000 }
Published: Temperature { sensor_id: 1, value: 22.5, timestamp: 1703001235567000000 }
...
Publisher finished
Comprendre le code
Participant
let participant = Participant::builder("temp_publisher")
.domain_id(0)
.with_transport(TransportMode::UdpMulticast)
.build()?;
Le Participant est votre point d'entree vers HDDS :
- Nom :
"temp_publisher"- identifie ce Participant - Domain ID :
0- les Participants doivent utiliser le meme Domain pour communiquer - Transport :
UdpMulticast- pour la communication reseau (utilisezIntraProcesspour le meme processus)
Topic et Writer
let writer = participant
.topic::<Temperature>("temperature/room1")?
.writer()
.qos(QoS::reliable().keep_last(10))
.build()?;
topic::<T>()cree un handle de Topic pour le type.writer()commence la construction d'un DataWriter.qos()configure la qualite de service.build()cree le Writer
Topic et Reader
let reader = participant
.topic::<Temperature>("temperature/room1")?
.reader()
.qos(QoS::reliable().keep_last(100))
.build()?;
Meme patron que le Writer, mais cree un DataReader.
Lecture des donnees
while let Some(sample) = reader.try_take()? {
// process sample
}
try_take() retourne Option<T> :
Some(sample)- un echantillon etait disponible et a ete retire du cacheNone- aucun echantillon disponible
Utiliser un WaitSet (alternative au polling)
Au lieu de faire du polling avec sleep(), utilisez un WaitSet pour une lecture evenementielle :
use hdds::WaitSet;
use std::time::Duration;
let reader = participant
.topic::<Temperature>("temperature/room1")?
.reader()
.build()?;
// Get status condition and create waitset
let condition = reader.get_status_condition();
let mut waitset = WaitSet::new();
waitset.attach(&condition)?;
loop {
// Wait for data (blocks until data available or timeout)
let _active = waitset.wait(Some(Duration::from_secs(5)))?;
// Take all available samples
while let Some(sample) = reader.try_take()? {
println!("Received: {:?}", sample);
}
}
Configuration QoS
Livraison fiable
let qos = QoS::reliable();
Garantit que tous les echantillons sont livres (avec retransmission si necessaire).
Conserver l'historique pour les retardataires
let qos = QoS::reliable()
.keep_last(10) // Keep last 10 samples per instance
.transient_local(); // Replay to late-joining readers
Best Effort (envoyer et oublier)
let qos = QoS::best_effort();
Le plus rapide, mais des echantillons peuvent etre perdus.
Capteurs multiples
Le champ #[key] permet de suivre plusieurs instances :
for sensor_id in [1, 2, 3] {
let temp = Temperature {
sensor_id,
value: 22.0 + (sensor_id as f32),
timestamp: now(),
};
writer.write(&temp)?;
}
Chaque sensor_id est suivi independamment avec son propre historique.
Code source complet
L'exemple complet est disponible :
git clone https://git.hdds.io/hdds/hdds-examples.git
cd hdds-examples/hello-world-rust
cargo run --bin subscriber &
cargo run --bin publisher
Prochaines etapes
- API Reference Rust - Documentation API complete
- QoS Policies - Ajuster finement la distribution des donnees
- Exemples - Exemples plus complexes