Skip to main content

beyond_lib/
usb.rs

1//! An implementation of the Bigscreen Beyond's protocols, enabled through the
2//! `usb` feature.
3
4use std::{
5    path::Path,
6    sync::{Arc, Mutex},
7    time::Duration,
8};
9
10use dashmap::DashMap;
11use deku::DekuContainerRead;
12use flume::{Receiver, RecvTimeoutError, Sender};
13use hidapi::{HidApi, HidDevice, HidError};
14use thiserror::Error;
15use tracing::{error, info_span, trace};
16
17use crate::{
18    ParseError,
19    device_ids::{BEYOND_PID, BEYOND_PID_FIRMWARE, BIGSCREEN_VID},
20    helpers::{Brightness, DisplayMode, RgbColor},
21    protocol::{
22        CommandIds, HID_REPORT_SIZE, TIMESTAT_STEP,
23        memory::{BANK_CAPACITY, BANK_COUNT, ConfigurationMemory, MEMORY_SIZE},
24        status::DeviceStatus,
25    },
26};
27
28#[derive(Debug, Clone, Error)]
29pub enum DeviceError {
30    #[error("HID error: {0}")]
31    HidError(String),
32    #[error("the returned message was an incorrect size ({0} != {HID_REPORT_SIZE})")]
33    ReturnMessageBadSize(usize),
34    #[error("HMD took too long to respond (over {CMD_TIMEOUT}ms)")]
35    Timeout,
36    #[error("Device is closed")]
37    Closed,
38    #[error(transparent)]
39    Parse(#[from] ParseError),
40}
41impl From<HidError> for DeviceError {
42    fn from(e: HidError) -> Self {
43        Self::HidError(e.to_string())
44    }
45}
46impl From<RecvTimeoutError> for DeviceError {
47    fn from(e: RecvTimeoutError) -> Self {
48        match e {
49            RecvTimeoutError::Timeout => Self::Timeout,
50            RecvTimeoutError::Disconnected => todo!(),
51        }
52    }
53}
54
55type DeviceResult<T> = Result<T, DeviceError>;
56
57pub const CMD_TIMEOUT: u64 = 10_000;
58pub const HID_TIMEOUT: i32 = 10_000;
59
60type Data = [u8; HID_REPORT_SIZE];
61
62struct EventDispatcher {
63    listeners: Arc<DashMap<u8, Sender<DeviceResult<Data>>>>,
64
65    // Transmit None to stop, and Some to change the device
66    dispatch_tx: Sender<Option<HidDevice>>,
67    // Current state of the dispatcher thread
68    state: Arc<Mutex<Result<(), DeviceError>>>,
69}
70
71impl EventDispatcher {
72    fn dispatcher(
73        device: HidDevice,
74        listeners: Arc<DashMap<u8, Sender<DeviceResult<Data>>>>,
75        state: Arc<Mutex<DeviceResult<()>>>,
76    ) -> Sender<Option<HidDevice>> {
77        let (tx, rx) = flume::bounded::<Option<HidDevice>>(1);
78
79        let _handle = std::thread::spawn(move || {
80            let span = info_span!("receiver_thread");
81            let _handle = span.enter();
82
83            let mut data: Data = [0u8; HID_REPORT_SIZE];
84            let mut device: Option<HidDevice> = Some(device);
85
86            loop {
87                match rx.try_recv() {
88                    // If empty, then there either isn't any data to be received
89                    Err(flume::TryRecvError::Empty) => {}
90                    Err(flume::TryRecvError::Disconnected) => {
91                        panic!("Socket disconnected")
92                    }
93                    Ok(None) => {
94                        device = None;
95                        *state.try_lock().unwrap() = Err(DeviceError::Closed);
96                    }
97                    Ok(Some(dev)) => {
98                        device = Some(dev);
99                        *state.try_lock().unwrap() = Ok(());
100                    }
101                }
102
103                // If there is no device set, then we just wait till we just
104                // wait until we receive a new one
105                if device.is_none() {
106                    loop {
107                        if let Some(dev) = rx.recv().expect("Socket disconnected") {
108                            device = Some(dev);
109                            *state.try_lock().unwrap() = Ok(());
110                            break;
111                        }
112                    }
113                }
114
115                // The state should always match up with whether the device is
116                // available or not
117                assert_eq!(
118                    state.lock().unwrap().is_ok(),
119                    device.is_some(),
120                    "Struct state & local device state should always match"
121                );
122
123                let dev: &HidDevice = device.as_ref().unwrap_or_else(|| unreachable!());
124
125                match dev.read_timeout(&mut data, HID_TIMEOUT) {
126                    // Only actual ok state
127                    // (all other states are considered errors)
128                    Ok(HID_REPORT_SIZE) => {
129                        trace!(?data, "Received HID interrupt");
130                    }
131
132                    Ok(len) => {
133                        trace!(%len, ?data, "Returned data has incorrect size (must be {HID_REPORT_SIZE})");
134                        let err = DeviceError::ReturnMessageBadSize(len);
135                        Self::dispatch_error(&listeners, &err);
136                        *state.try_lock().unwrap() = Err(err);
137
138                        device = None;
139                        continue;
140                    }
141                    Err(e) => {
142                        error!(error = ?e, "There was an HID error");
143                        let err = e.into();
144                        Self::dispatch_error(&listeners, &err);
145                        *state.try_lock().unwrap() = Err(err);
146
147                        device = None;
148                        continue;
149                    }
150                }
151
152                let id = data[0];
153
154                let Some(listener) = listeners.get(&id) else {
155                    trace!(%id, "No listeners found (dropping packet)");
156                    continue;
157                };
158
159                if listener.try_send(Ok(data)).is_err() {
160                    listeners.remove(&id);
161                }
162            }
163        });
164
165        tx
166    }
167
168    fn dispatch_error(listeners: &DashMap<u8, Sender<DeviceResult<Data>>>, error: &DeviceError) {
169        listeners.iter().for_each(|l| {
170            // We don't care if it is closed
171            let _ = l.try_send(Err(error.clone()));
172        });
173        listeners.clear();
174    }
175
176    pub fn new(device: HidDevice) -> Self {
177        let listeners = Arc::new(DashMap::with_capacity(256));
178
179        let state = Arc::new(Mutex::new(Ok(())));
180        let dispatch_tx = Self::dispatcher(device, Arc::clone(&listeners), Arc::clone(&state));
181
182        Self {
183            listeners,
184            dispatch_tx,
185            state,
186        }
187    }
188
189    pub fn state(&self) -> Result<(), DeviceError> {
190        self.state.lock().unwrap().clone()
191    }
192    pub fn stop(&self) {
193        Self::dispatch_error(&self.listeners, &DeviceError::Closed);
194        self.dispatch_tx.send(None).unwrap();
195    }
196    pub fn change_device(&self, dev: HidDevice) {
197        self.dispatch_tx.send(Some(dev)).unwrap();
198    }
199
200    /// Will subscribe for receiving data, who's first byte starts with ID.
201    ///
202    /// # Errors
203    ///
204    /// Will error if the device is in a bad state due to a previous error.
205    /// If so, please [`Self::change_device`] to update the active device.
206    pub fn subscribe(&self, id: u8) -> DeviceResult<Receiver<DeviceResult<Data>>> {
207        self.state()?;
208
209        let (tx, rx) = flume::bounded(1);
210
211        if let Some(prev) = self.listeners.insert(id, tx) {
212            // TODO: properly handle this
213            assert!(
214                prev.is_disconnected(),
215                "Previously opened socket still open"
216            );
217        }
218
219        Ok(rx)
220    }
221}
222
223/// The main Bigscreen Beyond device wrapper which lower-level functions are
224/// stored.
225struct DeviceWrapper {
226    /// Sending device handle.
227    dev: Mutex<HidDevice>,
228    dispatcher: EventDispatcher,
229    locks: Box<[Mutex<()>; 256]>,
230}
231
232impl DeviceWrapper {
233    pub fn new(api: &mut HidApi) -> Result<Self, HidError> {
234        // As we cannot share the device across threads, we create 2 devices.
235        // One for sending, and one for receiving
236        api.refresh_devices()?;
237        let out_dev = Self::open_main_device(api)?;
238        let in_dev = Self::open_main_device(api)?;
239
240        Ok(Self {
241            dev: out_dev.into(),
242            dispatcher: EventDispatcher::new(in_dev),
243            locks: Box::new(std::array::from_fn(|_| Mutex::new(()))),
244        })
245    }
246
247    fn open_main_device(api: &HidApi) -> Result<HidDevice, HidError> {
248        let dev = api.open(BIGSCREEN_VID, BEYOND_PID)?;
249        dev.set_blocking_mode(true)?;
250
251        Ok(dev)
252    }
253
254    pub fn refresh_device(&self, api: &HidApi) -> Result<(), HidError> {
255        let out_dev = Self::open_main_device(api)?;
256        let in_dev = Self::open_main_device(api)?;
257
258        *self.dev.try_lock().unwrap() = out_dev;
259        self.dispatcher.change_device(in_dev);
260        Ok(())
261    }
262
263    #[inline]
264    fn dev(&self) -> std::sync::MutexGuard<'_, HidDevice> {
265        self.dev.lock().unwrap()
266    }
267
268    fn get_hid_manufacturer(&self) -> Result<Option<String>, HidError> {
269        self.dev().get_manufacturer_string()
270    }
271    fn get_hid_product(&self) -> Result<Option<String>, HidError> {
272        self.dev().get_product_string()
273    }
274    fn get_hid_serial(&self) -> Result<Option<String>, HidError> {
275        self.dev().get_serial_number_string()
276    }
277
278    /// Sends data to the device.
279    ///
280    /// The `id` is the first byte, followed by `data`.
281    ///
282    /// # Panics
283    ///
284    /// `data` must be under `{HID_REPORT_SIZE}` bytes; if it isn't, this
285    /// function will panic.
286    pub fn send(&self, id: u8, data: &[u8]) -> Result<(), HidError> {
287        assert!(
288            data.len() < HID_REPORT_SIZE,
289            "HID event must be under {HID_REPORT_SIZE} bytes long."
290        );
291        let mut buf = [0u8; HID_REPORT_SIZE + 1];
292        buf[1] = id;
293        buf[2..(data.len() + 2)].copy_from_slice(data);
294
295        trace!(%id, ?data, "Sending data");
296        self.dev().send_feature_report(&buf)
297    }
298
299    /// Halts until the HMD sends a response starting with `response_id`.
300    pub fn wait_for(&self, response_id: u8) -> DeviceResult<Data> {
301        let _lock = self.locks[response_id as usize].lock().unwrap();
302        let rx = self.dispatcher.subscribe(response_id).unwrap();
303
304        rx.recv_timeout(Duration::from_millis(CMD_TIMEOUT))?
305    }
306
307    /// Sends data to the device, and waits for the HMD to respond.
308    ///
309    /// The `id` is the first byte, followed by `data`.
310    /// The function will halt until the HMD sends a response starting with
311    /// `response_id`.
312    ///
313    /// # Panics
314    ///
315    /// `data` must be under `{HID_REPORT_SIZE}` bytes; if it isn't, this
316    /// function will panic.
317    pub fn send_and_wait(&self, send_id: u8, data: &[u8], response_id: u8) -> DeviceResult<Data> {
318        let _lock = self.locks[response_id as usize].lock().unwrap();
319        let rx = self.dispatcher.subscribe(response_id).unwrap();
320
321        self.send(send_id, data).unwrap();
322
323        rx.recv_timeout(Duration::from_millis(CMD_TIMEOUT))?
324    }
325
326    /// Sends data to the device, and waits for the HMD to acknowledge.
327    ///
328    /// The `id` is the first byte, followed by `data`.
329    /// This function will halt until the HMD responds with
330    /// `{CommandIds::AckCommand.id()}`.
331    pub fn send_and_wait_ack(&self, id: u8, data: &[u8]) -> DeviceResult<()> {
332        let _ = self.send_and_wait(id, data, CommandIds::Ack.id())?;
333        Ok(())
334    }
335}
336
337/// Main handler for the Bigscreen Beyond device.
338///
339/// # Examples
340///
341/// After initializing, you can use the various included functions to change the
342/// state of the Bigscreen Beyond device.
343///
344/// ```no_run
345/// # use beyond_lib::usb::BSBDevice;
346/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
347/// let dev = BSBDevice::new()?;
348///
349/// dev.set_fan_speed(50)?;
350/// #     Ok(())
351/// # }
352/// ```
353///
354/// A lot of the time, you'll need to permanently modify the memory.
355/// Here's an example on how to do that:
356///
357/// ```no_run
358/// # use beyond_lib::usb::BSBDevice;
359/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
360/// let dev = BSBDevice::new()?;
361///
362/// let mut memory = dev.read_memory()?;
363/// memory.set_fan(50);
364/// dev.write_memory(&memory)?;
365/// #     Ok(())
366/// # }
367/// ```
368#[derive(Clone)]
369pub struct BSBDevice {
370    // We store a global hidapi reference so that we don't need to initialize it
371    // every time we wanna do something with it.
372    hidapi: Arc<Mutex<HidApi>>,
373    device: Arc<DeviceWrapper>,
374}
375impl BSBDevice {
376    pub fn new() -> Result<Self, DeviceError> {
377        let mut hidapi = HidApi::new()?;
378        let dev = DeviceWrapper::new(&mut hidapi)?;
379
380        Ok(Self {
381            hidapi: Arc::new(Mutex::new(hidapi)),
382            device: Arc::new(dev),
383        })
384    }
385
386    /// Fetches the manufacturer string through the underlying HID event.
387    pub fn get_hid_manufacturer(&self) -> DeviceResult<Option<String>> {
388        Ok(self.device.get_hid_manufacturer()?)
389    }
390    /// Fetches the product string through the underlying HID event.
391    pub fn get_hid_product(&self) -> DeviceResult<Option<String>> {
392        Ok(self.device.get_hid_product()?)
393    }
394    /// Fetches the serial number string through the underlying HID event.
395    ///
396    /// Note that this is the "primary serial", and is normally hidden to the
397    /// user.
398    /// If you are looking for the serial, check out
399    /// [`Self::get_secondary_serial`], which gets the "secondary serial"
400    /// starting with "BS".
401    pub fn get_hid_serial(&self) -> DeviceResult<Option<String>> {
402        Ok(self.device.get_hid_serial()?)
403    }
404
405    #[inline]
406    fn str_stat(&self, id: u8) -> DeviceResult<String> {
407        let buf = self.device.send_and_wait(id, &[], id)?;
408
409        let size = buf
410            .iter()
411            // The first byte is the ID
412            .skip(1)
413            // Strings are null terminated
414            .position(|&x| x == 0)
415            .unwrap_or(HID_REPORT_SIZE - 1);
416        Ok(String::from_utf8(buf[1..=size].to_vec()).unwrap())
417    }
418    /// Fetches the "secondary serial".
419    ///
420    /// This is the value shown on the utility, and starts with "BS".
421    pub fn get_secondary_serial(&self) -> DeviceResult<String> {
422        self.str_stat(CommandIds::SecondarySerial.id())
423    }
424    /// Fetches the active firmware version.
425    pub fn get_firmware_version(&self) -> DeviceResult<String> {
426        self.str_stat(CommandIds::FirmwareVersion.id())
427    }
428
429    /// Temporarily activates the fan, setting its speed.
430    pub fn set_fan_speed(&self, v: u8) -> DeviceResult<()> {
431        self.device.send_and_wait_ack(CommandIds::SetFan.id(), &[v])
432    }
433    /// Temporarily modifies the current brightness.
434    pub fn set_brightness(&self, v: &Brightness) -> DeviceResult<()> {
435        self.device
436            .send_and_wait_ack(CommandIds::SetBrightness.id(), &v.0.to_be_bytes())
437    }
438    /// Temporarily modifies the LED color.
439    pub fn set_led_color(&self, v: &RgbColor) -> DeviceResult<()> {
440        self.device
441            .send_and_wait_ack(CommandIds::SetLed.id(), &v.as_bytes())
442    }
443    /// Modifies & saves the display mode.
444    pub fn set_display_mode(&self, v: &DisplayMode) -> DeviceResult<()> {
445        let mut memory = self.read_memory()?;
446        if &memory.display_mode()? == v {
447            return Ok(());
448        }
449
450        self.device
451            .send_and_wait_ack(CommandIds::SetMode.id(), &[v.id()])?;
452        // SAFETY: We do wait for an extra ack at the end of writing the memory.
453        unsafe { memory.set_display_mode(*v) };
454
455        self.write_memory(&memory)?;
456        let _ = self.device.wait_for(CommandIds::Ack.id())?;
457        Ok(())
458    }
459
460    /// Returns in minutes, to the closest [`TIMESTAT_STEP`].
461    #[inline]
462    fn time_stat(&self, v: u8) -> DeviceResult<u32> {
463        const C: u8 = CommandIds::TimeStat.id();
464
465        let data = self.device.send_and_wait(C, &[v], C)?;
466        Ok(
467            u32::from_le_bytes(data[1..=4].try_into().unwrap_or_else(|_| {
468                unreachable!("Data is always 64 bytes long. We will always have enough to parse")
469            })) * TIMESTAT_STEP,
470        )
471    }
472    /// Total time the HMD has had power, but no usage.
473    ///
474    /// Returns in minutes, to the closest [`TIMESTAT_STEP`].
475    pub fn get_time_idle(&self) -> DeviceResult<u32> {
476        self.time_stat(0)
477    }
478    /// Total usage time.
479    ///
480    /// Returns in minutes, to the closest [`TIMESTAT_STEP`].
481    pub fn get_time_usage(&self) -> DeviceResult<u32> {
482        self.time_stat(1)
483    }
484    /// Time in the longest session, where a session is the continuous
485    /// activation of the proximity sensor.
486    ///
487    /// Returns in minutes, to the closest [`TIMESTAT_STEP`].
488    pub fn get_time_longest(&self) -> DeviceResult<u32> {
489        self.time_stat(2)
490    }
491
492    /// Reads the static configuration memory.
493    pub fn read_memory(&self) -> DeviceResult<ConfigurationMemory> {
494        const C: u8 = CommandIds::ReadMemory.id();
495        let mut memory = [0u8; MEMORY_SIZE];
496
497        for i in 0..BANK_COUNT {
498            let buf = self.device.send_and_wait(C, &[i], C)?;
499            // TODO: Error checking
500            assert_eq!(buf[1], BANK_CAPACITY, "Bank capacity must be known");
501
502            memory[(i as usize * BANK_CAPACITY as usize)
503                ..((i as usize + 1) * BANK_CAPACITY as usize)]
504                .copy_from_slice(&buf[2..34]);
505        }
506
507        Ok(ConfigurationMemory::try_from_bytes(&memory)?)
508    }
509    /// Writes to the static configuration memory.
510    ///
511    /// This persists when the headset looses power.
512    pub fn write_memory(&self, data: &ConfigurationMemory) -> DeviceResult<()> {
513        let memory = ConfigurationMemory::to_blocks(data);
514
515        for (i, dat) in memory.iter().enumerate() {
516            let mut buf = [0u8; BANK_CAPACITY as usize + 1];
517            buf[0] = u8::try_from(i).unwrap_or_else(|_| {
518                unreachable!("There will never be more blocks than the size of a u8")
519            });
520            buf[1..=(BANK_CAPACITY as usize)].copy_from_slice(dat);
521
522            self.device
523                .send_and_wait_ack(CommandIds::WriteMemory.id(), &buf)?;
524        }
525
526        self.device
527            .send_and_wait_ack(CommandIds::CommitMemory.id(), &[])?;
528        Ok(())
529    }
530
531    pub fn get_status(&self) -> DeviceResult<DeviceStatus> {
532        let buf = self.device.wait_for(CommandIds::DeviceStat.id())?;
533
534        Ok(DeviceStatus::from_bytes((&buf, 0)).unwrap().1)
535    }
536
537    // TODO: This is unfinished code
538    #[allow(unused)]
539    fn update_firmware(&self, path: &Path) {
540        // The dispatcher will not be useful during the updater
541        // (especially as the device PID will change)
542        self.device.dispatcher.stop();
543
544        // Enter flash mode
545        self.device
546            .send(CommandIds::FirmwareFlash.id(), &[])
547            .unwrap();
548
549        let api = self.hidapi.lock().unwrap();
550        let dev = api.open(BIGSCREEN_VID, BEYOND_PID_FIRMWARE).unwrap();
551
552        // TODO: Magic!
553
554        let mut buf = [0u8; 65];
555        buf[1] = CommandIds::FirmwareFlash.id();
556        dev.send_feature_report(&buf).unwrap();
557
558        // Finally, refresh the device to leave things back to a good state
559        self.device.refresh_device(&api).unwrap();
560    }
561
562    // TODO: This is unfinished code
563    #[cfg(feature = "et-dfu")]
564    #[allow(unused)]
565    fn update_bigeye_firmware(&self, path: &Path) {
566        use dfu_libusb::{Dfu, DfuLibusb};
567        use tracing::info;
568
569        use crate::device_ids::BIGEYE_PID_DFU;
570
571        self.device
572            .send_and_wait_ack(
573                CommandIds::BigeyeCommand.id(),
574                &[CommandIds::FirmwareFlash.id()],
575            )
576            .unwrap();
577
578        // TODO: Wait for device to show up
579        let ctx = rusb::Context::new().unwrap();
580        let device: Dfu<rusb::Context> =
581            DfuLibusb::open(&ctx, BIGSCREEN_VID, BIGEYE_PID_DFU, 0, 0).unwrap();
582
583        let file = std::fs::File::open(path).unwrap();
584        #[allow(clippy::single_match_else)]
585        match device.download_all(file) {
586            Ok(Some(_)) => info!("DFU firmware succsessful"),
587            _ => error!("DFU firmware download failed"),
588        }
589
590        self.device.send_and_wait_ack(
591            CommandIds::BigeyeCommand.id(),
592            &[CommandIds::FirmwareFlash.id()],
593        );
594    }
595}