Skip to main content

lib/
sensor.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::path::Path;
6use std::{fs, io};
7
8use anyhow::{Context as _, Error, format_err};
9use async_trait::async_trait;
10use fidl_fuchsia_input_report::{
11    DeviceDescriptor, InputDeviceMarker, InputDeviceProxy, InputReportsReaderMarker,
12    InputReportsReaderProxy, SensorInputDescriptor, SensorType,
13};
14
15#[derive(Debug)]
16pub struct AmbientLightInputRpt {
17    pub illuminance: f32,
18    pub red: f32,
19    pub green: f32,
20    pub blue: f32,
21}
22
23struct AmbientLightComponent {
24    pub report_index: usize,
25    pub exponent: i32,
26    pub report_id: u8, // report ID associated with descriptor
27}
28
29struct AmbientLightInputReportReaderProxy {
30    pub proxy: InputReportsReaderProxy,
31
32    pub illuminance: Option<AmbientLightComponent>,
33    pub red: Option<AmbientLightComponent>,
34    pub green: Option<AmbientLightComponent>,
35    pub blue: Option<AmbientLightComponent>,
36}
37
38fn open_input_report_device(path: &str) -> Result<InputDeviceProxy, Error> {
39    log::info!("Opening sensor at {:?}", path);
40    let (proxy, server) = fidl::endpoints::create_proxy::<InputDeviceMarker>();
41    fdio::service_connect(path, server.into_channel())
42        .context("Failed to connect built-in service")?;
43    Ok(proxy)
44}
45
46async fn open_sensor_input_report_reader() -> Result<AmbientLightInputReportReaderProxy, Error> {
47    let input_report_directory = "/dev/class/input-report";
48    let dir_path = Path::new(input_report_directory);
49    let entries = fs::read_dir(dir_path)?;
50    for entry in entries {
51        let entry = entry?;
52        let device_path = entry.path();
53        let device_path = device_path.to_str().expect("Bad path");
54        let device = open_input_report_device(device_path)?;
55
56        fn get_sensor_input(
57            descriptor: &DeviceDescriptor,
58        ) -> Result<&Vec<SensorInputDescriptor>, Error> {
59            let sensor = descriptor.sensor.as_ref().context("device has no sensor")?;
60            let input_desc = sensor.input.as_ref().context("sensor has no input descriptor")?;
61            Ok(input_desc)
62        }
63
64        if let Ok(descriptor) = device.get_descriptor().await {
65            match get_sensor_input(&descriptor) {
66                Ok(input_desc) => {
67                    let mut illuminance = None;
68                    let mut red = None;
69                    let mut green = None;
70                    let mut blue = None;
71
72                    for input in input_desc {
73                        let axes =
74                            input.values.as_ref().context("SensorInputDescriptor has no values")?;
75                        for (i, val) in axes.iter().enumerate() {
76                            let component = AmbientLightComponent {
77                                report_index: i,
78                                exponent: val.axis.unit.exponent,
79                                report_id: input.report_id.unwrap_or(0),
80                            };
81                            match val.type_ {
82                                SensorType::LightIlluminance => illuminance = Some(component),
83                                SensorType::LightRed => red = Some(component),
84                                SensorType::LightGreen => green = Some(component),
85                                SensorType::LightBlue => blue = Some(component),
86                                _ => {}
87                            }
88                        }
89                    }
90
91                    if illuminance.is_some() {
92                        let (proxy, server_end) =
93                            fidl::endpoints::create_proxy::<InputReportsReaderMarker>();
94                        if let Ok(()) = device.get_input_reports_reader(server_end) {
95                            return Ok(AmbientLightInputReportReaderProxy {
96                                proxy: proxy,
97                                illuminance,
98                                red,
99                                blue,
100                                green,
101                            });
102                        }
103                    }
104                }
105                Err(e) => {
106                    log::info!("Skip device {}: {}", device_path, e);
107                }
108            };
109        }
110    }
111    Err(io::Error::new(io::ErrorKind::NotFound, "no sensor found").into())
112}
113
114/// Reads the sensor's input report and decodes it.
115async fn read_sensor_input_report(
116    device: &AmbientLightInputReportReaderProxy,
117) -> Result<Option<AmbientLightInputRpt>, Error> {
118    let r = device.proxy.read_input_reports().await;
119
120    match r {
121        Ok(Ok(reports)) => {
122            for report in reports {
123                if report.report_id.unwrap_or(0) != device.illuminance.as_ref().unwrap().report_id {
124                    continue;
125                }
126
127                let sensor =
128                    report.sensor.context("sensor required in InputReport for sensor device")?;
129                let values = sensor.values.context("values required in SensorInputReport")?;
130                let f = |component: &Option<AmbientLightComponent>| match component {
131                    Some(val) => match val.exponent {
132                        0 => values[val.report_index] as f32,
133                        _ => values[val.report_index] as f32 * f32::powf(10.0, val.exponent as f32),
134                    },
135                    None => 0.0,
136                };
137
138                let illuminance = f(&device.illuminance);
139                let red = f(&device.red);
140                let green = f(&device.green);
141                let blue = f(&device.blue);
142
143                return Ok(Some(AmbientLightInputRpt { illuminance, red, blue, green }));
144            }
145            Ok(None)
146        }
147        Ok(Err(e)) => Err(format_err!("ReadInputReports error: {}", e)),
148        Err(e) => Err(format_err!("FIDL call failed: {}", e)),
149    }
150}
151
152/// TODO(lingxueluo) Default and temporary report when sensor is not valid(https://fxbug.dev/42119013).
153fn default_report() -> Result<Option<AmbientLightInputRpt>, Error> {
154    Ok(Some(AmbientLightInputRpt { illuminance: 200.0, red: 200.0, green: 200.0, blue: 200.0 }))
155}
156
157pub struct Sensor {
158    proxy: Option<AmbientLightInputReportReaderProxy>,
159}
160
161impl Sensor {
162    pub async fn new() -> Sensor {
163        let proxy = open_sensor_input_report_reader().await;
164        match proxy {
165            Ok(proxy) => return Sensor { proxy: Some(proxy) },
166            Err(_e) => {
167                println!("No valid sensor found.");
168                return Sensor { proxy: None };
169            }
170        }
171    }
172
173    async fn read(&self) -> Result<Option<AmbientLightInputRpt>, Error> {
174        if self.proxy.is_none() {
175            default_report()
176        } else {
177            read_sensor_input_report(self.proxy.as_ref().unwrap()).await
178        }
179    }
180}
181
182#[async_trait]
183pub trait SensorControl: Send {
184    async fn read(&self) -> Result<Option<AmbientLightInputRpt>, Error>;
185}
186
187#[async_trait]
188impl SensorControl for Sensor {
189    async fn read(&self) -> Result<Option<AmbientLightInputRpt>, Error> {
190        self.read().await
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use fuchsia_async as fasync;
198
199    #[fasync::run_singlethreaded(test)]
200    async fn test_open_sensor_error() {
201        let sensor = Sensor { proxy: None };
202        if let Some(ambient_light_input_rpt) = sensor.read().await.unwrap() {
203            assert_eq!(ambient_light_input_rpt.illuminance, 200.0);
204            assert_eq!(ambient_light_input_rpt.red, 200.0);
205            assert_eq!(ambient_light_input_rpt.green, 200.0);
206            assert_eq!(ambient_light_input_rpt.blue, 200.0);
207        }
208    }
209}