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}