Skip to main content

ajazz_rs/
asynchronous.rs

1//! Code from this module is using [block_in_place](tokio::task::block_in_place),
2//! and so they cannot be used in [current_thread](tokio::runtime::Builder::new_current_thread) runtimes
3
4use std::iter::zip;
5use std::sync::Arc;
6use std::time::Duration;
7
8use hidapi::{HidApi, HidResult};
9use image::DynamicImage;
10use tokio::sync::Mutex;
11use tokio::task::block_in_place;
12use tokio::time::sleep;
13
14use crate::{DeviceState, DeviceStateUpdate, Kind, list_devices, Ajazz, AjazzError, AjazzInput};
15use crate::images::{convert_image_async, ImageRect};
16
17/// Actually refreshes the device list, can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
18pub fn refresh_device_list_async(hidapi: &mut HidApi) -> HidResult<()> {
19    block_in_place(move || hidapi.refresh_devices())
20}
21
22/// Returns a list of devices as (Kind, Serial Number) that could be found using HidApi,
23/// can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
24///
25/// **WARNING:** To refresh the list, use [refresh_device_list]
26pub fn list_devices_async(hidapi: &HidApi) -> Vec<(Kind, String)> {
27    block_in_place(move || list_devices(hidapi))
28}
29
30/// Stream Deck interface suitable to be used in async, uses [block_in_place](block_in_place)
31/// so this wrapper cannot be used in [current_thread](tokio::runtime::Builder::new_current_thread) runtimes
32#[derive(Clone)]
33pub struct AsyncAjazz {
34    kind: Kind,
35    device: Arc<Mutex<Ajazz>>,
36}
37
38/// Static functions of the struct
39impl AsyncAjazz {
40    /// Attempts to connect to the device, can be safely ran inside [multi_thread](tokio::runtime::Builder::new_multi_thread) runtime
41    pub fn connect(hidapi: &HidApi, kind: Kind, serial: &str) -> Result<AsyncAjazz, AjazzError> {
42        let device = block_in_place(move || Ajazz::connect(hidapi, kind, serial))?;
43
44        Ok(AsyncAjazz {
45            kind,
46            device: Arc::new(Mutex::new(device)),
47        })
48    }
49}
50
51/// Instance methods of the struct
52impl AsyncAjazz {
53    /// Returns kind of the Stream Deck
54    pub fn kind(&self) -> Kind {
55        self.kind
56    }
57
58    /// Returns manufacturer string of the device
59    pub async fn manufacturer(&self) -> Result<String, AjazzError> {
60        let device = self.device.lock().await;
61        block_in_place(move || device.manufacturer())
62    }
63
64    /// Returns product string of the device
65    pub async fn product(&self) -> Result<String, AjazzError> {
66        let device = self.device.lock().await;
67        block_in_place(move || device.product())
68    }
69
70    /// Returns serial number of the device
71    pub async fn serial_number(&self) -> Result<String, AjazzError> {
72        let device = self.device.lock().await;
73        block_in_place(move || device.serial_number())
74    }
75
76    /// Returns firmware version of the StreamDeck
77    pub async fn firmware_version(&self) -> Result<String, AjazzError> {
78        let device = self.device.lock().await;
79        block_in_place(move || device.firmware_version())
80    }
81
82    /// Reads button states, awaits until there's data.
83    /// Poll rate determines how often button state gets checked
84    pub async fn read_input(&self, poll_rate: f32) -> Result<AjazzInput, AjazzError> {
85        loop {
86            let device = self.device.lock().await;
87            let data = block_in_place(move || device.read_input(None))?;
88
89            if !data.is_empty() {
90                return Ok(data);
91            }
92
93            sleep(Duration::from_secs_f32(1.0 / poll_rate)).await;
94        }
95    }
96
97    /// Resets the device
98    pub async fn reset(&self) -> Result<(), AjazzError> {
99        let device = self.device.lock().await;
100        block_in_place(move || device.reset())
101    }
102
103    /// Sets brightness of the device, value range is 0 - 100
104    pub async fn set_brightness(&self, percent: u8) -> Result<(), AjazzError> {
105        let device = self.device.lock().await;
106        block_in_place(move || device.set_brightness(percent))
107    }
108
109    /// Writes image data to Stream Deck device, changes must be flushed with `.flush()` before
110    /// they will appear on the device!
111    pub async fn write_image(&self, key: u8, image_data: &[u8]) -> Result<(), AjazzError> {
112        let device = self.device.lock().await;
113        block_in_place(move || device.write_image(key, image_data))
114    }
115
116    /// Sets button's image to blank, changes must be flushed with `.flush()` before
117    /// they will appear on the device!
118    pub async fn clear_button_image(&self, key: u8) -> Result<(), AjazzError> {
119        let device = self.device.lock().await;
120        block_in_place(move || device.clear_button_image(key))
121    }
122
123    /// Sets blank images to every button, changes must be flushed with `.flush()` before
124    /// they will appear on the device!
125    pub async fn clear_all_button_images(&self) -> Result<(), AjazzError> {
126        let device = self.device.lock().await;
127        block_in_place(move || device.clear_all_button_images())
128    }
129
130    /// Sets specified button's image, changes must be flushed with `.flush()` before
131    /// they will appear on the device!
132    pub async fn set_button_image(&self, key: u8, image: DynamicImage) -> Result<(), AjazzError> {
133        let image = convert_image_async(self.kind, image)?;
134
135        let device = self.device.lock().await;
136        block_in_place(move || device.write_image(key, &image))
137    }
138
139    /// Set logo image
140    pub async fn set_logo_image(&self, image: DynamicImage) -> Result<(), AjazzError> {
141        let device = self.device.lock().await;
142        block_in_place(move || device.set_logo_image(image))
143    }
144
145    /// Sleeps the device
146    pub async fn sleep(&self) -> Result<(), AjazzError> {
147        let device = self.device.lock().await;
148        block_in_place(move || device.sleep())
149    }
150
151    /// Make periodic events to the device, to keep it alive
152    pub async fn keep_alive(&self) -> Result<(), AjazzError> {
153        let device = self.device.lock().await;
154        block_in_place(move || device.keep_alive())
155    }
156
157    /// Shutdown the device
158    pub async fn shutdown(&self) -> Result<(), AjazzError> {
159        let device = self.device.lock().await;
160        block_in_place(move || device.shutdown())
161    }
162
163    /// Flushes the button's image to the device
164    pub async fn flush(&self) -> Result<(), AjazzError> {
165        let device = self.device.lock().await;
166        block_in_place(move || device.flush())
167    }
168
169    /// Returns button state reader for this device
170    pub fn get_reader(&self) -> Arc<AsyncDeviceStateReader> {
171        Arc::new(AsyncDeviceStateReader {
172            device: self.clone(),
173            states: Mutex::new(DeviceState {
174                buttons: vec![false; self.kind.key_count() as usize],
175                encoders: vec![false; self.kind.encoder_count() as usize],
176            }),
177        })
178    }
179}
180
181/// Button reader that keeps state of the Stream Deck and returns events instead of full states
182pub struct AsyncDeviceStateReader {
183    device: AsyncAjazz,
184    states: Mutex<DeviceState>,
185}
186
187impl AsyncDeviceStateReader {
188    /// Reads states and returns updates
189    pub async fn read(&self, poll_rate: f32) -> Result<Vec<DeviceStateUpdate>, AjazzError> {
190        let input = self.device.read_input(poll_rate).await?;
191        let mut my_states = self.states.lock().await;
192
193        let mut updates = vec![];
194
195        match input {
196            AjazzInput::ButtonStateChange(buttons) => {
197                for (index, is_changed) in buttons.iter().enumerate() {
198                    if !is_changed {
199                        continue;
200                    }
201
202                    my_states.buttons[index] = !my_states.buttons[index];
203                    if my_states.buttons[index] {
204                        updates.push(DeviceStateUpdate::ButtonDown(index as u8));
205                    } else {
206                        updates.push(DeviceStateUpdate::ButtonUp(index as u8));
207                    }
208                }
209            }
210
211            AjazzInput::EncoderStateChange(encoders) => {
212                for (index, is_changed) in encoders.iter().enumerate() {
213                    if !is_changed {
214                        continue;
215                    }
216
217                    my_states.encoders[index] = !my_states.encoders[index];
218                    if my_states.encoders[index] {
219                        updates.push(DeviceStateUpdate::EncoderDown(index as u8));
220                    } else {
221                        updates.push(DeviceStateUpdate::EncoderUp(index as u8));
222                    }
223                }
224            }
225
226            AjazzInput::EncoderTwist(twist) => {
227                for (index, change) in twist.iter().enumerate() {
228                    if *change != 0 {
229                        updates.push(DeviceStateUpdate::EncoderTwist(index as u8, *change));
230                    }
231                }
232            }
233
234            _ => {}
235        }
236
237        drop(my_states);
238
239        Ok(updates)
240    }
241}