blob: 35d34550a4d130124983d92edde0e57b05ce9805 [file] [log] [blame]
// Copyright 2020 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/powerd/system/ambient_light_sensor_delegate_file.h"
#include <fcntl.h>
#include <algorithm>
#include <cerrno>
#include <cmath>
#include <cstring>
#include <map>
#include <utility>
#include <base/bind.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/stl_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include "power_manager/common/util.h"
namespace power_manager {
namespace system {
namespace {
// Default path examined for backlight device directories.
const char kDefaultDeviceListPath[] = "/sys/bus/iio/devices";
// Default interval for polling the ambient light sensor.
const int kDefaultPollIntervalMs = 1000;
enum class ChannelType {
X,
Y,
Z,
};
SensorLocation StringToSensorLocation(const std::string& location) {
if (location == "base")
return SensorLocation::BASE;
if (location == "lid")
return SensorLocation::LID;
return SensorLocation::UNKNOWN;
}
std::string SensorLocationToString(SensorLocation location) {
switch (location) {
case SensorLocation::UNKNOWN:
return "unknown";
case SensorLocation::BASE:
return "base";
case SensorLocation::LID:
return "lid";
}
}
bool ParseLuxData(const std::string& data, int* value) {
DCHECK(value);
std::string trimmed_data;
base::TrimWhitespaceASCII(data, base::TRIM_ALL, &trimmed_data);
if (!base::StringToInt(trimmed_data, value)) {
LOG(ERROR) << "Could not read lux value from ALS file contents: ["
<< trimmed_data << "]";
return false;
}
VLOG(1) << "Read lux value " << value;
return true;
}
} // namespace
const struct ColorChannelInfo {
ChannelType type;
const char* rgb_name;
const char* xyz_name;
bool is_lux_channel;
} kColorChannelConfig[] = {
{ChannelType::X, "red", "x", false},
{ChannelType::Y, "green", "y", true},
{ChannelType::Z, "blue", "z", false},
};
const int AmbientLightSensorDelegateFile::kNumInitAttemptsBeforeLogging = 5;
const int AmbientLightSensorDelegateFile::kNumInitAttemptsBeforeGivingUp = 20;
AmbientLightSensorDelegateFile::AmbientLightSensorDelegateFile(
SensorLocation expected_sensor_location, bool enable_color_support)
: device_list_path_(kDefaultDeviceListPath),
poll_interval_ms_(kDefaultPollIntervalMs),
enable_color_support_(enable_color_support),
num_init_attempts_(0),
expected_sensor_location_(expected_sensor_location) {}
AmbientLightSensorDelegateFile::~AmbientLightSensorDelegateFile() {}
void AmbientLightSensorDelegateFile::Init(bool read_immediately) {
if (read_immediately)
ReadAls();
StartTimer();
}
bool AmbientLightSensorDelegateFile::TriggerPollTimerForTesting() {
if (!poll_timer_.IsRunning())
return false;
ReadAls();
return true;
}
bool AmbientLightSensorDelegateFile::IsColorSensor() const {
return !color_als_files_.empty();
}
base::FilePath AmbientLightSensorDelegateFile::GetIlluminancePath() const {
if (IsColorSensor()) {
for (const ColorChannelInfo& channel : kColorChannelConfig) {
if (!channel.is_lux_channel)
continue;
if (color_als_files_.at(&channel).HasOpenedFile())
return color_als_files_.at(&channel).path();
}
} else {
if (als_file_.HasOpenedFile())
return als_file_.path();
}
return base::FilePath();
}
void AmbientLightSensorDelegateFile::StartTimer() {
poll_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(poll_interval_ms_), this,
&AmbientLightSensorDelegateFile::ReadAls);
}
void AmbientLightSensorDelegateFile::ReadAls() {
// We really want to read the ambient light level.
// Complete the deferred lux file open if necessary.
if (!als_file_.HasOpenedFile() && !InitAlsFile()) {
if (num_init_attempts_ >= kNumInitAttemptsBeforeGivingUp) {
LOG(ERROR) << "Giving up on reading from sensor";
poll_timer_.Stop();
}
return;
}
// The timer will be restarted after the read finishes.
poll_timer_.Stop();
if (!IsColorSensor()) {
als_file_.StartRead(
base::Bind(&AmbientLightSensorDelegateFile::ReadCallback,
base::Unretained(this)),
base::Bind(&AmbientLightSensorDelegateFile::ErrorCallback,
base::Unretained(this)));
return;
}
color_readings_.clear();
for (const ColorChannelInfo& channel : kColorChannelConfig) {
color_als_files_[&channel].StartRead(
base::Bind(&AmbientLightSensorDelegateFile::ReadColorChannelCallback,
base::Unretained(this), &channel),
base::Bind(&AmbientLightSensorDelegateFile::ErrorColorChannelCallback,
base::Unretained(this), &channel));
}
}
void AmbientLightSensorDelegateFile::ReadCallback(const std::string& data) {
if (!set_lux_callback_)
return;
int value = 0;
if (ParseLuxData(data, &value))
set_lux_callback_.Run(value, base::nullopt);
StartTimer();
}
void AmbientLightSensorDelegateFile::ErrorCallback() {
LOG(ERROR) << "Error reading ALS file";
StartTimer();
}
void AmbientLightSensorDelegateFile::ReadColorChannelCallback(
const ColorChannelInfo* channel, const std::string& data) {
int value = -1;
ParseLuxData(data, &value);
color_readings_[channel] = value;
CollectChannelReadings();
}
void AmbientLightSensorDelegateFile::ErrorColorChannelCallback(
const ColorChannelInfo* channel) {
LOG(ERROR) << "Error reading ALS file for " << channel->xyz_name << "channel";
color_readings_[channel] = -1;
CollectChannelReadings();
}
void AmbientLightSensorDelegateFile::CollectChannelReadings() {
if (!set_lux_callback_ ||
color_readings_.size() != base::size(kColorChannelConfig)) {
return;
}
// We should notify observers if there is either a change in lux or a change
// in color temperature. This means that we can always notify when we have the
// Y value but otherwise we need all three.
std::map<ChannelType, int> valid_readings;
double scale_factor = 0;
base::Optional<int> lux_value = base::nullopt;
for (const auto& reading : color_readings_) {
// -1 marks an invalid reading.
if (reading.second == -1)
continue;
if (reading.first->is_lux_channel)
lux_value = reading.second;
valid_readings[reading.first->type] = reading.second;
scale_factor += reading.second;
}
if (valid_readings.count(ChannelType::Y) == 0) {
StartTimer();
return;
}
int color_temperature;
if (valid_readings.size() != color_readings_.size() || scale_factor == 0) {
// We either don't have all of the channels or there is no light in the
// sensor and therefore no color temperature, but we can still notify
// for lux.
color_temperature = -1;
} else {
double scaled_x = valid_readings[ChannelType::X] / scale_factor;
double scaled_y = valid_readings[ChannelType::Y] / scale_factor;
// Avoid weird behavior around the function's pole.
if (scaled_y < 0.186) {
color_temperature = -1;
} else {
double n = (scaled_x - 0.3320) / (0.1858 - scaled_y);
color_temperature = static_cast<int>(449 * n * n * n + 3525 * n * n +
6823.3 * n + 5520.33);
}
}
set_lux_callback_.Run(lux_value, color_temperature);
StartTimer();
}
void AmbientLightSensorDelegateFile::InitColorAlsFiles(
const base::FilePath& device_dir) {
color_als_files_.clear();
std::map<const ColorChannelInfo*, AsyncFileReader> channel_map;
for (const ColorChannelInfo& channel : kColorChannelConfig) {
base::FilePath channel_path(device_dir.Append(
base::StringPrintf("in_illuminance_%s_raw", channel.rgb_name)));
if (!base::PathExists(channel_path))
return;
if (!channel_map[&channel].Init(channel_path))
return;
VLOG(2) << "Found " << channel.xyz_name << " light intensity file at "
<< channel_path.value();
}
color_als_files_ = std::move(channel_map);
LOG(INFO) << "ALS at path " << device_dir.value() << " has color support";
}
bool AmbientLightSensorDelegateFile::InitAlsFile() {
CHECK(!als_file_.HasOpenedFile());
// Search the iio/devices directory for a subdirectory (eg "device0" or
// "iio:device0") that contains the "[in_]illuminance[0]_{input|raw}" file.
base::FileEnumerator dir_enumerator(device_list_path_, false,
base::FileEnumerator::DIRECTORIES);
const char* input_names[] = {
"in_illuminance0_input", "in_illuminance_input", "in_illuminance0_raw",
"in_illuminance_raw", "illuminance0_input",
};
num_init_attempts_++;
for (base::FilePath check_path = dir_enumerator.Next(); !check_path.empty();
check_path = dir_enumerator.Next()) {
if (expected_sensor_location_ != SensorLocation::UNKNOWN) {
base::FilePath loc_path = check_path.Append("location");
std::string location;
if (!base::ReadFileToString(loc_path, &location)) {
continue;
}
base::TrimWhitespaceASCII(location, base::TRIM_ALL, &location);
SensorLocation als_loc = StringToSensorLocation(location);
if (als_loc != expected_sensor_location_) {
continue;
}
}
for (unsigned int i = 0; i < base::size(input_names); i++) {
base::FilePath als_path = check_path.Append(input_names[i]);
if (!base::PathExists(als_path))
continue;
if (!als_file_.Init(als_path))
continue;
if (enable_color_support_)
InitColorAlsFiles(check_path);
LOG(INFO) << "Using lux file " << GetIlluminancePath().value() << " for "
<< SensorLocationToString(expected_sensor_location_) << " ALS";
return true;
}
}
// If the illuminance file is not immediately found, issue a deferral
// message and try again later.
if (num_init_attempts_ > kNumInitAttemptsBeforeLogging)
PLOG(ERROR) << "lux file initialization failed";
return false;
}
} // namespace system
} // namespace power_manager