blackmagic_camera_control/
blecamera.rs

1use crate::command::Command;
2use crate::error::BluetoothCameraError;
3use crate::rawcommand::{Operation, RawCommand};
4use btleplug::api::{
5    Central, Characteristic, Manager as _, Peripheral as _, ScanFilter, ValueNotification,
6};
7use btleplug::platform::{Adapter, Manager, Peripheral};
8use futures::stream::StreamExt;
9use std::collections::HashMap;
10use std::pin::Pin;
11use std::sync::Arc;
12use std::time::Duration;
13use tokio::sync::broadcast::{self, Receiver, Sender};
14use tokio::sync::RwLock;
15use tokio::time;
16use uuid::Uuid;
17
18pub const CAMERA_SERVICE: Uuid = Uuid::from_u128(54650678423016196498641639054569411539);
19pub const CAMERA_MANUFACTURER: Uuid = Uuid::from_u128(855109558092022082745622393992443);
20pub const CAMERA_MODEL: Uuid = Uuid::from_u128(854713417279450761057654674240763);
21pub const OUTGOING_CAMERA_CONTROL: Uuid = Uuid::from_u128(124715205548830368390231916378743955899);
22pub const INCOMING_CAMERA_CONTROL: Uuid = Uuid::from_u128(245101749559754194128926468485788547033);
23pub const TIMECODE: Uuid = Uuid::from_u128(145629020620256484157652687441451644616);
24pub const CAMERA_STATUS: Uuid = Uuid::from_u128(170018700332869099062316608707586904505);
25pub const DEVICE_NAME: Uuid = Uuid::from_u128(339846463932956345205123112215954503836);
26pub const PROTOCOL_VERSION: Uuid = Uuid::from_u128(190244785298557795456958317949635929862);
27
28#[allow(dead_code)]
29pub struct BluetoothCamera {
30    name: String,
31
32    bluetooth_manager: Manager,
33    adapter: Adapter,
34
35    device: Option<Peripheral>,
36
37    write_char: Option<Characteristic>,
38    read_char: Option<Characteristic>,
39
40    updates: Sender<Command>,
41    cache: Arc<RwLock<HashMap<String, Command>>>,
42}
43
44impl BluetoothCamera {
45    /// Takes the BLE name of the camera and returns a new BluetoothCamera instance
46    ///
47    /// # Arguments
48    ///
49    /// * `name` - &str representing the Bluetooth name of the camera such as "A:5CA7128B"
50    pub async fn new(name: &str) -> Result<BluetoothCamera, BluetoothCameraError> {
51        let bluetooth_manager = Manager::new().await?;
52
53        let adapter = bluetooth_manager.adapters().await?;
54
55        let adapter = adapter
56            .into_iter()
57            .nth(0)
58            .ok_or(BluetoothCameraError::NoBluetooth)?;
59
60        Ok(BluetoothCamera {
61            name: name.to_string(),
62            bluetooth_manager,
63            adapter,
64            device: None,
65
66            write_char: None,
67            read_char: None,
68
69            updates: broadcast::channel(16).0,
70            cache: Arc::new(RwLock::new(HashMap::new())),
71        })
72    }
73
74    /// Tries to connect to the camera, waiting as long as supplied timeout specifies
75    ///
76    /// # Arguments
77    ///
78    /// * `timeout` - std::Duration of how long to wait before giving up
79    pub async fn connect(&mut self, timeout: Duration) -> Result<(), BluetoothCameraError> {
80        let now = time::Instant::now();
81        self.adapter
82            .start_scan(ScanFilter {
83                services: vec![CAMERA_SERVICE],
84            })
85            .await?;
86
87        loop {
88            // TODO Port this to TokioTimeout
89            if now.elapsed().as_millis() > timeout.as_millis() {
90                break;
91            }
92
93            match self.find_camera().await {
94                Ok(v) => {
95                    v.connect().await?;
96                    self.device = Some(v);
97
98                    // TODO: fix this hack once the underlying bluetooth library supports
99                    // actually reporting when the connection is "established".
100                    // Right now on you crash the library if you try to write on OSX
101                    // without waiting and the is_connected() endpoint is hardcoded
102                    // to return "false" on OSX. Oh well, SLEEP it is.
103
104                    time::sleep(Duration::from_millis(500)).await;
105
106                    let device = self
107                        .device
108                        .as_ref()
109                        .ok_or(BluetoothCameraError::DevRefError)?;
110
111                    // Seed the characteristics list.
112                    device.discover_services().await?;
113
114                    let char = device.characteristics();
115
116                    let inc = char
117                        .iter()
118                        .find(|c| c.uuid == INCOMING_CAMERA_CONTROL)
119                        .ok_or(BluetoothCameraError::NoCharacteristic)?;
120
121                    self.read_char = Some(inc.to_owned());
122
123                    let ouc = char
124                        .iter()
125                        .find(|c| c.uuid == OUTGOING_CAMERA_CONTROL)
126                        .ok_or(BluetoothCameraError::NoCharacteristic)?;
127
128                    self.write_char = Some(ouc.to_owned());
129
130                    // Subscribe to Incoming Camera Control
131                    device
132                        .subscribe(
133                            self.read_char
134                                .as_ref()
135                                .ok_or(BluetoothCameraError::DevRefError)?,
136                        )
137                        .await?;
138                    let stream = device.notifications().await?;
139
140                    let ble_cache = self.cache.clone();
141                    let ble_updates = self.updates.clone();
142                    tokio::spawn(async move {
143                        BluetoothCamera::handle_incoming(ble_cache, ble_updates, stream).await;
144                    });
145
146                    return Ok(());
147                }
148                Err(_) => {}
149            }
150
151            time::sleep(Duration::from_millis(50)).await;
152        }
153
154        Err(BluetoothCameraError::ConnectError)
155    }
156
157    /// Disconnects from the camera
158    ///
159    /// NOTE: THIS ACTUALLY DOESN'T WORK ON OSX BECAUSE THE UNDERLYING LIBRARY IS PEPEGA
160    pub async fn disconnect(&mut self) -> Result<(), BluetoothCameraError> {
161        Ok(self
162            .device
163            .as_ref()
164            .ok_or(BluetoothCameraError::DevRefError)?
165            .disconnect()
166            .await?)
167    }
168
169    pub async fn write(
170        &mut self,
171        destination: u8,
172        operation: Operation,
173        command: Command,
174    ) -> Result<(), BluetoothCameraError> {
175        let device = self
176            .device
177            .as_ref()
178            .ok_or(BluetoothCameraError::SendError)?;
179
180        device
181            .write(
182                self.write_char
183                    .as_ref()
184                    .ok_or(BluetoothCameraError::NoCharacteristic)?,
185                &RawCommand::to_raw(destination, operation, &command),
186                btleplug::api::WriteType::WithoutResponse,
187            )
188            .await?;
189
190        Ok(())
191    }
192
193    async fn handle_incoming(
194        cache: Arc<RwLock<HashMap<String, Command>>>,
195        updates: Sender<Command>,
196        mut stream: Pin<Box<dyn futures::Stream<Item = ValueNotification> + Send>>,
197    ) {
198        while let Some(data) = stream.next().await {
199            let cmd = Command::from_raw(&data.value);
200            match cmd {
201                Ok(v) => {
202                    let (cg, pr) = v.normalized_name();
203                    cache
204                        .write()
205                        .await
206                        .insert(format!("{}_{}", cg, pr), v.clone());
207                    let _ = updates.send(v.clone());
208                }
209                Err(_) => {}
210            }
211        }
212    }
213
214    /// Gives you the latest cached version of the supplied Command
215    /// If no cached version is found, returns the empty property
216    ///
217    /// # Arguments
218    ///
219    /// * `cmd` - Command like this: Command::Metadata(Metadata::LensDistance("".to_string()))
220    pub async fn get(&self, cmd: Command) -> Command {
221        let (cg, pr) = cmd.normalized_name();
222        match self
223            .cache
224            .clone()
225            .read()
226            .await
227            .get(&format! {"{}_{}", &cg, &pr})
228        {
229            Some(c) => c.clone(),
230            None => cmd,
231        }
232    }
233
234    /// Gives you the latest cached version of the supplied normalized_name
235    ///
236    /// # Arguments
237    ///
238    /// * `normalized_name` - &str like this: metadata_lens_distance
239    pub async fn get_normalized(&self, normalized_name: &str) -> Option<Command> {
240        match self.cache.clone().read().await.get(normalized_name) {
241            Some(c) => Some(c.clone()),
242            None => None,
243        }
244    }
245
246    /// Returns a channel which allows you to get updates from the camera
247    pub async fn updates(&mut self) -> Receiver<Command> {
248        self.updates.subscribe()
249    }
250
251    async fn find_camera(&self) -> Result<Peripheral, BluetoothCameraError> {
252        for p in self.adapter.peripherals().await? {
253            if p.properties()
254                .await?
255                .ok_or(BluetoothCameraError::DiscoveryError)?
256                .local_name
257                .iter()
258                .any(|name| name.contains(&self.name))
259            {
260                return Ok(p);
261            }
262        }
263        Err(BluetoothCameraError::CameraNotFound(self.name.to_string()))
264    }
265}