phytrace_sdk/models/domains/
hri.rs1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use validator::Validate;
6
7use super::common::Position3D;
8use crate::models::enums::{HandoverState, HumanActivity, InteractionState};
9
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct HriDomain {
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub interaction_state: Option<InteractionState>,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub tracked_humans: Option<Vec<TrackedHuman>>,
20
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub human_count: Option<u32>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub voice: Option<VoiceInteraction>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub handover: Option<Handover>,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub social: Option<SocialNavigation>,
36
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub gestures: Option<Vec<DetectedGesture>>,
40}
41
42#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
44pub struct TrackedHuman {
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub human_id: Option<String>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub position: Option<Position3D>,
52
53 #[serde(skip_serializing_if = "Option::is_none")]
55 #[validate(range(min = 0.0))]
56 pub distance_m: Option<f64>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub bearing_deg: Option<f64>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub velocity_mps: Option<f64>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub heading_deg: Option<f64>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub activity: Option<HumanActivity>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub looking_at_robot: Option<bool>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
80 #[validate(range(min = 0.0, max = 1.0))]
81 pub confidence: Option<f64>,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub first_detected: Option<DateTime<Utc>>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub is_interacting: Option<bool>,
90}
91
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
94pub struct VoiceInteraction {
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub is_listening: Option<bool>,
98
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub is_speaking: Option<bool>,
102
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub last_utterance: Option<String>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub recognition_confidence: Option<f64>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub detected_intent: Option<String>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub current_response: Option<String>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub ambient_noise_db: Option<f64>,
122}
123
124#[derive(Debug, Clone, Default, Serialize, Deserialize)]
126pub struct Handover {
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub state: Option<HandoverState>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub human_id: Option<String>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub object_id: Option<String>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub position: Option<Position3D>,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub human_ready: Option<bool>,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub release_force_n: Option<f64>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub detected_force_n: Option<f64>,
154}
155
156#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
158pub struct SocialNavigation {
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub enabled: Option<bool>,
162
163 #[serde(skip_serializing_if = "Option::is_none")]
165 #[validate(range(min = 0.0))]
166 pub personal_space_m: Option<f64>,
167
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub passing_side: Option<String>,
171
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub is_yielding: Option<bool>,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub detected_groups: Option<u32>,
179}
180
181#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
183pub struct DetectedGesture {
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub gesture_type: Option<String>,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub human_id: Option<String>,
191
192 #[serde(skip_serializing_if = "Option::is_none")]
194 #[validate(range(min = 0.0, max = 1.0))]
195 pub confidence: Option<f64>,
196
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub timestamp: Option<DateTime<Utc>>,
200}
201
202impl HriDomain {
203 pub fn new(state: InteractionState) -> Self {
205 Self {
206 interaction_state: Some(state),
207 ..Default::default()
208 }
209 }
210
211 pub fn with_human(mut self, human: TrackedHuman) -> Self {
213 let humans = self.tracked_humans.get_or_insert_with(Vec::new);
214 humans.push(human);
215 self.human_count = Some(humans.len() as u32);
216 self
217 }
218
219 pub fn with_voice(mut self, voice: VoiceInteraction) -> Self {
221 self.voice = Some(voice);
222 self
223 }
224
225 pub fn with_handover(mut self, handover: Handover) -> Self {
227 self.handover = Some(handover);
228 self
229 }
230}
231
232impl TrackedHuman {
233 pub fn at_distance(human_id: impl Into<String>, distance_m: f64) -> Self {
235 Self {
236 human_id: Some(human_id.into()),
237 distance_m: Some(distance_m),
238 first_detected: Some(Utc::now()),
239 ..Default::default()
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_hri_domain() {
250 let hri = HriDomain::new(InteractionState::HumanDetected)
251 .with_human(TrackedHuman::at_distance("human-001", 2.5));
252
253 assert_eq!(hri.human_count, Some(1));
254 }
255}