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