1use crate::input_device::{
6 Handled, InputDeviceDescriptor, InputDeviceEvent, InputEvent, InputEventType,
7};
8use crate::input_handler::{InputHandler, InputHandlerStatus};
9use crate::inspect_handler::{BufferNode, CircularBuffer};
10use crate::light_sensor::calibrator::{Calibrate, Calibrator};
11use crate::light_sensor::led_watcher::{CancelableTask, LedWatcher, LedWatcherHandle};
12use crate::light_sensor::types::{AdjustmentSetting, Calibration, Rgbc, SensorConfiguration};
13use anyhow::{Context, Error, format_err};
14use async_trait::async_trait;
15use async_utils::hanging_get::server::HangingGet;
16use fidl_fuchsia_input_report::{FeatureReport, InputDeviceProxy, SensorFeatureReport};
17use fidl_fuchsia_lightsensor::{
18 LightSensorData as FidlLightSensorData, Rgbc as FidlRgbc, SensorRequest, SensorRequestStream,
19 SensorWatchResponder,
20};
21use fidl_fuchsia_settings::LightProxy;
22use fidl_fuchsia_ui_brightness::ControlProxy as BrightnessControlProxy;
23use fuchsia_inspect::NumericProperty;
24use fuchsia_inspect::health::Reporter;
25
26use futures::channel::oneshot;
27use futures::lock::Mutex;
28use futures::{Future, FutureExt, TryStreamExt};
29use std::cell::RefCell;
30use std::rc::Rc;
31use std::sync::Arc;
32
33type NotifyFn = Box<dyn Fn(&LightSensorData, SensorWatchResponder) -> bool>;
34type SensorHangingGet = HangingGet<LightSensorData, SensorWatchResponder, NotifyFn>;
35
36const MIN_TIME_STEP_US: u32 = 2780;
39const MAX_GAIN: u32 = 64;
41const MAX_COUNT_PER_CYCLE: u32 = 1024;
43const MAX_SATURATION: u32 = u16::MAX as u32;
45const MAX_ATIME: u32 = 256;
46const ADC_SCALING_FACTOR: f32 = 64.0 * 256.0;
48const GAIN_UP_MARGIN_DIVISOR: u32 = 10;
50const TRANSITION_SCALING_FACTOR: f32 = 4.0;
52
53#[derive(Copy, Clone, Debug)]
54struct LightReading {
55 rgbc: Rgbc<f32>,
56 si_rgbc: Rgbc<f32>,
57 is_calibrated: bool,
58 lux: f32,
59 cct: Option<f32>,
60}
61
62fn num_cycles(atime: u32) -> u32 {
63 MAX_ATIME - atime
64}
65
66#[cfg_attr(test, derive(Debug))]
67struct ActiveSetting {
68 settings: Vec<AdjustmentSetting>,
69 idx: usize,
70}
71
72impl ActiveSetting {
73 fn new(settings: Vec<AdjustmentSetting>, idx: usize) -> Self {
74 Self { settings, idx }
75 }
76
77 async fn adjust<Fut>(
81 &mut self,
82 reading: Rgbc<u16>,
83 device_proxy: &InputDeviceProxy,
84 track_feature_update: impl Fn(FeatureEvent) -> Fut,
85 ) -> Result<bool, SaturatedError>
86 where
87 Fut: Future<Output = ()>,
88 {
89 let saturation_point =
90 (num_cycles(self.active_setting().atime) * MAX_COUNT_PER_CYCLE).min(MAX_SATURATION);
91 let gain_up_margin = saturation_point / GAIN_UP_MARGIN_DIVISOR;
92
93 let step_change = self.step_change();
94 let mut pull_up = true;
95
96 if saturated(reading) {
97 if self.adjust_down() {
98 log::info!("adjusting down due to saturation sentinel");
99 self.update_device(&device_proxy, track_feature_update)
100 .await
101 .context("updating light sensor device")?;
102 }
103 return Err(SaturatedError::Saturated);
104 }
105
106 for value in [reading.red, reading.green, reading.blue, reading.clear] {
107 let value = value as u32;
108 if value >= saturation_point {
109 if self.adjust_down() {
110 log::info!("adjusting down due to saturation point");
111 self.update_device(&device_proxy, track_feature_update)
112 .await
113 .context("updating light sensor device")?;
114 }
115 return Err(SaturatedError::Saturated);
116 } else if (value * step_change + gain_up_margin) >= saturation_point {
117 pull_up = false;
118 }
119 }
120
121 if pull_up {
122 if self.adjust_up() {
123 log::info!("adjusting up");
124 self.update_device(&device_proxy, track_feature_update)
125 .await
126 .context("updating light sensor device")?;
127 return Ok(true);
128 }
129 }
130
131 Ok(false)
132 }
133
134 async fn update_device<Fut>(
135 &self,
136 device_proxy: &InputDeviceProxy,
137 track_feature_update: impl Fn(FeatureEvent) -> Fut,
138 ) -> Result<(), Error>
139 where
140 Fut: Future<Output = ()>,
141 {
142 let active_setting = self.active_setting();
143 let feature_report = device_proxy
144 .get_feature_report()
145 .await
146 .context("calling get_feature_report")?
147 .map_err(|e| {
148 format_err!(
149 "getting feature report on light sensor device: {:?}",
150 zx::Status::from_raw(e),
151 )
152 })?;
153 let feature_report = FeatureReport {
154 sensor: Some(SensorFeatureReport {
155 sensitivity: Some(vec![active_setting.gain as i64]),
156 sampling_rate: Some(to_us(active_setting.atime) as i64),
158 ..(feature_report
159 .sensor
160 .ok_or_else(|| format_err!("missing sensor in feature_report"))?)
161 }),
162 ..feature_report
163 };
164 device_proxy
165 .set_feature_report(&feature_report)
166 .await
167 .context("calling set_feature_report")?
168 .map_err(|e| {
169 format_err!(
170 "updating feature report on light sensor device: {:?}",
171 zx::Status::from_raw(e),
172 )
173 })?;
174 if let Some(feature_event) = FeatureEvent::maybe_new(feature_report) {
175 (track_feature_update)(feature_event).await;
176 }
177 Ok(())
178 }
179
180 fn active_setting(&self) -> AdjustmentSetting {
181 self.settings[self.idx]
182 }
183
184 fn adjust_down(&mut self) -> bool {
186 if self.idx == 0 {
187 false
188 } else {
189 self.idx -= 1;
190 true
191 }
192 }
193
194 fn step_change(&self) -> u32 {
196 let current = self.active_setting();
197 let new = match self.settings.get(self.idx + 1) {
198 Some(setting) => *setting,
199 None => return 1,
202 };
203 div_round_up(new.gain, current.gain) * div_round_up(to_us(new.atime), to_us(current.atime))
204 }
205
206 fn adjust_up(&mut self) -> bool {
208 if self.idx == self.settings.len() - 1 {
209 false
210 } else {
211 self.idx += 1;
212 true
213 }
214 }
215}
216
217struct FeatureEvent {
218 event_time: zx::MonotonicInstant,
219 sampling_rate: i64,
220 sensitivity: i64,
221}
222
223impl FeatureEvent {
224 fn maybe_new(report: FeatureReport) -> Option<Self> {
225 let sensor = report.sensor?;
226 Some(FeatureEvent {
227 sampling_rate: sensor.sampling_rate?,
228 sensitivity: *sensor.sensitivity?.get(0)?,
229 event_time: zx::MonotonicInstant::get(),
230 })
231 }
232}
233
234impl BufferNode for FeatureEvent {
235 fn get_name(&self) -> &'static str {
236 "feature_report_update_event"
237 }
238
239 fn record_inspect(&self, node: &fuchsia_inspect::Node) {
240 node.record_int("sampling_rate", self.sampling_rate);
241 node.record_int("sensitivity", self.sensitivity);
242 node.record_int("event_time", self.event_time.into_nanos());
243 }
244}
245
246pub struct LightSensorHandler<T> {
247 hanging_get: RefCell<SensorHangingGet>,
248 calibrator: Option<T>,
249 active_setting: RefCell<ActiveSettingState>,
250 rgbc_to_lux_coefs: Rgbc<f32>,
251 si_scaling_factors: Rgbc<f32>,
252 vendor_id: u32,
253 product_id: u32,
254 inspect_status: InputHandlerStatus,
256 feature_updates: Arc<Mutex<CircularBuffer<FeatureEvent>>>,
257
258 events_saturated_count: fuchsia_inspect::UintProperty,
265 clients_connected_count: fuchsia_inspect::UintProperty,
268}
269
270#[cfg_attr(test, derive(Debug))]
271enum ActiveSettingState {
272 Uninitialized(Vec<AdjustmentSetting>),
273 Initialized(ActiveSetting),
274 Static(AdjustmentSetting),
275}
276
277pub type CalibratedLightSensorHandler = LightSensorHandler<Calibrator<LedWatcherHandle>>;
278pub async fn make_light_sensor_handler_and_spawn_led_watcher(
279 light_proxy: LightProxy,
280 brightness_proxy: BrightnessControlProxy,
281 calibration: Option<Calibration>,
282 configuration: SensorConfiguration,
283 input_handlers_node: &fuchsia_inspect::Node,
284) -> Result<(Rc<CalibratedLightSensorHandler>, Option<CancelableTask>), Error> {
285 let inspect_status = InputHandlerStatus::new(
286 input_handlers_node,
287 "light_sensor_handler",
288 false,
289 );
290 let (calibrator, watcher_task) = if let Some(calibration) = calibration {
291 let light_groups =
292 light_proxy.watch_light_groups().await.context("request initial light groups")?;
293 let led_watcher = LedWatcher::new(light_groups);
294 let (cancelation_tx, cancelation_rx) = oneshot::channel();
295 let light_proxy_receives_initial_response =
296 inspect_status.inspect_node.create_bool("light_proxy_receives_initial_response", false);
297 let brightness_proxy_receives_initial_response = inspect_status
298 .inspect_node
299 .create_bool("brightness_proxy_receives_initial_response", false);
300 let (led_watcher_handle, watcher_task) = led_watcher
301 .handle_light_groups_and_brightness_watch(
302 light_proxy,
303 brightness_proxy,
304 cancelation_rx,
305 light_proxy_receives_initial_response,
306 brightness_proxy_receives_initial_response,
307 );
308 let watcher_task = CancelableTask::new(cancelation_tx, watcher_task);
309 let calibrator = Calibrator::new(calibration, led_watcher_handle);
310 (Some(calibrator), Some(watcher_task))
311 } else {
312 (None, None)
313 };
314 Ok((LightSensorHandler::new(calibrator, configuration, inspect_status), watcher_task))
315}
316
317impl<T> LightSensorHandler<T> {
318 pub fn new(
319 calibrator: impl Into<Option<T>>,
320 configuration: SensorConfiguration,
321 inspect_status: InputHandlerStatus,
322 ) -> Rc<Self> {
323 let calibrator = calibrator.into();
324 let hanging_get = RefCell::new(HangingGet::new_unknown_state(Box::new(
325 |sensor_data: &LightSensorData, responder: SensorWatchResponder| -> bool {
326 if let Err(e) = responder.send(&FidlLightSensorData::from(*sensor_data)) {
327 log::warn!("Failed to send updated data to client: {e:?}",);
328 }
329 true
330 },
331 ) as NotifyFn));
332 let feature_updates = Arc::new(Mutex::new(CircularBuffer::new(5)));
333 let active_setting =
334 RefCell::new(ActiveSettingState::Uninitialized(configuration.settings));
335 let events_saturated_count =
336 inspect_status.inspect_node.create_uint("events_saturated_count", 0);
337 let clients_connected_count =
338 inspect_status.inspect_node.create_uint("clients_connected_count", 0);
339 inspect_status.inspect_node.record_lazy_child("recent_feature_events_log", {
340 let feature_updates = Arc::clone(&feature_updates);
341 move || {
342 let feature_updates = Arc::clone(&feature_updates);
343 async move {
344 let inspector = fuchsia_inspect::Inspector::default();
345 Ok(feature_updates.lock().await.record_all_lazy_inspect(inspector))
346 }
347 .boxed()
348 }
349 });
350 Rc::new(Self {
351 hanging_get,
352 calibrator,
353 active_setting,
354 rgbc_to_lux_coefs: configuration.rgbc_to_lux_coefficients,
355 si_scaling_factors: configuration.si_scaling_factors,
356 vendor_id: configuration.vendor_id,
357 product_id: configuration.product_id,
358 inspect_status,
359 events_saturated_count,
360 clients_connected_count,
361 feature_updates,
362 })
363 }
364
365 pub async fn handle_light_sensor_request_stream(
366 self: &Rc<Self>,
367 mut stream: SensorRequestStream,
368 ) -> Result<(), Error> {
369 let subscriber = self.hanging_get.borrow_mut().new_subscriber();
370 self.clients_connected_count.add(1);
371 while let Some(request) =
372 stream.try_next().await.context("Error handling light sensor request stream")?
373 {
374 match request {
375 SensorRequest::Watch { responder } => {
376 subscriber
377 .register(responder)
378 .context("registering responder for Watch call")?;
379 }
380 }
381 }
382 self.clients_connected_count.subtract(1);
383 Ok(())
384 }
385
386 fn calculate_lux(&self, reading: Rgbc<f32>) -> f32 {
388 Rgbc::multi_map(reading, self.rgbc_to_lux_coefs, |reading, coef| reading * coef)
389 .fold(0.0, |lux, c| lux + c)
390 }
391}
392
393fn process_reading(reading: Rgbc<u16>, initial_setting: AdjustmentSetting) -> Rgbc<f32> {
399 let gain_bias = MAX_GAIN / initial_setting.gain as u32;
400
401 reading.map(|v| {
402 div_round_closest(v as u32 * gain_bias * MAX_ATIME, num_cycles(initial_setting.atime))
403 as f32
404 })
405}
406
407#[derive(Debug)]
408enum SaturatedError {
409 Saturated,
410 Anyhow(Error),
411}
412
413impl From<Error> for SaturatedError {
414 fn from(value: Error) -> Self {
415 Self::Anyhow(value)
416 }
417}
418
419impl<T> LightSensorHandler<T>
420where
421 T: Calibrate,
422{
423 async fn get_calibrated_data(
424 &self,
425 reading: Rgbc<u16>,
426 device_proxy: &InputDeviceProxy,
427 ) -> Result<LightReading, SaturatedError> {
428 let (initial_setting, pulled_up) = {
431 let mut active_setting_state = self.active_setting.borrow_mut();
432 let track_feature_update = |feature_event| async move {
433 self.feature_updates.lock().await.push(feature_event);
434 };
435 match &mut *active_setting_state {
436 ActiveSettingState::Uninitialized(adjustment_settings) => {
437 let active_setting = ActiveSetting::new(std::mem::take(adjustment_settings), 0);
438 if let Err(e) =
439 active_setting.update_device(&device_proxy, track_feature_update).await
440 {
441 log::error!(
442 "Unable to set initial settings for sensor. Falling back \
443 to static setting: {e:?}"
444 );
445 let setting = active_setting.settings[0];
447 *active_setting_state = ActiveSettingState::Static(setting);
448 (setting, false)
449 } else {
450 *active_setting_state = ActiveSettingState::Initialized(active_setting);
454 return Err(SaturatedError::Saturated);
455 }
456 }
457 ActiveSettingState::Initialized(active_setting) => {
458 let initial_setting = active_setting.active_setting();
459 let pulled_up = active_setting
460 .adjust(reading, device_proxy, track_feature_update)
461 .await
462 .map_err(|e| match e {
463 SaturatedError::Saturated => SaturatedError::Saturated,
464 SaturatedError::Anyhow(e) => {
465 SaturatedError::Anyhow(e.context("adjusting active setting"))
466 }
467 })?;
468 (initial_setting, pulled_up)
469 }
470 ActiveSettingState::Static(setting) => (*setting, false),
471 }
472 };
473 let uncalibrated_rgbc = process_reading(reading, initial_setting);
474 let rgbc = self
475 .calibrator
476 .as_ref()
477 .map(|calibrator| calibrator.calibrate(uncalibrated_rgbc))
478 .unwrap_or(uncalibrated_rgbc);
479
480 let si_rgbc = (self.si_scaling_factors * rgbc).map(|c| c / ADC_SCALING_FACTOR);
481 let lux = self.calculate_lux(si_rgbc);
482 let cct = correlated_color_temperature(si_rgbc);
483 if cct.is_none() && pulled_up {
487 return Err(SaturatedError::Saturated);
488 }
489
490 let rgbc = uncalibrated_rgbc.map(|c| c as f32 / TRANSITION_SCALING_FACTOR);
491 Ok(LightReading { rgbc, si_rgbc, is_calibrated: self.calibrator.is_some(), lux, cct })
492 }
493}
494
495fn to_us(atime: u32) -> u32 {
497 num_cycles(atime) * MIN_TIME_STEP_US
498}
499
500fn div_round_up(n: u32, d: u32) -> u32 {
502 (n + d - 1) / d
503}
504
505fn div_round_closest(n: u32, d: u32) -> u32 {
507 (n + (d / 2)) / d
508}
509
510const MAX_SATURATION_RED: u16 = 21_067;
512const MAX_SATURATION_GREEN: u16 = 20_395;
513const MAX_SATURATION_BLUE: u16 = 20_939;
514const MAX_SATURATION_CLEAR: u16 = 65_085;
515
516fn saturated(reading: Rgbc<u16>) -> bool {
519 reading.red == MAX_SATURATION_RED
520 && reading.green == MAX_SATURATION_GREEN
521 && reading.blue == MAX_SATURATION_BLUE
522 && reading.clear == MAX_SATURATION_CLEAR
523}
524
525fn correlated_color_temperature(reading: Rgbc<f32>) -> Option<f32> {
529 let big_x = -0.7687 * reading.red + 9.7764 * reading.green + -7.4164 * reading.blue;
531 let big_y = -1.7475 * reading.red + 9.9603 * reading.green + -5.6755 * reading.blue;
532 let big_z = -3.6709 * reading.red + 4.8637 * reading.green + 4.3682 * reading.blue;
533
534 let div = big_x + big_y + big_z;
535 if div.abs() < f32::EPSILON {
536 return None;
537 }
538
539 let x = big_x / div;
540 let y = big_y / div;
541 let n = (x - 0.3320) / (0.1858 - y);
542 Some(449.0 * n.powi(3) + 3525.0 * n.powi(2) + 6823.3 * n + 5520.33)
543}
544
545#[async_trait(?Send)]
546impl<T> InputHandler for LightSensorHandler<T>
547where
548 T: Calibrate + 'static,
549{
550 async fn handle_input_event(self: Rc<Self>, mut input_event: InputEvent) -> Vec<InputEvent> {
551 fuchsia_trace::duration!("input", "light_sensor_handler");
552 if let InputEvent {
553 device_event: InputDeviceEvent::LightSensor(ref light_sensor_event),
554 device_descriptor: InputDeviceDescriptor::LightSensor(ref light_sensor_descriptor),
555 event_time,
556 handled: Handled::No,
557 trace_id: _,
558 } = input_event
559 {
560 fuchsia_trace::duration!("input", "light_sensor_handler[processing]");
561 self.inspect_status.count_received_event(&event_time);
562 if !(light_sensor_descriptor.vendor_id == self.vendor_id
564 && light_sensor_descriptor.product_id == self.product_id)
565 {
566 log::warn!(
568 "Unexpected device in light sensor handler: {:?}",
569 light_sensor_descriptor,
570 );
571 return vec![input_event];
572 }
573 let LightReading { rgbc, si_rgbc, is_calibrated, lux, cct } = match self
574 .get_calibrated_data(light_sensor_event.rgbc, &light_sensor_event.device_proxy)
575 .await
576 {
577 Ok(data) => data,
578 Err(SaturatedError::Saturated) => {
579 self.events_saturated_count.add(1);
581 return vec![input_event];
582 }
583 Err(SaturatedError::Anyhow(e)) => {
584 log::warn!("Failed to get light sensor readings: {e:?}");
585 return vec![input_event];
587 }
588 };
589 let publisher = self.hanging_get.borrow_mut().new_publisher();
590 publisher.set(LightSensorData {
591 rgbc,
592 si_rgbc,
593 is_calibrated,
594 calculated_lux: lux,
595 correlated_color_temperature: cct,
596 });
597 input_event.handled = Handled::Yes;
598 self.inspect_status.count_handled_event();
599 }
600 vec![input_event]
601 }
602
603 fn set_handler_healthy(self: std::rc::Rc<Self>) {
604 self.inspect_status.health_node.borrow_mut().set_ok();
605 }
606
607 fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
608 self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
609 }
610
611 fn get_name(&self) -> &'static str {
612 "LightSensorHandler"
613 }
614
615 fn interest(&self) -> Vec<InputEventType> {
616 vec![InputEventType::LightSensor]
617 }
618}
619
620#[derive(Copy, Clone, PartialEq)]
621struct LightSensorData {
622 rgbc: Rgbc<f32>,
623 si_rgbc: Rgbc<f32>,
624 is_calibrated: bool,
625 calculated_lux: f32,
626 correlated_color_temperature: Option<f32>,
627}
628
629impl From<LightSensorData> for FidlLightSensorData {
630 fn from(data: LightSensorData) -> Self {
631 Self {
632 rgbc: Some(FidlRgbc::from(data.rgbc)),
633 si_rgbc: Some(FidlRgbc::from(data.si_rgbc)),
634 is_calibrated: Some(data.is_calibrated),
635 calculated_lux: Some(data.calculated_lux),
636 correlated_color_temperature: data.correlated_color_temperature,
637 ..Default::default()
638 }
639 }
640}
641
642impl From<Rgbc<f32>> for FidlRgbc {
643 fn from(rgbc: Rgbc<f32>) -> Self {
644 Self {
645 red_intensity: rgbc.red,
646 green_intensity: rgbc.green,
647 blue_intensity: rgbc.blue,
648 clear_intensity: rgbc.clear,
649 }
650 }
651}
652
653#[cfg(test)]
654mod light_sensor_handler_tests;