vicon_sys/
sys.rs

1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4
5use std::{thread, time::Duration};
6
7use crate::{HasViconHardware, Vector3D, ViconError, ViconSdkStatus, ViconSubject};
8
9include!(concat!(env!("OUT_DIR"), "/libvicon.rs"));
10
11/// Maximum number of times [`ViconSystem::new`]
12/// will attempt to connect to a Vicon data stream.
13pub const MAX_CONNECT_RETRIES: usize = 3;
14
15/// Maximim timeout [`ViconSystem::new`] will
16/// use when connecting to a vicon data stream.
17pub const MAX_CONNECT_TIMEOUT: u32 = 1000;
18
19/// An active connection to
20/// a real Vicon data stream.
21pub struct ViconSystem {
22    vicon_handle: *mut std::ffi::c_void,
23}
24
25impl ViconSystem {
26    /// Returns a new system connected
27    /// to a Vicon data stream at `hostname`.
28    ///
29    /// The provided `hostname` may optionally
30    /// include a port suffix (e.g., `192.168.1.1:810`).
31    pub fn new(hostname: &str) -> Result<Self, ViconError> {
32        let vicon_handle = unsafe { Client_Create() };
33
34        // Try connecting to the Vicon.
35        let host_and_port = std::ffi::CString::new(hostname).unwrap();
36        let mut attempts = 0;
37        loop {
38            let status: ViconSdkStatus = unsafe {
39                Client_SetConnectionTimeout(vicon_handle, MAX_CONNECT_TIMEOUT);
40                Client_Connect(vicon_handle, host_and_port.as_ptr()).into()
41            };
42
43            if status.is_success() {
44                break;
45            }
46
47            if attempts > MAX_CONNECT_RETRIES {
48                return Err(ViconError::SdkError { source: status });
49            }
50
51            attempts += 1;
52        }
53
54        // Configure SDK client data stream.
55        unsafe {
56            Client_SetStreamMode(vicon_handle, CStreamMode_ClientPull.try_into().unwrap());
57            Client_SetAxisMapping(
58                vicon_handle,
59                CDirection_Forward.try_into().unwrap(),
60                CDirection_Left.try_into().unwrap(),
61                CDirection_Up.try_into().unwrap(),
62            );
63        }
64
65        // TODO: The reference usage by the NEST Lab
66        //       questions if these steps are needed
67        //       --copy-pasta for completeness.
68        unsafe {
69            Client_EnableSegmentData(vicon_handle);
70            Client_EnableMarkerData(vicon_handle);
71        }
72        thread::sleep(Duration::from_millis(1000));
73
74        Ok(Self { vicon_handle })
75    }
76}
77
78impl HasViconHardware for ViconSystem {
79    fn read_frame_subjects(&mut self) -> Result<Vec<crate::ViconSubject>, ViconError> {
80        // Get a new frame.
81        let _: ViconError = unsafe { Client_GetFrame(self.vicon_handle).try_into()? };
82
83        // Count the subjects in the frame.
84        let mut subject_count = COutput_GetSubjectCount {
85            Result: CResult_UnknownResult as i32,
86            SubjectCount: 0,
87        };
88        unsafe {
89            Client_GetSubjectCount(self.vicon_handle, &mut subject_count);
90        }
91        let _: ViconError = subject_count.Result.try_into()?;
92        let subject_count = subject_count.SubjectCount;
93
94        // Visit all subjects in the frame.
95        let mut subjects = Vec::with_capacity(subject_count.try_into().unwrap());
96        for i in 0..subject_count {
97            // Get the subject's name.
98            let mut buffer = vec![0; 1024];
99            let subject_name = unsafe {
100                let _: ViconError = Client_GetSubjectName(
101                    self.vicon_handle,
102                    i,
103                    buffer.capacity() as i32,
104                    buffer.as_mut_ptr(),
105                )
106                .try_into()?;
107                buffer_to_cstring(buffer)
108            };
109
110            // Get the subject's segment count.
111            let mut segment_count = COutput_GetSegmentCount {
112                Result: CResult_UnknownResult as i32,
113                SegmentCount: 0,
114            };
115            unsafe {
116                Client_GetSegmentCount(
117                    self.vicon_handle,
118                    subject_name.as_ptr(),
119                    &mut segment_count,
120                );
121            }
122            let _: ViconError = segment_count.Result.try_into()?;
123            let segment_count = segment_count.SegmentCount;
124
125            // Skip subjects with no segments.
126            if segment_count == 0 {
127                continue;
128            }
129
130            // Get the _zeroth_ segment's name.
131            let mut buffer = vec![0; 1024];
132            let segment_name = unsafe {
133                let _: ViconError = Client_GetSegmentName(
134                    self.vicon_handle,
135                    subject_name.as_ptr(),
136                    0,
137                    buffer.capacity() as i32,
138                    buffer.as_mut_ptr(),
139                )
140                .try_into()?;
141                buffer_to_cstring(buffer)
142            };
143
144            // Get the segment's translation.
145            let mut segment_translation = COutput_GetSegmentGlobalTranslation {
146                Result: CResult_UnknownResult as i32,
147                Translation: [0.0f64; 3],
148                Occluded: -1,
149            };
150            unsafe {
151                Client_GetSegmentGlobalTranslation(
152                    self.vicon_handle,
153                    subject_name.as_ptr(),
154                    segment_name.as_ptr(),
155                    &mut segment_translation,
156                );
157            }
158            let _: ViconError = segment_translation.Result.try_into()?;
159
160            // Skip occluded segments.
161            if segment_translation.Occluded != 0 {
162                continue;
163            }
164
165            // Get the segment's rotation.
166            let mut segment_rotation = COutput_GetSegmentGlobalRotationEulerXYZ {
167                Result: CResult_UnknownResult as i32,
168                Rotation: [0.0f64; 3],
169                Occluded: -1,
170            };
171            unsafe {
172                Client_GetSegmentGlobalRotationEulerXYZ(
173                    self.vicon_handle,
174                    subject_name.as_ptr(),
175                    segment_name.as_ptr(),
176                    &mut segment_rotation,
177                );
178            }
179            let _: ViconError = segment_rotation.Result.try_into()?;
180
181            // Skip occluded segments.
182            if segment_rotation.Occluded != 0 {
183                continue;
184            }
185
186            subjects.push(ViconSubject::from_vicon_frame(
187                subject_name.to_str().unwrap().to_owned(),
188                segment_translation,
189                segment_rotation,
190            ));
191        }
192
193        Ok(subjects)
194    }
195}
196
197unsafe impl Send for ViconSystem {}
198
199impl ViconSubject {
200    /// Converts raw segment data from a Vicon
201    /// to a [`ViconSubject`].
202    fn from_vicon_frame(
203        name: String,
204        translation: COutput_GetSegmentGlobalTranslation,
205        rotation: COutput_GetSegmentGlobalRotationEulerXYZ,
206    ) -> Self {
207        // Calculate origins, converting from
208        // millimeters to meters.
209        let origin_x = translation.Translation[0] / 1000.0;
210        let origin_y = translation.Translation[1] / 1000.0;
211        let origin_z = translation.Translation[2] / 1000.0;
212
213        Self {
214            name,
215            origin: Vector3D {
216                x: origin_x,
217                y: origin_y,
218                z: origin_z,
219            },
220            rotation: Vector3D {
221                x: rotation.Rotation[0],
222                y: rotation.Rotation[1],
223                z: rotation.Rotation[2],
224            },
225        }
226    }
227}
228
229/// Utility which converts a `buffer`
230/// of raw C characters into a valid
231/// C string, stripping all `0`s from
232/// the buffer.
233unsafe fn buffer_to_cstring(buffer: Vec<std::os::raw::c_char>) -> std::ffi::CString {
234    // Remove all null bytes,
235    // and add _one_ null byte
236    // to the end.
237    let buffer = buffer
238        .into_iter()
239        .filter(|b| b != &0)
240        .map(|b| b as u8)
241        .collect::<Vec<u8>>();
242
243    std::ffi::CString::new(buffer).unwrap()
244}