gopro_controller/
query.rs

1/// Represents a setting or command query that can be sent to a GoPro device
2pub enum GoProQuery {
3    GetSettingValue(Vec<StatusID>),
4    GetAllSettingValues,
5    GetStatusValue(Vec<StatusID>),
6    GetAllStatusValues,
7    GetAvailableOptionSettings(Vec<StatusID>),
8    GetAvailableOptionAllSettings,
9    RegisterSettingValueUpdates(Vec<StatusID>),
10    RegisterStatusValueUpdates(Vec<StatusID>),
11    RegisterAvailableOptionSettings(Vec<StatusID>),
12    UnregisterSettingValueUpdates(Vec<StatusID>),
13    UnregisterStatusValueUpdates(Vec<StatusID>),
14    UnregisterAvailableOptionSettings(Vec<StatusID>),
15    AsyncNotificationSettingChanged,
16    AsyncNotificationStatusChanged,
17    AsyncNotificationOptionSettingChanged,
18}   
19
20impl AsRef<GoProQuery> for GoProQuery {
21    fn as_ref(&self) -> &GoProQuery {
22        self
23    }
24}
25
26use GoProQuery as GPC;
27impl GoProQuery {
28    /// Returns a byte array that can be sent to a GoPro Query characteristic
29    /// 
30    /// # Note:
31    /// 
32    /// The byte arrays in this implementation were taken directly from the GoPro Open Spec:
33    /// <https://gopro.github.io/OpenGoPro/ble_2_0#query-commands>
34    pub fn as_bytes(&self) -> Vec<u8> {
35        match self.as_ref() {
36            GPC::GetSettingValue(ids) => query_builder(0x12, ids.to_vec()),
37            GPC::GetAllSettingValues => {
38                vec![0x01, 0x12]
39            }
40            GPC::GetStatusValue(ids) => query_builder(0x13, ids.to_vec()),
41            GPC::GetAllStatusValues => {
42                vec![0x01, 0x13]
43            }
44            GPC::GetAvailableOptionSettings(ids) => query_builder(0x32, ids.to_vec()),
45            GPC::GetAvailableOptionAllSettings => {
46                vec![0x01, 0x32]
47            }
48            GPC::RegisterSettingValueUpdates(ids) => query_builder(0x52, ids.to_vec()),
49            GPC::RegisterStatusValueUpdates(ids) => query_builder(0x53, ids.to_vec()),
50            GPC::RegisterAvailableOptionSettings(ids) => query_builder(0x62, ids.to_vec()),
51            GPC::UnregisterSettingValueUpdates(ids) => query_builder(0x72, ids.to_vec()),
52            GPC::UnregisterStatusValueUpdates(ids) => query_builder(0x73, ids.to_vec()),
53            GPC::UnregisterAvailableOptionSettings(ids) => query_builder(0x82, ids.to_vec()),
54
55            //TODO: These seem to not work like the rest of the queries
56            // and definitely shouldn't return the bytes that they do
57            // right now.
58            GPC::AsyncNotificationSettingChanged => {
59                vec![0x01, 0x92]
60            }
61            GPC::AsyncNotificationStatusChanged => {
62                vec![0x01, 0x93]
63            }
64            GPC::AsyncNotificationOptionSettingChanged => {
65                vec![0x01, 0xA2]
66            }
67        }
68    }
69}
70
71/// Builds a proper query byte array from a query id and a vector of status ids
72///
73/// # Arguments
74/// * `query_id` - The id of the query to be sent
75/// * `status_ids` - A vector of status ids to be queried
76///
77/// # Returns
78/// A byte array that can be sent to a GoPro Query characteristic
79fn query_builder(query_id: u8, status_ids: Vec<StatusID>) -> Vec<u8> {
80    let query_body = status_ids
81        .iter()
82        .map(|id| id.as_byte())
83        .collect::<Vec<u8>>();
84    [query_body.len() as u8 + 1, query_id]
85        .into_iter()
86        .chain(query_body)
87        .collect::<Vec<u8>>()
88}
89
90/// Camera Status Identifiers
91/// 
92/// # Note:
93/// 
94/// The integers in this implementation were taken directly from the GoPro Open Spec:
95/// <https://gopro.github.io/OpenGoPro/ble_2_0#status-ids>
96#[derive(Clone, Copy)]
97pub enum StatusID {
98    /// Indicates if the system’s internal battery is present.
99    InternalBatteryPresent = 1,
100
101    /// Approximate level of the internal battery in bars.
102    InternalBatteryLevel = 2,
103
104    /// Indicates if the system is currently overheating.
105    SystemHot = 6,
106
107    /// Indicates if the camera is busy.
108    SystemBusy = 8,
109
110    /// Indicates if Quick Capture feature is enabled.
111    QuickCaptureActive = 9,
112
113    /// Indicates if the system is currently encoding.
114    EncodingActive = 10,
115
116    /// Indicates if LCD lock is active.
117    LCDLockActive = 11,
118
119    /// Duration (in seconds) of the video encoding; 0 otherwise.
120    VideoProgressCounter = 13,
121
122    /// Are Wireless Connections enabled?
123    WirelessConnectionsEnabled = 17,
124
125    /// The pairing state of the camera.
126    PairingState = 19,
127
128    /// The last type of pairing the camera engaged in.
129    LastPairingType = 20,
130
131    /// Time (milliseconds) since boot of last successful pairing complete action.
132    PairTime = 21,
133
134    /// State of current scan for WiFi Access Points.
135    WiFiScanState = 22,
136
137    /// The time, in milliseconds since boot, that the WiFi Access Point scan completed.
138    WiFiScanTimeMsec = 23,
139
140    /// WiFi AP provisioning state.
141    WiFiProvisionStatus = 24,
142
143    /// Wireless remote control version.
144    RemoteControlVersion = 26,
145
146    /// Indicates if a wireless remote control is connected.
147    RemoteControlConnected = 27,
148
149    /// Wireless Pairing State.
150    WirelessPairingState = 28,
151
152    /// Provisioned WIFI AP SSID.
153    WlanSSID = 29,
154
155    /// Camera’s WIFI SSID.
156    ApSSID = 30,
157
158    /// The number of wireless devices connected to the camera.
159    AppCount = 31,
160
161    /// Indicates if Preview Stream is enabled.
162    PreviewStreamEnabled = 32,
163
164    /// Primary Storage Status.
165    SdStatus = 33,
166
167    /// How many photos can be taken before sdcard is full.
168    RemainingPhotos = 34,
169
170    /// How many minutes of video can be captured before sdcard is full.
171    RemainingVideoTime = 35,
172
173    /// How many group photos can be taken before sdcard is full.
174    NumGroupPhotos = 36,
175
176    /// Total number of group videos on sdcard.
177    NumGroupVideos = 37,
178
179    /// Total number of photos on sdcard.
180    NumTotalPhotos = 38,
181
182    /// Total number of videos on sdcard.
183    NumTotalVideos = 39,
184
185    /// The current status of Over The Air (OTA) update.
186    OtaStatus = 41,
187
188    /// Indicates if there is a pending request to cancel a firmware update download.
189    DownloadCancelRequestPending = 42,
190
191    /// Indicates if the locate camera feature is active.
192    CameraLocateActive = 45,
193
194    /// The current timelapse interval countdown value.
195    MultiShotCountDown = 49,
196
197    /// Remaining space on the sdcard in Kilobytes.
198    RemainingSpace = 54,
199
200    /// Indicates if preview stream is supported in current mode.
201    PreviewStreamSupported = 55,
202
203    /// WiFi signal strength in bars.
204    WiFiBars = 56,
205
206    /// The number of hilights in encoding video.
207    NumHilights = 58,
208
209    /// Time since boot (msec) of most recent hilight in encoding video.
210    LastHilightTimeMsec = 59,
211
212    /// The min time between camera status updates (msec).
213    NextPollMsec = 60,
214
215    /// How many minutes of Timelapse video can be captured before sdcard is full.
216    RemainingTimelapseTime = 64,
217
218    /// Liveview Exposure Select Mode.
219    ExposureSelectType = 65,
220
221    /// Liveview Exposure Select: x-coordinate (percent).
222    ExposureSelectX = 66,
223
224    /// Liveview Exposure Select: y-coordinate (percent).
225    ExposureSelectY = 67,
226
227    /// Indicates if the camera currently has a GPS lock.
228    GpsStatus = 68,
229
230    /// Indicates if the camera is in AP Mode.
231    ApState = 69,
232
233    /// Internal battery level (percent).
234    InternalBatteryPercentage = 70,
235
236    /// Microphone Accessory status.
237    AccMicStatus = 74,
238
239    /// Digital Zoom level (percent).
240    DigitalZoom = 75,
241
242    /// Wireless Band.
243    WirelessBand = 76,
244
245    /// Indicates if Digital Zoom feature is available.
246    DigitalZoomActive = 77,
247
248    /// Indicates if current video settings are mobile friendly.
249    MobileFriendlyVideo = 78,
250
251    /// Indicates if the camera is in First Time Use (FTU) UI flow.
252    FirstTimeUse = 79,
253
254    /// Indicates if 5GHz wireless band is available.
255    Band5ghzAvailable = 81,
256
257    /// Indicates if the system is ready to accept commands.
258    SystemReady = 82,
259
260    /// Indicates if the internal battery is charged sufficiently for OTA update.
261    BattOkayForOta = 83,
262
263    /// Indicates if the camera is getting too cold to continue recording.
264    VideoLowTempAlert = 85,
265
266    /// The rotational orientation of the camera.
267    ActualOrientation = 86,
268
269    /// Indicates if the camera can zoom while encoding.
270    ZoomWhileEncoding = 88,
271
272    /// Current flatmode ID.
273    CurrentMode = 89,
274
275    /// Current Video Preset (ID).
276    ActiveVideoPresets = 93,
277
278    /// Current Photo Preset (ID).
279    ActivePhotoPresets = 94,
280
281    /// Current Timelapse Preset (ID).
282    ActiveTimelapsePresets = 95,
283
284    /// Current Preset Group (ID).
285    ActivePresetsGroup = 96,
286
287    /// Current Preset (ID).
288    ActivePreset = 97,
289
290    /// Preset Modified Status.
291    PresetModified = 98,
292
293    /// How many Live Bursts can be captured before sdcard is full.
294    RemainingLiveBursts = 99,
295
296    /// Total number of Live Bursts on sdcard.
297    NumTotalLiveBursts = 100,
298
299    /// Indicates if Capture Delay is currently active.
300    CaptureDelayActive = 101,
301
302    /// Media mod State.
303    MediaModMicStatus = 102,
304
305    /// Time Warp Speed.
306    TimewarpSpeedRampActive = 103,
307
308    /// Indicates if the system’s Linux core is active.
309    LinuxCoreActive = 104,
310
311    /// Camera lens type.
312    CameraLensType = 105,
313
314    /// Indicates if Video Hindsight Capture is Active.
315    VideoHindsightCaptureActive = 106,
316
317    /// Scheduled Capture Preset ID.
318    ScheduledPreset = 107,
319
320    /// Indicates if Scheduled Capture is set.
321    ScheduledEnabled = 108,
322
323    /// Media Mode Status.
324    MediaModStatus = 110,
325
326    /// Indicates if sdcard meets specified minimum write speed.
327    SdRatingCheckError = 111,
328
329    /// Number of sdcard write speed errors since device booted.
330    SdWriteSpeedError = 112,
331
332    /// Indicates if Turbo Transfer is active.
333    TurboTransfer = 113,
334
335    /// Camera control status ID.
336    CameraControlStatus = 114,
337
338    /// Indicates if the camera is connected to a PC via USB.
339    UsbConnected = 115,
340
341    /// Camera control over USB state.
342    AllowControlOverUsb = 116,
343
344    /// Total SD card capacity in Kilobytes.
345    TotalSDSpaceKB = 117,
346}
347
348impl StatusID {
349    /// Returns the byte representation of the status id
350    fn as_byte(self) -> u8 {
351        if self as u16 > u8::MAX as u16 {
352            panic!("Invalid StatusID, must be less than 255")
353        }
354        self as u8
355    }
356}
357
358#[derive(Debug)]
359/// Represents the response to a query sent to a GoPro device
360pub struct QueryResponse {
361    pub message_length: u16,
362    /// The query id that was sent
363    pub query_id: u8,
364    pub command_status: u8,
365    /// The status id that was queried
366    pub status_id: u8,
367    pub status_value_length: u8,
368    /// The actual value that the caller is interested in
369    pub status_value: Vec<u8>,
370}
371
372//TODO: Need functionality for interpreting the status_value field
373// in a way that makes sense given the status_id that was queried
374
375impl QueryResponse {
376    pub fn deserialize(data: &[u8]) -> Result<Self, &'static str> {
377        if data.len() < 5 {
378            // Header is at least 5 bytes
379            return Err("Data too short for header");
380        }
381
382        let message_length = if data[0] & 0x80 == 0 {
383            // If the first bit is not set, it's a single byte length
384            u16::from(data[0])
385        } else {
386            // If the first bit is set, we assume the length is specified by two bytes
387            if data.len() < 6 {
388                // Need at least 6 bytes if message length is encoded in 2 bytes
389                return Err("Data too short for extended length header");
390            }
391            u16::from_be_bytes([data[0] & 0x7F, data[1]]) // Masking to remove the leading 1
392        };
393
394        let base_index = if message_length > 0x7F { 2 } else { 1 };
395
396        // Gather the rest of the information after the length
397        let query_id = data[base_index];
398        let command_status = data[base_index + 1];
399        let status_id = data[base_index + 2];
400        let status_value_length = data[base_index + 3];
401
402        if data.len() < base_index + 4 + status_value_length as usize {
403            return Err("Data too short for status value");
404        }
405
406        // Gather the status value(s)
407        let status_value_start = base_index + 4;
408        let status_value_end = status_value_start + status_value_length as usize;
409        let status_value = data[status_value_start..status_value_end].to_vec();
410
411        Ok(QueryResponse {
412            message_length,
413            query_id,
414            command_status,
415            status_id,
416            status_value_length,
417            status_value,
418        })
419    }
420}
421
422#[test]
423/// Sanity test for query builder
424fn test_query_builder() {
425    let query = query_builder(
426        0x01,
427        vec![
428            StatusID::InternalBatteryLevel,
429            StatusID::InternalBatteryPresent,
430        ],
431    );
432    assert_eq!(query, vec![0x03, 0x01, 0x02, 0x01]);
433}