blob: fe66cfdaa6529406ce17576ff55fe2a33d1188d7 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::Context;
use anyhow::Error;
use fuchsia_async::DurationExt;
use fuchsia_zircon::Duration;
use led::{Led, LedControl};
use lib::backlight::{Backlight, BacklightControl};
use lib::sensor::{Sensor, SensorControl};
const SMALL_SLEEP_TIME_MS: i64 = 1;
const LARGE_SLEEP_TIME_MS: i64 = 500;
// Threshold for deciding whether we use a large or small sleep time
const SMALL_BRIGHTNESS_CHANGE: f64 = 0.01;
// Simple linear correlation between light sensor readings and LED and backlight brightness
// This is the approximate light reading in a bright room, divide the reading by this
// factor to get the screen and LED brightness on a scale of 0.0 to 1.0.
const LIGHT_FACTOR: f64 = 18000.0;
// Range of values that may be sent to the screen
const MIN_LIGHT: f64 = 0.003;
const MAX_LIGHT: f64 = 1.0;
// If the brightness size is larger than the BRIGHTNESS[i] then use STEP_SIZE[i].
// The default is to use the final STEP_SIZE value
const BRIGHTNESS: [f64; 3] = [0.5, 0.3, 0.1];
const STEP_SIZE: [f64; 4] = [0.05, 0.01, 0.005, 0.001];
mod led;
pub struct Control {}
impl Control {
pub async fn run(
sensor: &impl SensorControl,
backlight: &impl BacklightControl,
leds: &mut impl LedControl,
) -> Result<(), Error> {
let num_leds = leds.get_num_lights().await.context("error received get num lights")?;
tracing::info!("There is/are {} led(s)", num_leds);
for i in 0..num_leds {
let info = match leds.get_info(i).await {
Ok(Ok(info)) => info,
_ => continue,
};
tracing::info!("LED {} is {:?}", i + 1, info);
}
loop {
let sleep_time =
Self::run_auto_brightness_step(sensor, backlight, leds, num_leds).await?;
fuchsia_async::Timer::new(sleep_time.after_now()).await;
}
}
async fn run_auto_brightness_step(
sensor: &impl SensorControl,
backlight: &impl BacklightControl,
leds: &mut impl LedControl,
num_leds: u32,
) -> Result<Duration, Error> {
let mut sleep_time = Duration::from_millis(SMALL_SLEEP_TIME_MS);
if let Some(ambient_light_input_rpt) = sensor.read().await? {
let brightness = backlight.get_brightness().await?;
let mut new_brightness = num_traits::clamp(
ambient_light_input_rpt.illuminance as f64 / LIGHT_FACTOR,
MIN_LIGHT,
MAX_LIGHT,
);
// Choose a step size that is not noticeable.
// The brighter it is the larger the step size we can get away with
let mut step_size = STEP_SIZE[STEP_SIZE.len() - 1];
for i in 0..BRIGHTNESS.len() {
if brightness > BRIGHTNESS[i] {
step_size = STEP_SIZE[i];
break;
}
}
let desired_brightness = new_brightness;
new_brightness = if new_brightness > brightness {
num_traits::clamp(brightness + step_size, 0.0, new_brightness)
} else {
num_traits::clamp(brightness - step_size, new_brightness, 1.0)
};
new_brightness = num_traits::clamp(new_brightness, MIN_LIGHT, MAX_LIGHT);
backlight.set_brightness(new_brightness).await?;
for i in 0..num_leds {
let _ = leds.set_brightness(i, new_brightness).await.unwrap();
}
// If we are not close to the required brightness then use short sleep time
let step_time =
if num_traits::abs(new_brightness - desired_brightness) > SMALL_BRIGHTNESS_CHANGE {
SMALL_SLEEP_TIME_MS
} else {
LARGE_SLEEP_TIME_MS
};
sleep_time = Duration::from_millis(step_time);
}
Ok(sleep_time)
}
}
#[fuchsia::main(logging_tags = ["auto-brightness"])]
async fn main() -> Result<(), Error> {
tracing::info!("Started");
let backlight = Backlight::without_display_power()?;
let mut leds = Led::new().await?;
let sensor = Sensor::new().await;
Control::run(&sensor, &backlight, &mut leds).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{Control, LIGHT_FACTOR};
use crate::led::LedControl;
use anyhow::Error;
use async_trait::async_trait;
use fidl_fuchsia_hardware_light::{Capability, Info, LightError};
use fuchsia_async::{self as fasync};
use fuchsia_zircon::Duration;
use futures::lock::Mutex;
use lib::sensor::AmbientLightInputRpt;
use lib::{backlight::BacklightControl, sensor::SensorControl};
use std::sync::Arc;
struct MockSensor {
next_reading: f32,
}
#[async_trait]
impl SensorControl for MockSensor {
async fn read(&self) -> Result<Option<AmbientLightInputRpt>, Error> {
let report = AmbientLightInputRpt {
illuminance: self.next_reading,
red: 0.0,
green: 0.0,
blue: 0.0,
};
Ok(Some(report))
}
}
#[derive(Debug)]
struct MockBacklight {
brightness: Arc<Mutex<f64>>,
}
#[async_trait]
impl BacklightControl for MockBacklight {
async fn get_brightness(&self) -> Result<f64, Error> {
let brightness = self.brightness.lock().await;
Ok(*brightness)
}
async fn set_brightness(&self, value: f64) -> Result<(), Error> {
let mut brightness = self.brightness.lock().await;
*brightness = value;
Ok(())
}
async fn get_max_absolute_brightness(&self) -> Result<f64, Error> {
Ok(0.0)
}
}
struct MockLed {
num_lights: u32,
brightness: f64,
info: Info,
}
#[async_trait]
impl LedControl for MockLed {
async fn set_brightness(
&mut self,
_index: u32,
value: f64,
) -> Result<Result<(), LightError>, fidl::Error> {
self.brightness = value;
Ok(Ok(()))
}
async fn get_num_lights(&mut self) -> Result<u32, fidl::Error> {
Ok(self.num_lights)
}
async fn get_info(&self, _index: u32) -> Result<Result<Info, LightError>, fidl::Error> {
Ok(Ok(self.info.clone()))
}
}
struct Devices {
sensor: MockSensor,
backlight: MockBacklight,
leds: MockLed,
}
fn setup(next_reading: f64, brightness: f64) -> Devices {
let sensor = MockSensor { next_reading: next_reading as f32 };
let backlight = MockBacklight { brightness: Arc::new(Mutex::new(brightness)) };
let leds = MockLed {
brightness: 0.0,
num_lights: 1,
info: Info { name: "amber".into(), capability: Capability::Brightness },
};
Devices { sensor: sensor, backlight: backlight, leds: leds }
}
async fn run_step(devices: &mut Devices) -> Duration {
let num_leds = 1;
Control::run_auto_brightness_step(
&devices.sensor,
&devices.backlight,
&mut devices.leds,
num_leds,
)
.await
.expect("a Duration")
}
#[fasync::run_singlethreaded(test)]
async fn auto_brightness_large_sleep_test() {
let mut devices = setup(0.0 * LIGHT_FACTOR, 0.0);
let sleep_time = run_step(&mut devices).await;
assert_eq!(sleep_time.into_millis(), 500);
}
#[fasync::run_singlethreaded(test)]
async fn auto_brightness_small_sleep_test() {
let mut devices = setup(0.5 * LIGHT_FACTOR, 0.0);
let sleep_time = run_step(&mut devices).await;
assert_eq!(sleep_time.into_millis(), 1);
}
async fn auto_brightness_step_size_test(brightness: f64, expected_step_size: f64) {
let mut devices = setup(0.0, brightness);
run_step(&mut devices).await;
let new_brightness = devices.backlight.get_brightness().await.unwrap();
let step_size = brightness - new_brightness;
assert!((step_size - expected_step_size).abs() < 0.000001);
}
#[fasync::run_singlethreaded(test)]
async fn auto_brightness_step_size1_test() {
auto_brightness_step_size_test(0.6, 0.05).await;
}
#[fasync::run_singlethreaded(test)]
async fn auto_brightness_step_size2_test() {
auto_brightness_step_size_test(0.4, 0.01).await;
}
#[fasync::run_singlethreaded(test)]
async fn auto_brightness_step_size3_test() {
auto_brightness_step_size_test(0.2, 0.005).await;
}
#[fasync::run_singlethreaded(test)]
async fn auto_brightness_step_size4_test() {
auto_brightness_step_size_test(0.01, 0.001).await;
}
}