Skip to main content

mtp_rs/ptp/session/
properties.rs

1//! Device property operations.
2//!
3//! This module contains methods for getting and setting device properties,
4//! primarily used for digital cameras to query/set settings like ISO, aperture, etc.
5
6use crate::ptp::{
7    DevicePropDesc, DevicePropertyCode, OperationCode, PropertyDataType, PropertyValue,
8};
9use crate::Error;
10
11use super::PtpSession;
12
13impl PtpSession {
14    // =========================================================================
15    // Device property operations
16    // =========================================================================
17
18    /// Get the descriptor for a device property.
19    ///
20    /// Returns detailed information about the property including its type,
21    /// current value, default value, and allowed values/range.
22    ///
23    /// This is primarily used for digital cameras to query settings like
24    /// ISO, aperture, shutter speed, etc. Most Android MTP devices do not
25    /// support device properties.
26    ///
27    /// # Arguments
28    ///
29    /// * `property` - The device property code to query
30    ///
31    /// # Example
32    ///
33    /// ```rust,ignore
34    /// let desc = session.get_device_prop_desc(DevicePropertyCode::BatteryLevel).await?;
35    /// println!("Battery level: {:?}", desc.current_value);
36    /// ```
37    pub async fn get_device_prop_desc(
38        &self,
39        property: DevicePropertyCode,
40    ) -> Result<DevicePropDesc, Error> {
41        let (response, data) = self
42            .execute_with_receive(
43                OperationCode::GetDevicePropDesc,
44                &[u16::from(property) as u32],
45            )
46            .await?;
47        Self::check_response(&response, OperationCode::GetDevicePropDesc)?;
48        DevicePropDesc::from_bytes(&data)
49    }
50
51    /// Get the current value of a device property.
52    ///
53    /// Returns the raw bytes of the property value. To interpret the value,
54    /// you need to know the property's data type. Use `get_device_prop_desc()`
55    /// to get the full descriptor including the data type.
56    ///
57    /// # Arguments
58    ///
59    /// * `property` - The device property code to query
60    pub async fn get_device_prop_value(
61        &self,
62        property: DevicePropertyCode,
63    ) -> Result<Vec<u8>, Error> {
64        let (response, data) = self
65            .execute_with_receive(
66                OperationCode::GetDevicePropValue,
67                &[u16::from(property) as u32],
68            )
69            .await?;
70        Self::check_response(&response, OperationCode::GetDevicePropValue)?;
71        Ok(data)
72    }
73
74    /// Get a device property value as a typed PropertyValue.
75    ///
76    /// This is a convenience method that parses the raw bytes according to
77    /// the specified data type.
78    ///
79    /// # Arguments
80    ///
81    /// * `property` - The device property code to query
82    /// * `data_type` - The expected data type of the property
83    pub async fn get_device_prop_value_typed(
84        &self,
85        property: DevicePropertyCode,
86        data_type: PropertyDataType,
87    ) -> Result<PropertyValue, Error> {
88        let data = self.get_device_prop_value(property).await?;
89        let (value, _) = PropertyValue::from_bytes(&data, data_type)?;
90        Ok(value)
91    }
92
93    /// Set a device property value.
94    ///
95    /// The value should be the raw bytes of the new value. The value type
96    /// must match the property's data type.
97    ///
98    /// # Arguments
99    ///
100    /// * `property` - The device property code to set
101    /// * `value` - The raw bytes of the new value
102    pub async fn set_device_prop_value(
103        &self,
104        property: DevicePropertyCode,
105        value: &[u8],
106    ) -> Result<(), Error> {
107        let response = self
108            .execute_with_send(
109                OperationCode::SetDevicePropValue,
110                &[u16::from(property) as u32],
111                value,
112            )
113            .await?;
114        Self::check_response(&response, OperationCode::SetDevicePropValue)?;
115        Ok(())
116    }
117
118    /// Set a device property value from a PropertyValue.
119    ///
120    /// This is a convenience method that serializes the PropertyValue to bytes.
121    ///
122    /// # Arguments
123    ///
124    /// * `property` - The device property code to set
125    /// * `value` - The new value
126    pub async fn set_device_prop_value_typed(
127        &self,
128        property: DevicePropertyCode,
129        value: &PropertyValue,
130    ) -> Result<(), Error> {
131        let data = value.to_bytes();
132        self.set_device_prop_value(property, &data).await
133    }
134
135    /// Reset a device property to its default value.
136    ///
137    /// # Arguments
138    ///
139    /// * `property` - The device property code to reset
140    pub async fn reset_device_prop_value(&self, property: DevicePropertyCode) -> Result<(), Error> {
141        let response = self
142            .execute(
143                OperationCode::ResetDevicePropValue,
144                &[u16::from(property) as u32],
145            )
146            .await?;
147        Self::check_response(&response, OperationCode::ResetDevicePropValue)?;
148        Ok(())
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155    use crate::ptp::session::tests::{
156        data_container, mock_transport, ok_response, response_with_params,
157    };
158    use crate::ptp::{pack_u16, ResponseCode};
159
160    /// Build a battery level property descriptor for testing.
161    fn build_battery_prop_desc(current: u8) -> Vec<u8> {
162        let mut buf = Vec::new();
163        // PropertyCode: 0x5001 (BatteryLevel)
164        buf.extend_from_slice(&pack_u16(0x5001));
165        // DataType: UINT8 (0x0002)
166        buf.extend_from_slice(&pack_u16(0x0002));
167        // GetSet: read-only (0x00)
168        buf.push(0x00);
169        // DefaultValue: 100
170        buf.push(100);
171        // CurrentValue
172        buf.push(current);
173        // FormFlag: Range (0x01)
174        buf.push(0x01);
175        // Range: min=0, max=100, step=1
176        buf.push(0); // min
177        buf.push(100); // max
178        buf.push(1); // step
179        buf
180    }
181
182    #[tokio::test]
183    async fn test_get_device_prop_desc() {
184        let (transport, mock) = mock_transport();
185        mock.queue_response(ok_response(1)); // OpenSession
186
187        // Queue battery level prop desc
188        let prop_desc_data = build_battery_prop_desc(75);
189        mock.queue_response(data_container(
190            2,
191            OperationCode::GetDevicePropDesc,
192            &prop_desc_data,
193        ));
194        mock.queue_response(ok_response(2));
195
196        let session = PtpSession::open(transport, 1).await.unwrap();
197        let desc = session
198            .get_device_prop_desc(DevicePropertyCode::BatteryLevel)
199            .await
200            .unwrap();
201
202        assert_eq!(desc.property_code, DevicePropertyCode::BatteryLevel);
203        assert_eq!(desc.data_type, PropertyDataType::Uint8);
204        assert!(!desc.writable);
205        assert_eq!(desc.current_value, PropertyValue::Uint8(75));
206    }
207
208    #[tokio::test]
209    async fn test_get_device_prop_desc_not_supported() {
210        let (transport, mock) = mock_transport();
211        mock.queue_response(ok_response(1)); // OpenSession
212        mock.queue_response(response_with_params(
213            2,
214            ResponseCode::DevicePropNotSupported,
215            &[],
216        ));
217
218        let session = PtpSession::open(transport, 1).await.unwrap();
219        let result = session
220            .get_device_prop_desc(DevicePropertyCode::BatteryLevel)
221            .await;
222
223        assert!(matches!(
224            result,
225            Err(crate::Error::Protocol {
226                code: ResponseCode::DevicePropNotSupported,
227                ..
228            })
229        ));
230    }
231
232    #[tokio::test]
233    async fn test_get_device_prop_value() {
234        let (transport, mock) = mock_transport();
235        mock.queue_response(ok_response(1)); // OpenSession
236
237        // Queue battery level value (75%)
238        let value_data = vec![75u8];
239        mock.queue_response(data_container(
240            2,
241            OperationCode::GetDevicePropValue,
242            &value_data,
243        ));
244        mock.queue_response(ok_response(2));
245
246        let session = PtpSession::open(transport, 1).await.unwrap();
247        let data = session
248            .get_device_prop_value(DevicePropertyCode::BatteryLevel)
249            .await
250            .unwrap();
251
252        assert_eq!(data, vec![75u8]);
253    }
254
255    #[tokio::test]
256    async fn test_get_device_prop_value_typed() {
257        let (transport, mock) = mock_transport();
258        mock.queue_response(ok_response(1)); // OpenSession
259
260        // Queue ISO value (400 = 0x0190)
261        let value_data = vec![0x90, 0x01]; // 400 in little-endian
262        mock.queue_response(data_container(
263            2,
264            OperationCode::GetDevicePropValue,
265            &value_data,
266        ));
267        mock.queue_response(ok_response(2));
268
269        let session = PtpSession::open(transport, 1).await.unwrap();
270        let value = session
271            .get_device_prop_value_typed(
272                DevicePropertyCode::ExposureIndex,
273                PropertyDataType::Uint16,
274            )
275            .await
276            .unwrap();
277
278        assert_eq!(value, PropertyValue::Uint16(400));
279    }
280
281    #[tokio::test]
282    async fn test_set_device_prop_value() {
283        let (transport, mock) = mock_transport();
284        mock.queue_response(ok_response(1)); // OpenSession
285        mock.queue_response(ok_response(2)); // SetDevicePropValue
286
287        let session = PtpSession::open(transport, 1).await.unwrap();
288        let value = vec![0x90, 0x01]; // 400 in little-endian
289        session
290            .set_device_prop_value(DevicePropertyCode::ExposureIndex, &value)
291            .await
292            .unwrap();
293    }
294
295    #[tokio::test]
296    async fn test_set_device_prop_value_typed() {
297        let (transport, mock) = mock_transport();
298        mock.queue_response(ok_response(1)); // OpenSession
299        mock.queue_response(ok_response(2)); // SetDevicePropValue
300
301        let session = PtpSession::open(transport, 1).await.unwrap();
302        session
303            .set_device_prop_value_typed(
304                DevicePropertyCode::ExposureIndex,
305                &PropertyValue::Uint16(400),
306            )
307            .await
308            .unwrap();
309    }
310
311    #[tokio::test]
312    async fn test_set_device_prop_value_invalid() {
313        let (transport, mock) = mock_transport();
314        mock.queue_response(ok_response(1)); // OpenSession
315        mock.queue_response(response_with_params(
316            2,
317            ResponseCode::InvalidDevicePropValue,
318            &[],
319        ));
320
321        let session = PtpSession::open(transport, 1).await.unwrap();
322        let result = session
323            .set_device_prop_value(DevicePropertyCode::ExposureIndex, &[0x00, 0x00])
324            .await;
325
326        assert!(matches!(
327            result,
328            Err(crate::Error::Protocol {
329                code: ResponseCode::InvalidDevicePropValue,
330                ..
331            })
332        ));
333    }
334
335    #[tokio::test]
336    async fn test_reset_device_prop_value() {
337        let (transport, mock) = mock_transport();
338        mock.queue_response(ok_response(1)); // OpenSession
339        mock.queue_response(ok_response(2)); // ResetDevicePropValue
340
341        let session = PtpSession::open(transport, 1).await.unwrap();
342        session
343            .reset_device_prop_value(DevicePropertyCode::ExposureIndex)
344            .await
345            .unwrap();
346    }
347}