ble_ledly/
controller.rs

1use crate::device::{CharKind, Device, UuidKind};
2use crate::error::BluetoothError;
3use btleplug::api::{Central, Manager as _, Peripheral as _, ScanFilter};
4use btleplug::platform::{Adapter, Manager};
5use std::time::Duration;
6use tokio::time;
7
8pub struct Controller<D: Device> {
9    prefix: Option<String>,
10
11    ble_manager: Manager,
12    ble_adapter: Adapter,
13
14    //TODO: provide key-like access - hashmap
15    devices: Vec<D>,
16}
17
18impl<D: Device> Controller<D> {
19    /// Creates a new `Device` controller
20    ///
21    /// # Examples
22    ///
23    ///
24    /// ```no_run
25    /// use ble_ledly::device::LedDevice;
26    /// use ble_ledly::Controller;
27    /// use std::error::Error;
28    ///
29    ///  async fn test() -> Result<(), Box<dyn Error>> {
30    ///     let mut controller = Controller::<LedDevice>::new().await?;
31    ///     Ok(())
32    /// }
33    /// ```
34    pub async fn new() -> Result<Controller<D>, BluetoothError> {
35        let ble_manager = Manager::new().await?;
36
37        let ble_adapter = ble_manager.adapters().await?;
38        let client = ble_adapter
39            .into_iter()
40            .nth(0) // take first
41            .ok_or(BluetoothError::InvalidBluetoothAdapter)?;
42
43        Ok(Self {
44            prefix: None,
45            ble_manager,
46            ble_adapter: client,
47            devices: Vec::<D>::new(),
48        })
49    }
50
51    /// Creates a new `Device` controller with `Prefix`.
52    /// The `prefix` is used to automatically filter the
53    /// devices found during `device_discovery()`; The filter looks
54    /// if the `prefix` __id contained__ in the device name.
55    ///
56    /// # Examples
57    ///
58    /// ```no_run
59    /// use ble_ledly::device::LedDevice;
60    /// use ble_ledly::Controller;
61    /// use std::error::Error;
62    ///
63    ///  async fn test() -> Result<(), Box<dyn Error>> {
64    ///     let mut controller = Controller::<LedDevice>::new_with_prefix("QHM-").await?;
65    ///     Ok(())
66    /// }
67    /// ```
68    pub async fn new_with_prefix(prefix: &str) -> Result<Controller<D>, BluetoothError> {
69        let ble_manager = Manager::new().await?;
70
71        let ble_adapter = ble_manager.adapters().await?;
72        let client = ble_adapter
73            .into_iter()
74            .nth(0) // take first
75            .ok_or(BluetoothError::InvalidBluetoothAdapter)?;
76
77        Ok(Self {
78            prefix: Some(prefix.to_string()),
79            ble_manager,
80            ble_adapter: client,
81            devices: Vec::<D>::new(),
82        })
83    }
84
85    /// Sets all the devices default _Characteristic_ (Write or Read)
86    /// to the provided value. Global shortcut, instead of setting, per-device _Characteristic_
87    /// configuration
88    ///
89    /// # Examples
90    ///
91    /// ```compile_fail
92    /// controller.set_all_char(&CharKind::Write, &UuidKind::Uuid16(0xFFD9))?;
93    /// ````
94    pub fn set_all_char(
95        &mut self,
96        char_kind: &CharKind,
97        uuid_kind: &UuidKind,
98    ) -> Result<(), BluetoothError> {
99        self.devices
100            .iter_mut()
101            .map(|device| device.set_char(char_kind, uuid_kind))
102            .collect::<Result<(), BluetoothError>>()?;
103        Ok(())
104    }
105
106    //---------//
107    // Getters //
108    //---------//
109    pub fn ble_manager(&self) -> &Manager {
110        &self.ble_manager
111    }
112
113    /// Return a list (Vec<D>) of all the connected devices.
114    /// The list is empty until devices are connected to the controller.
115    ///
116    /// # Examples
117    ///
118    /// ```no_run
119    /// use ble_ledly::device::LedDevice;
120    /// use ble_ledly::Controller;
121    /// use std::error::Error;
122    ///
123    ///  async fn test() -> Result<(), Box<dyn Error>> {
124    ///     let mut controller = Controller::<LedDevice>::new_with_prefix("QHM-").await?;
125    ///     let connected_lights = controller.list();
126    ///     Ok(())
127    /// }
128    pub fn list(&mut self) -> &mut Vec<D> {
129        &mut self.devices
130    }
131
132    //------------------//
133    // Device Discovery //
134    //------------------//
135    /// Discover _ble devices_ by running a scan op. on the default adapter
136    /// and returns the found _devices__.
137    ///
138    /// # Examples
139    ///
140    /// ```no_run
141    /// use ble_ledly::device::LedDevice;
142    /// use ble_ledly::Controller;
143    /// use std::error::Error;
144    ///
145    ///  async fn test() -> Result<(), Box<dyn Error>> {
146    ///     let mut controller = Controller::<LedDevice>::new_with_prefix("QHM-").await?;
147    ///     let led_devices = controller.device_discovery().await?;
148    ///     Ok(())
149    /// }
150    pub async fn device_discovery(&self) -> Result<Vec<D>, BluetoothError> {
151        self.ble_adapter.start_scan(ScanFilter::default()).await?;
152        time::sleep(Duration::from_secs(2)).await;
153
154        let mut devices: Vec<D> = Vec::new();
155
156        for p in self.ble_adapter.peripherals().await? {
157            let name = &p
158                .properties()
159                .await?
160                .ok_or(BluetoothError::InvalidPeriperipheralProperty)?
161                .local_name
162                .unwrap_or(String::from("Unknown"));
163
164            if name.contains(self.prefix.as_ref().unwrap_or(&"".to_string())) {
165                devices.push(D::new(&name, &name, Some(p), None, None));
166            }
167        }
168        Ok(devices)
169    }
170    //---------//
171    // Connect //
172    //---------//
173    /// Standalone `connect()` that can be used in conjunction with
174    /// `Controller::new_with_prefix(prfix)`; it automatically runs
175    /// a `device_discovery()` and connects to all devices that match `prefix`.
176    /// If no `prefix` is provided it attemps to connect to all available devices.
177    ///
178    /// # Examples
179    ///
180    /// ```no_run
181    /// use ble_ledly::device::LedDevice;
182    /// use ble_ledly::Controller;
183    /// use std::error::Error;
184    ///
185    ///  async fn test() -> Result<(), Box<dyn Error>> {
186    ///     let mut controller = Controller::<LedDevice>::new_with_prefix("QHM-").await?;
187    ///     controller.connect().await?;
188    ///     Ok(())
189    /// }
190    pub async fn connect(&mut self) -> Result<(), BluetoothError> {
191        // Discover devices //
192        let devices = self.device_discovery().await?;
193        self._connect(devices).await?;
194        Ok(())
195    }
196
197    /// Connect to the devices passed as function's argument.
198    ///
199    /// # Examples
200    ///
201    /// ```compile_fail
202    /// let led_devices = controller.device_discovery().await?;
203    /// filter devices
204    /// let lights: Vec<LedDevice> = led_devices
205    ///    .into_iter()
206    ///   .filter(|device| device.name.contains("QHM-"))
207    ///    .collect();
208    /// controller.connect_with_devices(lights).await?;
209    ///
210    /// ```
211    pub async fn connect_with_devices(&mut self, devices: Vec<D>) -> Result<(), BluetoothError> {
212        self._connect(devices).await?;
213        Ok(())
214    }
215    async fn _connect(&mut self, devices: Vec<D>) -> Result<(), BluetoothError> {
216        self.devices = devices;
217
218        // Connect devices //
219        for device in self.devices.iter_mut() {
220            // Connect //
221            device
222                .peripheral()
223                .as_ref()
224                .ok_or(BluetoothError::InvalidPeripheralReference)?
225                .connect()
226                .await?;
227
228            // Service discovry //
229            device
230                .peripheral()
231                .as_ref()
232                .ok_or(BluetoothError::InvalidPeripheralReference)?
233                .discover_services()
234                .await?;
235        }
236        Ok(())
237    }
238}