| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "power_manager/ambient_light_sensor.h" |
| |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <cmath> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/file_util.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| |
| namespace power_manager { |
| |
| // Period in which to poll the ambient light sensor. |
| static const int kSensorPollPeriodMs = 1000; |
| |
| // Lux level <= kLuxLo should return 0% response. |
| static const int kLuxLo = 12; |
| |
| // Lux level >= kLuxHi should return 100% response. |
| static const int kLuxHi = 1000; |
| |
| // A positive kLuxOffset gives us a flatter curve, particularly at lower lux. |
| // Alternatively, we could use a higher kLuxLo. |
| static const int kLuxOffset = 4; |
| |
| AmbientLightSensor::AmbientLightSensor(BacklightController* controller, |
| PowerPrefsInterface* prefs) |
| : controller_(controller), |
| prefs_(prefs), |
| is_polling_(false), |
| disable_polling_(false), |
| still_deferring_(false), |
| als_found_(false), |
| read_cb_(base::Bind(&AmbientLightSensor::ReadCallback, |
| base::Unretained(this))), |
| error_cb_(base::Bind(&AmbientLightSensor::ErrorCallback, |
| base::Unretained(this))) { |
| // Initialize factors used for LuxToPercent calculation. |
| // See comments in Tsl2563LuxToPercent() for a full description. |
| double hi = kLuxHi + kLuxOffset; |
| double lo = kLuxLo + kLuxOffset; |
| log_multiply_factor_ = 100 / log(hi / lo); |
| log_subtract_factor_ = log(lo) * log_multiply_factor_; |
| } |
| |
| AmbientLightSensor::~AmbientLightSensor() {} |
| |
| bool AmbientLightSensor::DeferredInit() { |
| CHECK(!als_file_.HasOpenedFile()); |
| // Search the iio/devices directory for a subdirectory (eg "device0" or |
| // "iio:device0") that contains the "[in_]illuminance0_{input|raw}" file. |
| file_util::FileEnumerator dir_enumerator( |
| FilePath("/sys/bus/iio/devices"), false, |
| file_util::FileEnumerator::DIRECTORIES); |
| const char* input_names[] = { |
| "in_illuminance0_input", |
| "in_illuminance0_raw", |
| "illuminance0_input", |
| }; |
| |
| for (FilePath check_path = dir_enumerator.Next(); !check_path.empty(); |
| check_path = dir_enumerator.Next()) { |
| for (unsigned int i = 0; i < arraysize(input_names); i++) { |
| FilePath als_path = check_path.Append(input_names[i]); |
| if (als_file_.Init(als_path.value())) { |
| if (still_deferring_) |
| LOG(INFO) << "Finally found the lux file"; |
| return true; |
| } |
| } |
| } |
| |
| // If the illuminance file is not immediately found, issue a deferral |
| // message and try again later. |
| if (still_deferring_) |
| return false; |
| LOG(WARNING) << "Deferring lux: " << strerror(errno); |
| still_deferring_ = true; |
| return false; |
| } |
| |
| bool AmbientLightSensor::Init() { |
| int64 disable_als; |
| // TODO: In addition to disable_als, we should add another prefs file |
| // that allows polling ALS as usual but prevents backlight changes from |
| // happening. This will be useful for power and system profiling. |
| if (prefs_->GetInt64(kDisableALSPref, &disable_als) && disable_als) { |
| LOG(INFO) << "Not using ambient light sensor"; |
| return false; |
| } |
| if (controller_) |
| controller_->SetAmbientLightSensor(this); |
| return true; |
| } |
| |
| void AmbientLightSensor::EnableOrDisableSensor(PowerState state) { |
| if (state != BACKLIGHT_ACTIVE) { |
| LOG(INFO) << "Disabling light sensor poll"; |
| disable_polling_ = true; |
| return; |
| } |
| |
| // We want to poll. |
| // There is a possible race between setting disable_polling_ = true above |
| // and now setting it false. If BacklightController rapidly transitions |
| // the backlight into and out of dim, we might try to turn on polling when |
| // it is already on. |
| // is_polling_ resolves the race. No locking is needed in this single |
| // threaded application. |
| disable_polling_ = false; |
| if (is_polling_) |
| return; // already polling. |
| |
| // Start polling. |
| LOG(INFO) << "Enabling light sensor poll"; |
| is_polling_ = true; |
| g_timeout_add(kSensorPollPeriodMs, ReadAlsThunk, this); |
| } |
| |
| gboolean AmbientLightSensor::ReadAls() { |
| if (disable_polling_) { |
| is_polling_ = false; |
| return false; // Returning false removes the timeout. |
| } |
| |
| // We really want to read the ambient light level. |
| // Complete the deferred lux file open if necessary. |
| if (!als_file_.HasOpenedFile() && !DeferredInit()) |
| return true; // Return true to try again later. |
| |
| als_file_.StartRead(&read_cb_, &error_cb_); |
| return FALSE; |
| } |
| |
| void AmbientLightSensor::ReadCallback(const std::string& data) { |
| int value = -1; |
| std::string trimmed_data; |
| TrimWhitespaceASCII(data, TRIM_ALL, &trimmed_data); |
| if (base::StringToInt(trimmed_data, &value)) { |
| if (controller_) |
| controller_->SetAlsBrightnessOffsetPercent(Tsl2563LuxToPercent(value)); |
| } else { |
| LOG(ERROR) << "Could not read lux value from ALS file contents: [" |
| << trimmed_data << "]"; |
| } |
| // If the polling has been disabled, do not read again. |
| if (disable_polling_) { |
| is_polling_ = false; |
| return; |
| } |
| // Schedule next poll. |
| g_timeout_add(kSensorPollPeriodMs, ReadAlsThunk, this); |
| } |
| |
| void AmbientLightSensor::ErrorCallback() { |
| LOG(ERROR) << "Error reading ALS file."; |
| // If the polling has been disabled, do not read again. |
| if (disable_polling_) { |
| is_polling_ = false; |
| return; |
| } |
| // Schedule next poll. |
| g_timeout_add(kSensorPollPeriodMs, ReadAlsThunk, this); |
| } |
| |
| double AmbientLightSensor::Tsl2563LuxToPercent(int luxval) { |
| // Notes on tsl2563 Ambient Light Response (_ALR) table: |
| // |
| // measurement location: lux file value, intended luma level |
| // dark room: 0, 0 |
| // office: 75, 50 |
| // outside, day, shade: 1000-3000, 100 |
| // outside, day, direct sunlight: 10000, 100 |
| // |
| // Give a natural logorithmic response of 0-100% for lux values 12-1000. |
| // What's a log? If value=e^exponent, then log(value)=exponent. |
| // |
| // Multiply the log by log_multiply_factor_ to provide the 100% range. |
| // Calculated as: 100 / (log((kLuxHi + kLuxOffset) / (kLuxLo + kLuxOffset))) |
| // hi = kLuxHi + kLuxOffset |
| // lo = kLuxLo + kLuxOffset |
| // (log(hi) - log(lo)) * log_multiply_factor_ = 100 |
| // So: log_multiply_factor_ = 100 / log(hi / lo) |
| // |
| // Subtract log_subtract_factor_ from the log product to normalize to 0. |
| // Calculated as: log_subtract_factor_ = log(lo) * log_multiply_factor_ |
| // lo = kLuxLo + kLuxOffset |
| // log(lo) * log_multiply_factor_ - log_subtract_factor_ = 0 |
| // So: log_subtract_factor_ = log(lo) * log_multiply_factor_ |
| |
| int value = luxval + kLuxOffset; |
| double response = log(value) * log_multiply_factor_ - log_subtract_factor_; |
| return std::max(0.0, std::min(100.0, response)); |
| } |
| |
| } // namespace power_manager |