// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/device/generic_sensor/platform_sensor_chromeos.h"
#include <iterator>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "services/device/public/cpp/generic_sensor/sensor_traits.h"
namespace device {
namespace {
constexpr char kAxes[][3] = {"_x", "_y", "_z"};
} // namespace
PlatformSensorChromeOS::PlatformSensorChromeOS(
int32_t iio_device_id,
mojom::SensorType type,
SensorReadingSharedBuffer* reading_buffer,
base::WeakPtr<PlatformSensorProvider> provider,
mojo::ConnectionErrorWithReasonCallback sensor_device_disconnect_callback,
double scale,
mojo::Remote<chromeos::sensors::mojom::SensorDevice> sensor_device_remote)
: PlatformSensor(type, reading_buffer, std::move(provider)),
iio_device_id_(iio_device_id),
sensor_device_disconnect_callback_(
std::move(sensor_device_disconnect_callback)),
default_configuration_(
PlatformSensorConfiguration(GetSensorMaxAllowedFrequency(type))),
scale_(scale),
sensor_device_remote_(std::move(sensor_device_remote)) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(sensor_device_remote_.is_bound());
DCHECK_GT(scale_, 0.0);
sensor_device_remote_.set_disconnect_with_reason_handler(
base::BindOnce(&PlatformSensorChromeOS::OnSensorDeviceDisconnect,
weak_factory_.GetWeakPtr()));
sensor_device_remote_->SetTimeout(0);
sensor_device_remote_->GetAllChannelIds(
base::BindOnce(&PlatformSensorChromeOS::GetAllChannelIdsCallback,
weak_factory_.GetWeakPtr()));
}
PlatformSensorChromeOS::~PlatformSensorChromeOS() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
mojom::ReportingMode PlatformSensorChromeOS::GetReportingMode() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (GetType() == mojom::SensorType::AMBIENT_LIGHT)
return mojom::ReportingMode::ON_CHANGE;
return mojom::ReportingMode::CONTINUOUS;
}
void PlatformSensorChromeOS::OnSampleUpdated(
const base::flat_map<int32_t, int64_t>& sample) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!channel_indices_.empty());
if (sample.size() != channel_indices_.size()) {
LOG(WARNING) << "Invalid sample with size: " << sample.size();
OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED);
return;
}
for (auto index : channel_indices_) {
if (!base::Contains(sample, index)) {
LOG(ERROR) << "Missing channel: " << iio_channel_ids_[index]
<< " in sample.";
OnErrorOccurred(chromeos::sensors::mojom::ObserverErrorType::READ_FAILED);
return;
}
}
if (num_failed_reads_ > 0 && ++num_recovery_reads_ == kNumRecoveryReads) {
num_recovery_reads_ = 0;
--num_failed_reads_;
}
SensorReading reading;
switch (GetType()) {
case mojom::SensorType::AMBIENT_LIGHT:
DCHECK_EQ(channel_indices_.size(), 2u);
reading.als.value = GetScaledValue(sample.at(channel_indices_[0]));
break;
case mojom::SensorType::PROXIMITY:
DCHECK_EQ(channel_indices_.size(), 2u);
reading.proximity.value = GetScaledValue(sample.at(channel_indices_[0]));
break;
case mojom::SensorType::ACCELEROMETER:
case mojom::SensorType::GRAVITY:
DCHECK_EQ(channel_indices_.size(), 4u);
reading.accel.x = GetScaledValue(sample.at(channel_indices_[0]));
reading.accel.y = GetScaledValue(sample.at(channel_indices_[1]));
reading.accel.z = GetScaledValue(sample.at(channel_indices_[2]));
break;
case mojom::SensorType::GYROSCOPE:
DCHECK_EQ(channel_indices_.size(), 4u);
reading.gyro.x = GetScaledValue(sample.at(channel_indices_[0]));
reading.gyro.y = GetScaledValue(sample.at(channel_indices_[1]));
reading.gyro.z = GetScaledValue(sample.at(channel_indices_[2]));
break;
case mojom::SensorType::MAGNETOMETER:
DCHECK_EQ(channel_indices_.size(), 4u);
reading.magn.x = GetScaledValue(sample.at(channel_indices_[0]));
reading.magn.y = GetScaledValue(sample.at(channel_indices_[1]));
reading.magn.z = GetScaledValue(sample.at(channel_indices_[2]));
break;
default:
break;
}
reading.raw.timestamp =
base::Nanoseconds(sample.at(channel_indices_.back())).InSecondsF();
UpdateSharedBufferAndNotifyClients(reading);
}
void PlatformSensorChromeOS::OnErrorOccurred(
chromeos::sensors::mojom::ObserverErrorType type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (type) {
case chromeos::sensors::mojom::ObserverErrorType::ALREADY_STARTED:
LOG(ERROR) << "Sensor " << iio_device_id_
<< ": Another observer has already started to read samples";
ResetOnError();
break;
case chromeos::sensors::mojom::ObserverErrorType::FREQUENCY_INVALID:
LOG(ERROR) << "Sensor " << iio_device_id_
<< ": Observer started with an invalid frequency";
ResetOnError();
break;
case chromeos::sensors::mojom::ObserverErrorType::NO_ENABLED_CHANNELS:
LOG(ERROR) << "Sensor " << iio_device_id_
<< ": Observer started with no channels enabled";
SetChannelsEnabled();
break;
case chromeos::sensors::mojom::ObserverErrorType::SET_FREQUENCY_IO_FAILED:
LOG(ERROR) << "Sensor " << iio_device_id_
<< ": Failed to set frequency to the physical device";
break;
case chromeos::sensors::mojom::ObserverErrorType::GET_FD_FAILED:
LOG(ERROR) << "Sensor " << iio_device_id_
<< ": Failed to get the device's fd to poll on";
break;
case chromeos::sensors::mojom::ObserverErrorType::READ_FAILED:
LOG(ERROR) << "Sensor " << iio_device_id_ << ": Failed to read a sample";
OnReadFailure();
break;
case chromeos::sensors::mojom::ObserverErrorType::READ_TIMEOUT:
LOG(ERROR) << "Sensor " << iio_device_id_ << ": A read timed out";
break;
default:
LOG(ERROR) << "Sensor " << iio_device_id_ << ": error "
<< static_cast<int>(type);
break;
}
}
bool PlatformSensorChromeOS::StartSensor(
const PlatformSensorConfiguration& configuration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!sensor_device_remote_.is_bound()) {
LOG(WARNING) << "Unbound sensor_device_remote_, skipping StartSensor.";
return false;
}
if (configuration.frequency() <= 0.0) {
LOG(ERROR) << "Invalid frequency: " << configuration.frequency()
<< " in sensor with id: " << iio_device_id_;
return false;
}
if (receiver_.is_bound() &&
configuration.frequency() == current_configuration_.frequency()) {
// Nothing to do.
return true;
}
current_configuration_ = configuration;
StartReadingIfReady();
return true;
}
void PlatformSensorChromeOS::StopSensor() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
receiver_.reset();
}
bool PlatformSensorChromeOS::CheckSensorConfiguration(
const PlatformSensorConfiguration& configuration) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return configuration.frequency() > 0 &&
configuration.frequency() <= default_configuration_.frequency();
}
PlatformSensorConfiguration PlatformSensorChromeOS::GetDefaultConfiguration() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return default_configuration_;
}
void PlatformSensorChromeOS::SensorReplaced() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(1) << "SensorReplaced with id: " << iio_device_id_;
ResetReadingBuffer();
ResetOnError();
}
void PlatformSensorChromeOS::ResetOnError() {
LOG(ERROR) << "ResetOnError of sensor with id: " << iio_device_id_;
sensor_device_remote_.reset();
receiver_.reset();
NotifySensorError();
}
void PlatformSensorChromeOS::OnSensorDeviceDisconnect(
uint32_t custom_reason_code,
const std::string& description) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto reason =
static_cast<chromeos::sensors::mojom::SensorDeviceDisconnectReason>(
custom_reason_code);
LOG(ERROR) << "OnSensorDeviceDisconnect, reason: " << reason
<< ", description: " << description;
std::move(sensor_device_disconnect_callback_)
.Run(custom_reason_code, description);
ResetOnError();
}
void PlatformSensorChromeOS::StartReadingIfReady() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(sensor_device_remote_.is_bound());
if (required_channel_ids_.empty() ||
current_configuration_.frequency() <= 0.0) {
// Not ready yet.
return;
}
UpdateSensorDeviceFrequency();
if (receiver_.is_bound())
return;
sensor_device_remote_->StartReadingSamples(BindNewPipeAndPassRemote());
}
mojo::PendingRemote<chromeos::sensors::mojom::SensorDeviceSamplesObserver>
PlatformSensorChromeOS::BindNewPipeAndPassRemote() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!receiver_.is_bound());
auto pending_remote = receiver_.BindNewPipeAndPassRemote(main_task_runner());
receiver_.set_disconnect_with_reason_handler(
base::BindOnce(&PlatformSensorChromeOS::OnObserverDisconnect,
weak_factory_.GetWeakPtr()));
return pending_remote;
}
void PlatformSensorChromeOS::OnObserverDisconnect(
uint32_t custom_reason_code,
const std::string& description) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(receiver_.is_bound());
auto reason =
static_cast<chromeos::sensors::mojom::SensorDeviceDisconnectReason>(
custom_reason_code);
LOG(ERROR) << "OnObserverDisconnect, reason: " << reason
<< ", description: " << description;
std::move(sensor_device_disconnect_callback_)
.Run(custom_reason_code, description);
ResetOnError();
}
void PlatformSensorChromeOS::SetRequiredChannels() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(required_channel_ids_.empty()); // Should only be called once.
std::optional<std::string> axes_prefix = std::nullopt;
switch (GetType()) {
case mojom::SensorType::AMBIENT_LIGHT:
required_channel_ids_.push_back(chromeos::sensors::mojom::kLightChannel);
break;
case mojom::SensorType::ACCELEROMETER:
axes_prefix = chromeos::sensors::mojom::kAccelerometerChannel;
break;
case mojom::SensorType::GYROSCOPE:
axes_prefix = chromeos::sensors::mojom::kGyroscopeChannel;
break;
case mojom::SensorType::MAGNETOMETER:
axes_prefix = chromeos::sensors::mojom::kMagnetometerChannel;
break;
case mojom::SensorType::GRAVITY:
axes_prefix = chromeos::sensors::mojom::kGravityChannel;
break;
default:
break;
}
if (axes_prefix.has_value()) {
for (const auto* axis : kAxes)
required_channel_ids_.push_back(axes_prefix.value() + std::string(axis));
}
required_channel_ids_.push_back(chromeos::sensors::mojom::kTimestampChannel);
}
void PlatformSensorChromeOS::GetAllChannelIdsCallback(
const std::vector<std::string>& iio_channel_ids) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SetRequiredChannels();
DCHECK(!required_channel_ids_.empty());
iio_channel_ids_ = iio_channel_ids;
for (const std::string& channel : required_channel_ids_) {
auto it = base::ranges::find(iio_channel_ids_, channel);
if (it == iio_channel_ids_.end()) {
LOG(ERROR) << "Missing channel: " << channel;
ResetOnError();
return;
}
channel_indices_.push_back(std::distance(iio_channel_ids_.begin(), it));
}
DCHECK_EQ(channel_indices_.size(), required_channel_ids_.size());
SetChannelsEnabled();
StartReadingIfReady();
}
void PlatformSensorChromeOS::UpdateSensorDeviceFrequency() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(sensor_device_remote_.is_bound());
sensor_device_remote_->SetFrequency(
current_configuration_.frequency(),
base::BindOnce(&PlatformSensorChromeOS::SetFrequencyCallback,
weak_factory_.GetWeakPtr(),
current_configuration_.frequency()));
}
void PlatformSensorChromeOS::SetFrequencyCallback(double target_frequency,
double result_frequency) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if ((target_frequency <= 0.0 && result_frequency <= 0.0) ||
(target_frequency > 0.0 && result_frequency > 0.0)) {
return;
}
LOG(ERROR) << "SetFrequency failed. Target frequency: " << target_frequency
<< ", result requency: " << result_frequency;
ResetOnError();
}
void PlatformSensorChromeOS::SetChannelsEnabled() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!sensor_device_remote_.is_bound()) {
LOG(WARNING)
<< "Unbound sensor_device_remote_, skipping SetChannelEnabled.";
return;
}
sensor_device_remote_->SetChannelsEnabled(
channel_indices_, true,
base::BindOnce(&PlatformSensorChromeOS::SetChannelsEnabledCallback,
weak_factory_.GetWeakPtr()));
}
void PlatformSensorChromeOS::SetChannelsEnabledCallback(
const std::vector<int32_t>& failed_indices) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (failed_indices.empty())
return;
for (int32_t index : failed_indices) {
LOG(ERROR) << "Failed to enable channel: " << iio_channel_ids_[index]
<< " in sensor with id: " << iio_device_id_;
}
ResetOnError();
}
double PlatformSensorChromeOS::GetScaledValue(int64_t value) const {
return value * scale_;
}
void PlatformSensorChromeOS::OnReadFailure() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (++num_failed_reads_ < kNumFailedReadsBeforeGivingUp) {
LOG(ERROR) << "ReadSamples error #" << num_failed_reads_ << " occurred";
return;
}
num_failed_reads_ = num_recovery_reads_ = 0;
LOG(ERROR) << "Too many failed reads";
ResetOnError();
}
} // namespace device