blob: 3736f0e4b38540d41ba0e46a3610a0b0e7368705 [file] [log] [blame]
// 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