Commit 061d113d authored by maksim.sisov's avatar maksim.sisov Committed by Commit bot

[sensors](CrOS/Linux) Implement Sensor device manager for sensors

This cl introduces a SensorDeviceManager for Linux and ChromeOS platforms.

Manager:
1) PlatformSensorProviderLinux owns a SensorDeviceManager that uses
DeviceMonitorLinux to enumerate iio devices and get removed/add udev
events. Once device is enumerated, added or removed, it is identified
and all the necessary information is put to SensorInfoLinux structure
and passed to the provider. If there is no such a device, it is added
to cache. In case of removal, manager checks if there was such a device
and updates it's cache accordingly + notifies the provider about a
removed device, which update own cache.
2) Requests: when a request for a sensor comes, it can be processed
immediately or asynchronously once service is up and running.

Reader:
1) There will be two strategies for a reader. At the moment
only polling read is supported.
2) The reader doesn't search for sensor files any more, those are passed
to it in SensorInfoLinux structure.

Unittests:
1) In order to make it possible to test the whole path, I've made
SensorDeviceManager's methods virtual, which allowed me to
mock and override them.
2) Unittests implement own udev methods that just read values from
files. If read is successful, the manager adds this type of sensor
associated with the files that have been read to it's cache and notifies
the provider.

BUG=606766

Review-Url: https://codereview.chromium.org/2533793002
Cr-Commit-Position: refs/heads/master@{#437550}
parent b0246be6
...@@ -108,8 +108,9 @@ test("device_unittests") { ...@@ -108,8 +108,9 @@ test("device_unittests") {
deps += [ "//dbus:test_support" ] deps += [ "//dbus:test_support" ]
} }
if (is_linux) { if (!is_linux_without_udev) {
sources += [ "generic_sensor/linux/sensor_reader_unittest.cc" ] sources +=
[ "generic_sensor/platform_sensor_and_provider_unittest_linux.cc" ]
} }
# HID and Serial: # HID and Serial:
......
...@@ -12,8 +12,6 @@ component("generic_sensor") { ...@@ -12,8 +12,6 @@ component("generic_sensor") {
output_name = "generic_sensor" output_name = "generic_sensor"
sources = [ sources = [
"generic_sensor_consts.h", "generic_sensor_consts.h",
"linux/platform_sensor_utils_linux.cc",
"linux/platform_sensor_utils_linux.h",
"linux/sensor_data_linux.cc", "linux/sensor_data_linux.cc",
"linux/sensor_data_linux.h", "linux/sensor_data_linux.h",
"platform_sensor.cc", "platform_sensor.cc",
...@@ -30,12 +28,12 @@ component("generic_sensor") { ...@@ -30,12 +28,12 @@ component("generic_sensor") {
"platform_sensor_provider_android.h", "platform_sensor_provider_android.h",
"platform_sensor_provider_base.cc", "platform_sensor_provider_base.cc",
"platform_sensor_provider_base.h", "platform_sensor_provider_base.h",
"platform_sensor_provider_linux.cc",
"platform_sensor_provider_linux.h",
"platform_sensor_provider_mac.cc", "platform_sensor_provider_mac.cc",
"platform_sensor_provider_mac.h", "platform_sensor_provider_mac.h",
"platform_sensor_provider_win.cc", "platform_sensor_provider_win.cc",
"platform_sensor_provider_win.h", "platform_sensor_provider_win.h",
"platform_sensor_reader_linux.cc",
"platform_sensor_reader_linux.h",
"platform_sensor_reader_win.cc", "platform_sensor_reader_win.cc",
"platform_sensor_reader_win.h", "platform_sensor_reader_win.h",
"platform_sensor_win.cc", "platform_sensor_win.cc",
...@@ -67,12 +65,23 @@ component("generic_sensor") { ...@@ -67,12 +65,23 @@ component("generic_sensor") {
deps += [ ":jni_headers" ] deps += [ ":jni_headers" ]
} }
if (is_mac || is_linux) { if (is_mac) {
deps += [ "//device/sensors/public/cpp" ] deps += [ "//device/sensors/public/cpp" ]
libs = [ "IOKit.framework" ]
} }
if (is_mac) { if (use_udev) {
libs = [ "IOKit.framework" ] deps += [
"//device/base",
"//device/udev_linux",
]
sources += [
"linux/platform_sensor_manager.cc",
"linux/platform_sensor_manager.h",
"platform_sensor_provider_linux.cc",
"platform_sensor_provider_linux.h",
]
} }
if (is_win) { if (is_win) {
......
// Copyright 2016 The Chromium 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 "device/generic_sensor/linux/platform_sensor_manager.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "device/generic_sensor/linux/sensor_data_linux.h"
namespace device {
namespace {
std::string StringOrEmptyIfNull(const char* value) {
return value ? value : std::string();
}
} // namespace
SensorDeviceManager::SensorDeviceManager()
: observer_(this),
delegate_(nullptr),
task_runner_(base::ThreadTaskRunnerHandle::Get()) {
thread_checker_.DetachFromThread();
}
SensorDeviceManager::~SensorDeviceManager() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void SensorDeviceManager::Start(Delegate* delegate) {
DCHECK(thread_checker_.CalledOnValidThread());
base::ThreadRestrictions::AssertIOAllowed();
DCHECK(!delegate_);
delegate_ = delegate;
DeviceMonitorLinux* monitor = DeviceMonitorLinux::GetInstance();
observer_.Add(monitor);
monitor->Enumerate(
base::Bind(&SensorDeviceManager::OnDeviceAdded, base::Unretained(this)));
task_runner_->PostTask(
FROM_HERE,
base::Bind(&SensorDeviceManager::Delegate::OnSensorNodesEnumerated,
base::Unretained(delegate_)));
}
std::string SensorDeviceManager::GetUdevDeviceGetSubsystem(udev_device* dev) {
return StringOrEmptyIfNull(udev_device_get_subsystem(dev));
}
std::string SensorDeviceManager::GetUdevDeviceGetSyspath(udev_device* dev) {
return StringOrEmptyIfNull(udev_device_get_syspath(dev));
}
std::string SensorDeviceManager::GetUdevDeviceGetSysattrValue(
udev_device* dev,
const std::string& attribute) {
return StringOrEmptyIfNull(
udev_device_get_sysattr_value(dev, attribute.c_str()));
}
std::string SensorDeviceManager::GetUdevDeviceGetDevnode(udev_device* dev) {
return StringOrEmptyIfNull(udev_device_get_devnode(dev));
}
void SensorDeviceManager::OnDeviceAdded(udev_device* dev) {
const std::string subsystem = GetUdevDeviceGetSubsystem(dev);
if (subsystem.empty() || subsystem.compare("iio") != 0)
return;
const std::string sysfs_path = GetUdevDeviceGetSyspath(dev);
if (sysfs_path.empty())
return;
const std::string device_node = GetUdevDeviceGetDevnode(dev);
if (device_node.empty())
return;
const uint32_t first = static_cast<uint32_t>(mojom::SensorType::FIRST);
const uint32_t last = static_cast<uint32_t>(mojom::SensorType::LAST);
for (uint32_t i = first; i < last; ++i) {
SensorPathsLinux data;
mojom::SensorType type = static_cast<mojom::SensorType>(i);
if (!InitSensorData(type, &data))
continue;
std::vector<base::FilePath> sensor_file_names;
for (const std::vector<std::string>& names : data.sensor_file_names) {
for (const auto& name : names) {
const std::string value =
GetUdevDeviceGetSysattrValue(dev, name.c_str());
if (value.empty())
continue;
base::FilePath full_path = base::FilePath(sysfs_path).Append(name);
sensor_file_names.push_back(full_path);
break;
}
}
if (sensor_file_names.empty())
continue;
const std::string scaling_value =
GetUdevDeviceGetSysattrValue(dev, data.sensor_scale_name.c_str());
// If scaling value is not found, treat it as 1.
double sensor_scaling_value = 1;
if (!scaling_value.empty())
base::StringToDouble(scaling_value, &sensor_scaling_value);
const std::string offset_vallue =
GetUdevDeviceGetSysattrValue(dev, data.sensor_offset_file_name.c_str());
// If offset value is not found, treat it as 0.
double sensor_offset_value = 0;
if (!offset_vallue.empty())
base::StringToDouble(offset_vallue, &sensor_offset_value);
const std::string frequency_value = GetUdevDeviceGetSysattrValue(
dev, data.sensor_frequency_file_name.c_str());
// If frequency is not found, use default one from SensorPathsLinux struct.
double sensor_frequency_value = data.default_configuration.frequency();
// By default, |reporting_mode| is ON_CHANGE. But if platform provides
// sampling frequency, the reporting mode is CONTINUOUS.
mojom::ReportingMode reporting_mode = mojom::ReportingMode::ON_CHANGE;
if (!frequency_value.empty()) {
base::StringToDouble(frequency_value, &sensor_frequency_value);
reporting_mode = mojom::ReportingMode::CONTINUOUS;
}
// Update own cache of known sensor devices.
if (!base::ContainsKey(sensors_by_node_, device_node))
sensors_by_node_[device_node] = data.type;
std::unique_ptr<SensorInfoLinux> device(new SensorInfoLinux(
device_node, sensor_frequency_value, sensor_scaling_value,
sensor_offset_value, reporting_mode, data.apply_scaling_func,
std::move(sensor_file_names)));
task_runner_->PostTask(
FROM_HERE, base::Bind(&SensorDeviceManager::Delegate::OnDeviceAdded,
base::Unretained(delegate_), data.type,
base::Passed(&device)));
// One |dev| can represent more than one sensor.
// For example, there is an accelerometer and gyroscope represented by one
// |dev| in Chrome OS, kernel < 3.18. Thus, iterate through all possible
// types of sensors.
}
}
void SensorDeviceManager::OnDeviceRemoved(udev_device* dev) {
const std::string subsystem = GetUdevDeviceGetSubsystem(dev);
if (subsystem.empty() || subsystem.compare("iio") != 0)
return;
const std::string device_node = GetUdevDeviceGetDevnode(dev);
if (device_node.empty())
return;
auto sensor = sensors_by_node_.find(device_node);
if (sensor == sensors_by_node_.end())
return;
mojom::SensorType type = sensor->second;
sensors_by_node_.erase(sensor);
task_runner_->PostTask(
FROM_HERE, base::Bind(&SensorDeviceManager::Delegate::OnDeviceRemoved,
base::Unretained(delegate_), type, device_node));
}
} // namespace device
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_GENERIC_SENSOR_LINUX_PLATFORM_SENSOR_MANAGER_H_
#define DEVICE_GENERIC_SENSOR_LINUX_PLATFORM_SENSOR_MANAGER_H_
#include "base/scoped_observer.h"
#include "device/base/device_monitor_linux.h"
#include "device/generic_sensor/public/interfaces/sensor.mojom.h"
namespace device {
struct SensorInfoLinux;
// This SensorDeviceManager uses LinuxDeviceMonitor to enumerate devices
// and listen to "add/removed" events to notify |provider_| about
// added or removed iio devices. It has own cache to speed up an identification
// process of removed devices.
class DEVICE_GENERIC_SENSOR_EXPORT SensorDeviceManager
: public DeviceMonitorLinux::Observer {
public:
class Delegate {
public:
// Called when SensorDeviceManager has enumerated through all possible
// iio udev devices.
virtual void OnSensorNodesEnumerated() = 0;
// Called after SensorDeviceManager has identified a udev device, which
// belongs to "iio" subsystem.
virtual void OnDeviceAdded(mojom::SensorType type,
std::unique_ptr<SensorInfoLinux> sensor) = 0;
// Called after "removed" event is received from LinuxDeviceMonitor and
// sensor is identified as known.
virtual void OnDeviceRemoved(mojom::SensorType type,
const std::string& device_node) = 0;
protected:
virtual ~Delegate() {}
};
SensorDeviceManager();
~SensorDeviceManager() override;
// Starts this service.
virtual void Start(Delegate* delegate);
protected:
using SensorDeviceMap = std::unordered_map<std::string, mojom::SensorType>;
// Wrappers around udev system methods that can be implemented differently
// by tests.
virtual std::string GetUdevDeviceGetSubsystem(udev_device* dev);
virtual std::string GetUdevDeviceGetSyspath(udev_device* dev);
virtual std::string GetUdevDeviceGetSysattrValue(
udev_device* dev,
const std::string& attribute);
virtual std::string GetUdevDeviceGetDevnode(udev_device* dev);
// DeviceMonitorLinux::Observer:
void OnDeviceAdded(udev_device* udev_device) override;
void OnDeviceRemoved(udev_device* device) override;
// Represents a map of sensors that are already known to this manager after
// initial enumeration.
SensorDeviceMap sensors_by_node_;
base::ThreadChecker thread_checker_;
ScopedObserver<DeviceMonitorLinux, DeviceMonitorLinux::Observer> observer_;
Delegate* delegate_;
// A task runner, which |delegate_| lives on.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(SensorDeviceManager);
};
} // namespace device
#endif // DEVICE_GENERIC_SENSOR_LINUX_PLATFORM_SENSOR_MANAGER_H_
// Copyright 2016 The Chromium 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 "device/generic_sensor/linux/platform_sensor_utils_linux.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "device/generic_sensor/public/cpp/sensor_reading.h"
namespace device {
namespace {
bool InitSensorPaths(const std::vector<std::string>& input_names,
const char* base_path,
std::vector<base::FilePath>* sensor_paths) {
// Search the iio/devices directory for a subdirectory (eg "device0" or
// "iio:device0") that contains the specified input_name file (eg
// "in_illuminance_input" or "in_illuminance0_input").
base::FileEnumerator dir_enumerator(base::FilePath(base_path), false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath check_path = dir_enumerator.Next(); !check_path.empty();
check_path = dir_enumerator.Next()) {
for (auto const& file_name : input_names) {
base::FilePath full_path = check_path.Append(file_name);
if (base::PathExists(full_path)) {
sensor_paths->push_back(full_path);
return true;
}
}
}
return false;
}
bool GetSensorFilePaths(const SensorDataLinux& data,
std::vector<base::FilePath>* sensor_paths) {
DCHECK(sensor_paths->empty());
// Depending on a sensor, there can be up to three sets of files that need
// to be checked. If one of three files is not found, a sensor is
// treated as a non-existing one.
for (auto const& file_names : data.sensor_file_names) {
// Supply InitSensorPaths() with a set of files.
// Only one file from each set should be found.
if (!InitSensorPaths(file_names, data.base_path_sensor_linux, sensor_paths))
return false;
}
return true;
}
// Returns -1 if unable to read a scaling value.
// Otherwise, returns a scaling value read from a file.
double ReadSensorScalingValue(const base::FilePath& scale_file_path) {
std::string value;
if (!base::ReadFileToString(scale_file_path, &value))
return -1;
double scaling_value;
base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
if (!base::StringToDouble(value, &scaling_value))
return -1;
return scaling_value;
}
} // namespace
// static
std::unique_ptr<SensorReader> SensorReader::Create(
const SensorDataLinux& data) {
base::ThreadRestrictions::AssertIOAllowed();
std::vector<base::FilePath> sensor_paths;
if (!GetSensorFilePaths(data, &sensor_paths))
return nullptr;
DCHECK(!sensor_paths.empty());
// Scaling value is 1 if scaling file is not specified is |data| or scaling
// file is not found.
double scaling_value = 1;
if (!data.sensor_scale_name.empty()) {
const base::FilePath scale_file_path =
sensor_paths.back().DirName().Append(data.sensor_scale_name);
if (base::PathExists(scale_file_path))
scaling_value = ReadSensorScalingValue(scale_file_path);
}
// A file with a scaling value is found, but couldn't be read.
if (scaling_value == -1)
return nullptr;
return base::WrapUnique(new SensorReader(
std::move(sensor_paths), scaling_value, data.apply_scaling_func));
}
SensorReader::SensorReader(
std::vector<base::FilePath> sensor_paths,
double scaling_value,
const SensorDataLinux::ReaderFunctor& apply_scaling_func)
: sensor_paths_(std::move(sensor_paths)),
scaling_value_(scaling_value),
apply_scaling_func_(apply_scaling_func) {
DCHECK(!sensor_paths_.empty());
}
SensorReader::~SensorReader() = default;
bool SensorReader::ReadSensorReading(SensorReading* reading) {
base::ThreadRestrictions::AssertIOAllowed();
SensorReading readings;
DCHECK_LE(sensor_paths_.size(), arraysize(readings.values));
int i = 0;
for (const auto& path : sensor_paths_) {
std::string new_read_value;
if (!base::ReadFileToString(path, &new_read_value))
return false;
double new_value = 0;
base::TrimWhitespaceASCII(new_read_value, base::TRIM_ALL, &new_read_value);
if (!base::StringToDouble(new_read_value, &new_value))
return false;
readings.values[i++] = new_value;
}
if (!apply_scaling_func_.is_null())
apply_scaling_func_.Run(scaling_value_, readings);
*reading = readings;
return true;
}
} // namespace device
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_GENERIC_SENSOR_LINUX_PLATFORM_SENSOR_UTILS_LINUX_H_
#define DEVICE_GENERIC_SENSOR_LINUX_PLATFORM_SENSOR_UTILS_LINUX_H_
#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "device/generic_sensor/generic_sensor_export.h"
#include "device/generic_sensor/linux/sensor_data_linux.h"
namespace device {
struct SensorDataLinux;
struct SensorReading;
// Generic reader class that reads sensors data from
// sensors files located in the base iio folder.
class DEVICE_GENERIC_SENSOR_EXPORT SensorReader {
public:
~SensorReader();
// Creates a new instance of SensorReader if sensor read files
// has been found and |sensor_paths_| are set.
static std::unique_ptr<SensorReader> Create(const SensorDataLinux& data);
// Reads sensor values into |*reading| from sensor files
// specified in |sensor_paths_|.
bool ReadSensorReading(SensorReading* reading);
private:
SensorReader(std::vector<base::FilePath> sensor_paths,
double scaling_value,
const SensorDataLinux::ReaderFunctor& apply_scaling_func);
// Contains paths to sensor files that are set when
// Create() is called.
const std::vector<base::FilePath> sensor_paths_;
// Scaling values that are applied to raw data from sensors.
const double scaling_value_;
// Used to apply scalings and invert signs if needed.
const SensorDataLinux::ReaderFunctor apply_scaling_func_;
DISALLOW_COPY_AND_ASSIGN(SensorReader);
};
} // namespace device
#endif // DEVICE_GENERIC_SENSOR_LINUX_PLATFORM_SENSOR_UTILS_LINUX_H_
...@@ -23,12 +23,11 @@ const char kChangedAxisKernelVersion[] = "3.18.0"; ...@@ -23,12 +23,11 @@ const char kChangedAxisKernelVersion[] = "3.18.0";
const base::FilePath::CharType* kSensorsBasePath = const base::FilePath::CharType* kSensorsBasePath =
FILE_PATH_LITERAL("/sys/bus/iio/devices"); FILE_PATH_LITERAL("/sys/bus/iio/devices");
void InitAmbientLightSensorData(SensorDataLinux* data) { void InitAmbientLightSensorData(SensorPathsLinux* data) {
std::vector<std::string> file_names{ std::vector<std::string> file_names{
"in_illuminance0_input", "in_illuminance_input", "in_illuminance0_raw", "in_illuminance0_input", "in_illuminance_input", "in_illuminance0_raw",
"in_illuminance_raw"}; "in_illuminance_raw"};
data->sensor_file_names.push_back(std::move(file_names)); data->sensor_file_names.push_back(std::move(file_names));
data->reporting_mode = mojom::ReportingMode::ON_CHANGE;
data->default_configuration = data->default_configuration =
PlatformSensorConfiguration(kDefaultAmbientLightFrequencyHz); PlatformSensorConfiguration(kDefaultAmbientLightFrequencyHz);
} }
...@@ -41,7 +40,7 @@ void MaybeCheckKernelVersionAndAssignFileNames( ...@@ -41,7 +40,7 @@ void MaybeCheckKernelVersionAndAssignFileNames(
const std::vector<std::string>& file_names_x, const std::vector<std::string>& file_names_x,
const std::vector<std::string>& file_names_y, const std::vector<std::string>& file_names_y,
const std::vector<std::string>& file_names_z, const std::vector<std::string>& file_names_z,
SensorDataLinux* data) { SensorPathsLinux* data) {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
const base::Version checked_kernel_version(kChangedAxisKernelVersion); const base::Version checked_kernel_version(kChangedAxisKernelVersion);
DCHECK(checked_kernel_version.IsValid()); DCHECK(checked_kernel_version.IsValid());
...@@ -62,7 +61,7 @@ void MaybeCheckKernelVersionAndAssignFileNames( ...@@ -62,7 +61,7 @@ void MaybeCheckKernelVersionAndAssignFileNames(
} }
// TODO(maksims): add support for lid accelerometer on chromeos. // TODO(maksims): add support for lid accelerometer on chromeos.
void InitAccelerometerSensorData(SensorDataLinux* data) { void InitAccelerometerSensorData(SensorPathsLinux* data) {
std::vector<std::string> file_names_x{"in_accel_x_base_raw", std::vector<std::string> file_names_x{"in_accel_x_base_raw",
"in_accel_x_raw"}; "in_accel_x_raw"};
std::vector<std::string> file_names_y{"in_accel_y_base_raw", std::vector<std::string> file_names_y{"in_accel_y_base_raw",
...@@ -72,32 +71,35 @@ void InitAccelerometerSensorData(SensorDataLinux* data) { ...@@ -72,32 +71,35 @@ void InitAccelerometerSensorData(SensorDataLinux* data) {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
data->sensor_scale_name = "in_accel_base_scale"; data->sensor_scale_name = "in_accel_base_scale";
data->apply_scaling_func = data->sensor_frequency_file_name = "in_accel_base_sampling_frequency";
base::Bind([](double scaling_value, SensorReading& reading) { data->apply_scaling_func = base::Bind(
double scaling = kMeanGravity / scaling_value; [](double scaling_value, double offset, SensorReading& reading) {
double scaling = (kMeanGravity / scaling_value) + offset;
reading.values[0] = scaling * reading.values[0]; reading.values[0] = scaling * reading.values[0];
reading.values[1] = scaling * reading.values[1]; reading.values[1] = scaling * reading.values[1];
reading.values[2] = scaling * reading.values[2]; reading.values[2] = scaling * reading.values[2];
}); });
#else #else
data->sensor_scale_name = "in_accel_scale"; data->sensor_scale_name = "in_accel_scale";
data->apply_scaling_func = data->sensor_offset_file_name = "in_accel_offset";
base::Bind([](double scaling_value, SensorReading& reading) { data->sensor_frequency_file_name = "in_accel_sampling_frequency";
data->apply_scaling_func = base::Bind(
[](double scaling_value, double offset, SensorReading& reading) {
double scaling = scaling_value + offset;
// Adapt Linux reading values to generic sensor api specs. // Adapt Linux reading values to generic sensor api specs.
reading.values[0] = -scaling_value * reading.values[0]; reading.values[0] = -scaling * reading.values[0];
reading.values[1] = -scaling_value * reading.values[1]; reading.values[1] = -scaling * reading.values[1];
reading.values[2] = -scaling_value * reading.values[2]; reading.values[2] = -scaling * reading.values[2];
}); });
#endif #endif
MaybeCheckKernelVersionAndAssignFileNames(file_names_x, file_names_y, MaybeCheckKernelVersionAndAssignFileNames(file_names_x, file_names_y,
file_names_z, data); file_names_z, data);
data->reporting_mode = mojom::ReportingMode::CONTINUOUS;
data->default_configuration = data->default_configuration =
PlatformSensorConfiguration(kDefaultAccelerometerFrequencyHz); PlatformSensorConfiguration(kDefaultAccelerometerFrequencyHz);
} }
void InitGyroscopeSensorData(SensorDataLinux* data) { void InitGyroscopeSensorData(SensorPathsLinux* data) {
std::vector<std::string> file_names_x{"in_anglvel_x_base_raw", std::vector<std::string> file_names_x{"in_anglvel_x_base_raw",
"in_anglvel_x_raw"}; "in_anglvel_x_raw"};
std::vector<std::string> file_names_y{"in_anglvel_y_base_raw", std::vector<std::string> file_names_y{"in_anglvel_y_base_raw",
...@@ -106,10 +108,11 @@ void InitGyroscopeSensorData(SensorDataLinux* data) { ...@@ -106,10 +108,11 @@ void InitGyroscopeSensorData(SensorDataLinux* data) {
"in_anglvel_z_raw"}; "in_anglvel_z_raw"};
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
data->sensor_scale_name = "in_anglvel_base_scale"; data->sensor_scale_name = "in_anglvel_base_scale";
data->apply_scaling_func = data->sensor_frequency_file_name = "in_anglvel_base_frequency";
base::Bind([](double scaling_value, SensorReading& reading) { data->apply_scaling_func = base::Bind(
[](double scaling_value, double offset, SensorReading& reading) {
double scaling = double scaling =
kMeanGravity * kRadiansInDegreesPerSecond / scaling_value; kMeanGravity * kRadiansInDegreesPerSecond / scaling_value + offset;
// Adapt CrOS reading values to generic sensor api specs. // Adapt CrOS reading values to generic sensor api specs.
reading.values[0] = -scaling * reading.values[0]; reading.values[0] = -scaling * reading.values[0];
reading.values[1] = -scaling * reading.values[1]; reading.values[1] = -scaling * reading.values[1];
...@@ -117,54 +120,60 @@ void InitGyroscopeSensorData(SensorDataLinux* data) { ...@@ -117,54 +120,60 @@ void InitGyroscopeSensorData(SensorDataLinux* data) {
}); });
#else #else
data->sensor_scale_name = "in_anglvel_scale"; data->sensor_scale_name = "in_anglvel_scale";
data->apply_scaling_func = data->sensor_offset_file_name = "in_anglvel_offset";
base::Bind([](double scaling_value, SensorReading& reading) { data->sensor_frequency_file_name = "in_anglvel_sampling_frequency";
reading.values[0] = scaling_value * reading.values[0]; data->apply_scaling_func = base::Bind(
reading.values[1] = scaling_value * reading.values[1]; [](double scaling_value, double offset, SensorReading& reading) {
reading.values[2] = scaling_value * reading.values[2]; double scaling = scaling_value + offset;
reading.values[0] = scaling * reading.values[0];
reading.values[1] = scaling * reading.values[1];
reading.values[2] = scaling * reading.values[2];
}); });
#endif #endif
MaybeCheckKernelVersionAndAssignFileNames(file_names_x, file_names_y, MaybeCheckKernelVersionAndAssignFileNames(file_names_x, file_names_y,
file_names_z, data); file_names_z, data);
data->reporting_mode = mojom::ReportingMode::CONTINUOUS;
data->default_configuration = data->default_configuration =
PlatformSensorConfiguration(kDefaultGyroscopeFrequencyHz); PlatformSensorConfiguration(kDefaultGyroscopeFrequencyHz);
} }
// TODO(maksims): Verify magnitometer works correctly on a chromebook when // TODO(maksims): Verify magnitometer works correctly on a chromebook when
// I get one with that sensor onboard. // I get one with that sensor onboard.
void InitMagnitometerSensorData(SensorDataLinux* data) { void InitMagnitometerSensorData(SensorPathsLinux* data) {
std::vector<std::string> file_names_x{"in_magn_x_raw"}; std::vector<std::string> file_names_x{"in_magn_x_raw"};
std::vector<std::string> file_names_y{"in_magn_y_raw"}; std::vector<std::string> file_names_y{"in_magn_y_raw"};
std::vector<std::string> file_names_z{"in_magn_z_raw"}; std::vector<std::string> file_names_z{"in_magn_z_raw"};
data->sensor_scale_name = "in_magn_scale"; data->sensor_scale_name = "in_magn_scale";
data->apply_scaling_func = base::Bind([](double scaling_value, data->sensor_offset_file_name = "in_magn_offset";
SensorReading& reading) { data->sensor_frequency_file_name = "in_magn_sampling_frequency";
reading.values[0] = scaling_value * kMicroteslaInGauss * reading.values[0]; data->apply_scaling_func = base::Bind(
reading.values[1] = scaling_value * kMicroteslaInGauss * reading.values[1]; [](double scaling_value, double offset, SensorReading& reading) {
reading.values[2] = scaling_value * kMicroteslaInGauss * reading.values[2]; double scaling = scaling_value + offset;
}); reading.values[0] = scaling * kMicroteslaInGauss * reading.values[0];
reading.values[1] = scaling * kMicroteslaInGauss * reading.values[1];
reading.values[2] = scaling * kMicroteslaInGauss * reading.values[2];
});
MaybeCheckKernelVersionAndAssignFileNames(file_names_x, file_names_y, MaybeCheckKernelVersionAndAssignFileNames(file_names_x, file_names_y,
file_names_z, data); file_names_z, data);
data->reporting_mode = mojom::ReportingMode::CONTINUOUS;
data->default_configuration = data->default_configuration =
PlatformSensorConfiguration(kDefaultMagnetometerFrequencyHz); PlatformSensorConfiguration(kDefaultMagnetometerFrequencyHz);
} }
} // namespace } // namespace
SensorDataLinux::SensorDataLinux() : base_path_sensor_linux(kSensorsBasePath) {} SensorPathsLinux::SensorPathsLinux()
: base_path_sensor_linux(kSensorsBasePath) {}
SensorDataLinux::~SensorDataLinux() = default; SensorPathsLinux::~SensorPathsLinux() = default;
SensorDataLinux::SensorDataLinux(const SensorDataLinux& other) = default; SensorPathsLinux::SensorPathsLinux(const SensorPathsLinux& other) = default;
bool InitSensorData(SensorType type, SensorDataLinux* data) { bool InitSensorData(SensorType type, SensorPathsLinux* data) {
DCHECK(data); DCHECK(data);
data->type = type;
switch (type) { switch (type) {
case SensorType::AMBIENT_LIGHT: case SensorType::AMBIENT_LIGHT:
InitAmbientLightSensorData(data); InitAmbientLightSensorData(data);
...@@ -179,11 +188,28 @@ bool InitSensorData(SensorType type, SensorDataLinux* data) { ...@@ -179,11 +188,28 @@ bool InitSensorData(SensorType type, SensorDataLinux* data) {
InitMagnitometerSensorData(data); InitMagnitometerSensorData(data);
break; break;
default: default:
NOTIMPLEMENTED();
return false; return false;
} }
return true; return true;
} }
SensorInfoLinux::SensorInfoLinux(
const std::string& sensor_device_node,
double sensor_device_frequency,
double sensor_device_scaling_value,
double sensor_device_offset_value,
mojom::ReportingMode mode,
SensorPathsLinux::ReaderFunctor scaling_func,
std::vector<base::FilePath> device_reading_files)
: device_node(sensor_device_node),
device_frequency(sensor_device_frequency),
device_scaling_value(sensor_device_scaling_value),
device_offset_value(sensor_device_offset_value),
reporting_mode(mode),
apply_scaling_func(scaling_func),
device_reading_files(std::move(device_reading_files)) {}
SensorInfoLinux::~SensorInfoLinux() = default;
} // namespace device } // namespace device
...@@ -10,19 +10,21 @@ ...@@ -10,19 +10,21 @@
namespace device { namespace device {
class PlatformSensorConfiguration;
struct SensorReading; struct SensorReading;
// This structure represents a context that is used to // This structure represents a context that is used to identify a udev device
// create a type specific SensorReader and a concrete // and create a type specific SensorInfoLinux. For example, when a
// sensor that uses the SensorReader to read sensor // SensorDeviceManager receives a udev device, it uses this structure to
// data from files specified in the |sensor_file_names|. // identify what type of sensor that is and creates a SensorInfoLinux structure
struct DEVICE_GENERIC_SENSOR_EXPORT SensorDataLinux { // that holds all the necessary information to create a PlatformSensorLinux.
using ReaderFunctor = struct DEVICE_GENERIC_SENSOR_EXPORT SensorPathsLinux {
base::Callback<void(double scaling, SensorReading& reading)>; using ReaderFunctor = base::Callback<
void(double scaling, double offset, SensorReading& reading)>;
SensorDataLinux();
~SensorDataLinux(); SensorPathsLinux();
SensorDataLinux(const SensorDataLinux& other); ~SensorPathsLinux();
SensorPathsLinux(const SensorPathsLinux& other);
// Provides a base path to all sensors. // Provides a base path to all sensors.
const base::FilePath::CharType* base_path_sensor_linux; const base::FilePath::CharType* base_path_sensor_linux;
// Provides an array of sensor file names to be searched for. // Provides an array of sensor file names to be searched for.
...@@ -31,16 +33,51 @@ struct DEVICE_GENERIC_SENSOR_EXPORT SensorDataLinux { ...@@ -31,16 +33,51 @@ struct DEVICE_GENERIC_SENSOR_EXPORT SensorDataLinux {
std::vector<std::vector<std::string>> sensor_file_names; std::vector<std::vector<std::string>> sensor_file_names;
// Scaling file to be found. // Scaling file to be found.
std::string sensor_scale_name; std::string sensor_scale_name;
// Frequency file to be found.
std::string sensor_frequency_file_name;
// Offset file to be found.
std::string sensor_offset_file_name;
// Used to apply scalings to raw sensor data. // Used to apply scalings to raw sensor data.
ReaderFunctor apply_scaling_func; ReaderFunctor apply_scaling_func;
// Reporting mode of a sensor. // Sensor type
mojom::ReportingMode reporting_mode; mojom::SensorType type;
// Default configuration of a sensor. // Default configuration of a sensor.
PlatformSensorConfiguration default_configuration; PlatformSensorConfiguration default_configuration;
}; };
// Initializes a sensor type specific data. // Initializes sensor data according to |type|.
bool InitSensorData(mojom::SensorType type, SensorDataLinux* data); bool DEVICE_GENERIC_SENSOR_EXPORT InitSensorData(mojom::SensorType type,
SensorPathsLinux* data);
// This structure represents an iio device, which info is taken
// from udev service. If a client requests a sensor from a provider,
// the provider takes this initialized and stored structure and uses it to
// create a requested PlatformSensorLinux of a certain type.
struct SensorInfoLinux {
// Represents current sensor device node.
const std::string device_node;
// Represents frequency of a sensor.
const double device_frequency;
// Represents scaling value to be applied on raw data.
const double device_scaling_value;
// Represents offset value that must be applied on raw data.
const double device_offset_value;
// Reporting mode of a sensor taken from SensorDataLinux.
const mojom::ReportingMode reporting_mode;
// Functor that is used to convert raw data.
const SensorPathsLinux::ReaderFunctor apply_scaling_func;
// Sensor files in sysfs. Used to poll data.
const std::vector<base::FilePath> device_reading_files;
SensorInfoLinux(const std::string& sensor_device_node,
double sensor_device_frequency,
double sensor_device_scaling_value,
double sensor_device_offset_value,
mojom::ReportingMode mode,
SensorPathsLinux::ReaderFunctor scaling_func,
std::vector<base::FilePath> iio_device_reading_files);
~SensorInfoLinux();
};
} // namespace device } // namespace device
......
This diff is collapsed.
...@@ -4,10 +4,8 @@ ...@@ -4,10 +4,8 @@
#include "device/generic_sensor/platform_sensor_linux.h" #include "device/generic_sensor/platform_sensor_linux.h"
#include "base/threading/thread.h"
#include "base/timer/timer.h"
#include "device/generic_sensor/linux/platform_sensor_utils_linux.h"
#include "device/generic_sensor/linux/sensor_data_linux.h" #include "device/generic_sensor/linux/sensor_data_linux.h"
#include "device/generic_sensor/platform_sensor_reader_linux.h"
namespace device { namespace device {
...@@ -25,48 +23,61 @@ PlatformSensorLinux::PlatformSensorLinux( ...@@ -25,48 +23,61 @@ PlatformSensorLinux::PlatformSensorLinux(
mojom::SensorType type, mojom::SensorType type,
mojo::ScopedSharedBufferMapping mapping, mojo::ScopedSharedBufferMapping mapping,
PlatformSensorProvider* provider, PlatformSensorProvider* provider,
const SensorDataLinux& data, const SensorInfoLinux* sensor_device,
std::unique_ptr<SensorReader> sensor_reader, scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner)
scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner_)
: PlatformSensor(type, std::move(mapping), provider), : PlatformSensor(type, std::move(mapping), provider),
timer_(new base::RepeatingTimer()), default_configuration_(
default_configuration_(data.default_configuration), PlatformSensorConfiguration(sensor_device->device_frequency)),
reporting_mode_(data.reporting_mode), reporting_mode_(sensor_device->reporting_mode),
sensor_reader_(std::move(sensor_reader)), weak_factory_(this) {
polling_thread_task_runner_(polling_thread_task_runner_), sensor_reader_ =
weak_factory_(this) {} SensorReader::Create(sensor_device, this, polling_thread_task_runner);
PlatformSensorLinux::~PlatformSensorLinux() {
polling_thread_task_runner_->DeleteSoon(FROM_HERE, timer_);
} }
PlatformSensorLinux::~PlatformSensorLinux() = default;
mojom::ReportingMode PlatformSensorLinux::GetReportingMode() { mojom::ReportingMode PlatformSensorLinux::GetReportingMode() {
DCHECK(task_runner_->BelongsToCurrentThread());
return reporting_mode_; return reporting_mode_;
} }
void PlatformSensorLinux::UpdatePlatformSensorReading(SensorReading reading) {
DCHECK(task_runner_->BelongsToCurrentThread());
bool notifyNeeded = false;
if (GetReportingMode() == mojom::ReportingMode::ON_CHANGE) {
if (!HaveValuesChanged(reading, old_values_))
return;
notifyNeeded = true;
}
old_values_ = reading;
reading.timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
UpdateSensorReading(reading, notifyNeeded);
}
void PlatformSensorLinux::NotifyPlatformSensorError() {
DCHECK(task_runner_->BelongsToCurrentThread());
NotifySensorError();
}
bool PlatformSensorLinux::StartSensor( bool PlatformSensorLinux::StartSensor(
const PlatformSensorConfiguration& configuration) { const PlatformSensorConfiguration& configuration) {
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
return polling_thread_task_runner_->PostTask( if (!sensor_reader_)
FROM_HERE, base::Bind(&PlatformSensorLinux::BeginPoll, return false;
weak_factory_.GetWeakPtr(), configuration)); return sensor_reader_->StartFetchingData(configuration);
} }
void PlatformSensorLinux::StopSensor() { void PlatformSensorLinux::StopSensor() {
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
polling_thread_task_runner_->PostTask( DCHECK(sensor_reader_);
FROM_HERE, base::Bind(&PlatformSensorLinux::StopPoll, this)); sensor_reader_->StopFetchingData();
} }
bool PlatformSensorLinux::CheckSensorConfiguration( bool PlatformSensorLinux::CheckSensorConfiguration(
const PlatformSensorConfiguration& configuration) { const PlatformSensorConfiguration& configuration) {
DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(task_runner_->BelongsToCurrentThread());
// TODO(maksims): make this sensor dependent.
// For example, in case of accelerometer, check current polling frequency
// exposed by iio driver.
return configuration.frequency() > 0 && return configuration.frequency() > 0 &&
configuration.frequency() <= configuration.frequency() <= default_configuration_.frequency();
mojom::SensorConfiguration::kMaxAllowedFrequency;
} }
PlatformSensorConfiguration PlatformSensorLinux::GetDefaultConfiguration() { PlatformSensorConfiguration PlatformSensorLinux::GetDefaultConfiguration() {
...@@ -74,41 +85,4 @@ PlatformSensorConfiguration PlatformSensorLinux::GetDefaultConfiguration() { ...@@ -74,41 +85,4 @@ PlatformSensorConfiguration PlatformSensorLinux::GetDefaultConfiguration() {
return default_configuration_; return default_configuration_;
} }
void PlatformSensorLinux::BeginPoll(
const PlatformSensorConfiguration& configuration) {
DCHECK(polling_thread_task_runner_->BelongsToCurrentThread());
timer_->Start(FROM_HERE, base::TimeDelta::FromMicroseconds(
base::Time::kMicrosecondsPerSecond /
configuration.frequency()),
this, &PlatformSensorLinux::PollForReadingData);
}
void PlatformSensorLinux::StopPoll() {
DCHECK(polling_thread_task_runner_->BelongsToCurrentThread());
timer_->Stop();
}
void PlatformSensorLinux::PollForReadingData() {
DCHECK(polling_thread_task_runner_->BelongsToCurrentThread());
SensorReading reading;
if (!sensor_reader_->ReadSensorReading(&reading)) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&PlatformSensorLinux::NotifySensorError, this));
StopPoll();
return;
}
bool notifyNeeded = false;
if (GetReportingMode() == mojom::ReportingMode::ON_CHANGE) {
if (!HaveValuesChanged(reading, old_values_))
return;
notifyNeeded = true;
}
old_values_ = reading;
reading.timestamp = (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
UpdateSensorReading(reading, notifyNeeded);
}
} // namespace device } // namespace device
...@@ -8,15 +8,13 @@ ...@@ -8,15 +8,13 @@
#include "device/generic_sensor/platform_sensor.h" #include "device/generic_sensor/platform_sensor.h"
namespace base { namespace base {
class RepeatingTimer;
class SingleThreadTaskRunner; class SingleThreadTaskRunner;
class Thread;
} }
namespace device { namespace device {
class SensorReader; class SensorReader;
struct SensorDataLinux; struct SensorInfoLinux;
class PlatformSensorLinux : public PlatformSensor { class PlatformSensorLinux : public PlatformSensor {
public: public:
...@@ -24,13 +22,17 @@ class PlatformSensorLinux : public PlatformSensor { ...@@ -24,13 +22,17 @@ class PlatformSensorLinux : public PlatformSensor {
mojom::SensorType type, mojom::SensorType type,
mojo::ScopedSharedBufferMapping mapping, mojo::ScopedSharedBufferMapping mapping,
PlatformSensorProvider* provider, PlatformSensorProvider* provider,
const SensorDataLinux& data, const SensorInfoLinux* sensor_device,
std::unique_ptr<SensorReader> sensor_reader,
scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner); scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner);
// Thread safe.
mojom::ReportingMode GetReportingMode() override; mojom::ReportingMode GetReportingMode() override;
// Called by a sensor reader. Takes new readings.
void UpdatePlatformSensorReading(SensorReading reading);
// Called by a sensor reader if an error occurs.
void NotifyPlatformSensorError();
protected: protected:
~PlatformSensorLinux() override; ~PlatformSensorLinux() override;
bool StartSensor(const PlatformSensorConfiguration& configuration) override; bool StartSensor(const PlatformSensorConfiguration& configuration) override;
...@@ -40,16 +42,6 @@ class PlatformSensorLinux : public PlatformSensor { ...@@ -40,16 +42,6 @@ class PlatformSensorLinux : public PlatformSensor {
PlatformSensorConfiguration GetDefaultConfiguration() override; PlatformSensorConfiguration GetDefaultConfiguration() override;
private: private:
void BeginPoll(const PlatformSensorConfiguration& configuration);
void StopPoll();
// Triggers |sensor_reader_| to read new sensor data.
// If new data is read, UpdateSensorReading() is called.
void PollForReadingData();
// Owned timer to be deleted on a polling thread.
base::RepeatingTimer* timer_;
const PlatformSensorConfiguration default_configuration_; const PlatformSensorConfiguration default_configuration_;
const mojom::ReportingMode reporting_mode_; const mojom::ReportingMode reporting_mode_;
...@@ -57,9 +49,6 @@ class PlatformSensorLinux : public PlatformSensor { ...@@ -57,9 +49,6 @@ class PlatformSensorLinux : public PlatformSensor {
// and stores them to a SensorReading structure. // and stores them to a SensorReading structure.
std::unique_ptr<SensorReader> sensor_reader_; std::unique_ptr<SensorReader> sensor_reader_;
// A task runner that is used to poll sensor data.
scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner_;
// Stores previously read values that are used to // Stores previously read values that are used to
// determine whether the recent values are changed // determine whether the recent values are changed
// and IPC can be notified that updates are available. // and IPC can be notified that updates are available.
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
#include "device/generic_sensor/platform_sensor_provider_android.h" #include "device/generic_sensor/platform_sensor_provider_android.h"
#elif defined(OS_WIN) #elif defined(OS_WIN)
#include "device/generic_sensor/platform_sensor_provider_win.h" #include "device/generic_sensor/platform_sensor_provider_win.h"
#elif defined(OS_LINUX) #elif defined(OS_LINUX) && defined(USE_UDEV)
#include "device/generic_sensor/platform_sensor_provider_linux.h" #include "device/generic_sensor/platform_sensor_provider_linux.h"
#endif #endif
...@@ -38,8 +38,10 @@ PlatformSensorProvider* PlatformSensorProvider::GetInstance() { ...@@ -38,8 +38,10 @@ PlatformSensorProvider* PlatformSensorProvider::GetInstance() {
return PlatformSensorProviderAndroid::GetInstance(); return PlatformSensorProviderAndroid::GetInstance();
#elif defined(OS_WIN) #elif defined(OS_WIN)
return PlatformSensorProviderWin::GetInstance(); return PlatformSensorProviderWin::GetInstance();
#elif defined(OS_LINUX) #elif defined(OS_LINUX) && defined(USE_UDEV)
return PlatformSensorProviderLinux::GetInstance(); return PlatformSensorProviderLinux::GetInstance();
#else
return nullptr;
#endif #endif
} }
......
...@@ -32,8 +32,7 @@ void PlatformSensorProviderBase::CreateSensor( ...@@ -32,8 +32,7 @@ void PlatformSensorProviderBase::CreateSensor(
return; return;
} }
mojo::ScopedSharedBufferMapping mapping = shared_buffer_handle_->MapAtOffset( mojo::ScopedSharedBufferMapping mapping = MapSharedBufferForType(type);
kReadingBufferSize, SensorReadingSharedBuffer::GetOffset(type));
if (!mapping) { if (!mapping) {
callback.Run(nullptr); callback.Run(nullptr);
return; return;
...@@ -43,8 +42,6 @@ void PlatformSensorProviderBase::CreateSensor( ...@@ -43,8 +42,6 @@ void PlatformSensorProviderBase::CreateSensor(
if (it != requests_map_.end()) { if (it != requests_map_.end()) {
it->second.push_back(callback); it->second.push_back(callback);
} else { // This is the first CreateSensor call. } else { // This is the first CreateSensor call.
memset(mapping.get(), 0, kReadingBufferSize);
requests_map_[type] = CallbackQueue({callback}); requests_map_[type] = CallbackQueue({callback});
CreateSensorInternal( CreateSensorInternal(
...@@ -117,4 +114,20 @@ void PlatformSensorProviderBase::NotifySensorCreated( ...@@ -117,4 +114,20 @@ void PlatformSensorProviderBase::NotifySensorCreated(
requests_map_.erase(type); requests_map_.erase(type);
} }
std::vector<mojom::SensorType>
PlatformSensorProviderBase::GetPendingRequestTypes() {
std::vector<mojom::SensorType> request_types;
for (auto const& entry : requests_map_)
request_types.push_back(entry.first);
return request_types;
}
mojo::ScopedSharedBufferMapping
PlatformSensorProviderBase::MapSharedBufferForType(mojom::SensorType type) {
mojo::ScopedSharedBufferMapping mapping = shared_buffer_handle_->MapAtOffset(
kReadingBufferSize, SensorReadingSharedBuffer::GetOffset(type));
memset(mapping.get(), 0, kReadingBufferSize);
return mapping;
}
} // namespace device } // namespace device
...@@ -53,13 +53,19 @@ class DEVICE_GENERIC_SENSOR_EXPORT PlatformSensorProviderBase ...@@ -53,13 +53,19 @@ class DEVICE_GENERIC_SENSOR_EXPORT PlatformSensorProviderBase
// are no sensors left. // are no sensors left.
virtual void AllSensorsRemoved() {} virtual void AllSensorsRemoved() {}
void NotifySensorCreated(mojom::SensorType type,
scoped_refptr<PlatformSensor> sensor);
std::vector<mojom::SensorType> GetPendingRequestTypes();
mojo::ScopedSharedBufferMapping MapSharedBufferForType(
mojom::SensorType type);
private: private:
friend class PlatformSensor; // To call RemoveSensor(); friend class PlatformSensor; // To call RemoveSensor();
bool CreateSharedBufferIfNeeded(); bool CreateSharedBufferIfNeeded();
void RemoveSensor(mojom::SensorType type); void RemoveSensor(mojom::SensorType type);
void NotifySensorCreated(mojom::SensorType type,
scoped_refptr<PlatformSensor> sensor);
private: private:
using CallbackQueue = std::vector<CreateSensorCallback>; using CallbackQueue = std::vector<CreateSensorCallback>;
......
...@@ -5,9 +5,7 @@ ...@@ -5,9 +5,7 @@
#include "device/generic_sensor/platform_sensor_provider_linux.h" #include "device/generic_sensor/platform_sensor_provider_linux.h"
#include "base/memory/singleton.h" #include "base/memory/singleton.h"
#include "base/task_runner_util.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
#include "device/generic_sensor/linux/platform_sensor_utils_linux.h"
#include "device/generic_sensor/linux/sensor_data_linux.h" #include "device/generic_sensor/linux/sensor_data_linux.h"
#include "device/generic_sensor/platform_sensor_linux.h" #include "device/generic_sensor/platform_sensor_linux.h"
...@@ -20,59 +18,59 @@ PlatformSensorProviderLinux* PlatformSensorProviderLinux::GetInstance() { ...@@ -20,59 +18,59 @@ PlatformSensorProviderLinux* PlatformSensorProviderLinux::GetInstance() {
base::LeakySingletonTraits<PlatformSensorProviderLinux>>::get(); base::LeakySingletonTraits<PlatformSensorProviderLinux>>::get();
} }
PlatformSensorProviderLinux::PlatformSensorProviderLinux() = default; PlatformSensorProviderLinux::PlatformSensorProviderLinux()
: sensor_nodes_enumerated_(false),
sensor_nodes_enumeration_started_(false),
sensor_device_manager_(nullptr) {}
PlatformSensorProviderLinux::~PlatformSensorProviderLinux() = default; PlatformSensorProviderLinux::~PlatformSensorProviderLinux() {
DCHECK(!sensor_device_manager_);
}
void PlatformSensorProviderLinux::CreateSensorInternal( void PlatformSensorProviderLinux::CreateSensorInternal(
mojom::SensorType type, mojom::SensorType type,
mojo::ScopedSharedBufferMapping mapping, mojo::ScopedSharedBufferMapping mapping,
const CreateSensorCallback& callback) { const CreateSensorCallback& callback) {
SensorDataLinux data; if (!sensor_device_manager_)
if (!InitSensorData(type, &data)) { sensor_device_manager_.reset(new SensorDeviceManager());
callback.Run(nullptr);
if (!sensor_nodes_enumerated_) {
if (!sensor_nodes_enumeration_started_) {
sensor_nodes_enumeration_started_ = file_task_runner_->PostTask(
FROM_HERE,
base::Bind(&SensorDeviceManager::Start,
base::Unretained(sensor_device_manager_.get()), this));
}
return; return;
} }
if (!polling_thread_) SensorInfoLinux* sensor_device = GetSensorDevice(type);
polling_thread_.reset(new base::Thread("Sensor polling thread")); if (!sensor_device) {
// If there are no sensors, stop polling thread.
if (!polling_thread_->IsRunning()) { if (!HasSensors())
if (!polling_thread_->StartWithOptions( AllSensorsRemoved();
base::Thread::Options(base::MessageLoop::TYPE_IO, 0))) { callback.Run(nullptr);
callback.Run(nullptr); return;
return;
}
polling_thread_task_runner_ = polling_thread_->task_runner();
} }
SensorDeviceFound(type, std::move(mapping), callback, sensor_device);
base::PostTaskAndReplyWithResult(
polling_thread_task_runner_.get(), FROM_HERE,
base::Bind(SensorReader::Create, data),
base::Bind(&PlatformSensorProviderLinux::SensorReaderFound,
base::Unretained(this), type, base::Passed(&mapping), callback,
data));
} }
void PlatformSensorProviderLinux::SensorReaderFound( void PlatformSensorProviderLinux::SensorDeviceFound(
mojom::SensorType type, mojom::SensorType type,
mojo::ScopedSharedBufferMapping mapping, mojo::ScopedSharedBufferMapping mapping,
const PlatformSensorProviderBase::CreateSensorCallback& callback, const PlatformSensorProviderBase::CreateSensorCallback& callback,
const SensorDataLinux& data, SensorInfoLinux* sensor_device) {
std::unique_ptr<SensorReader> sensor_reader) {
DCHECK(CalledOnValidThread()); DCHECK(CalledOnValidThread());
if (!sensor_reader) { if (!StartPollingThread()) {
// If there are no sensors, stop polling thread.
if (!HasSensors())
AllSensorsRemoved();
callback.Run(nullptr); callback.Run(nullptr);
return; return;
} }
callback.Run(new PlatformSensorLinux(type, std::move(mapping), this, data, scoped_refptr<PlatformSensorLinux> sensor =
std::move(sensor_reader), new PlatformSensorLinux(type, std::move(mapping), this, sensor_device,
polling_thread_task_runner_)); polling_thread_->task_runner());
callback.Run(sensor);
} }
void PlatformSensorProviderLinux::SetFileTaskRunner( void PlatformSensorProviderLinux::SetFileTaskRunner(
...@@ -85,18 +83,128 @@ void PlatformSensorProviderLinux::SetFileTaskRunner( ...@@ -85,18 +83,128 @@ void PlatformSensorProviderLinux::SetFileTaskRunner(
void PlatformSensorProviderLinux::AllSensorsRemoved() { void PlatformSensorProviderLinux::AllSensorsRemoved() {
DCHECK(CalledOnValidThread()); DCHECK(CalledOnValidThread());
DCHECK(file_task_runner_); DCHECK(file_task_runner_);
Shutdown();
// When there are no sensors left, the polling thread must be stopped. // When there are no sensors left, the polling thread must be stopped.
// Stop() can only be called on a different thread that allows io. // Stop() can only be called on a different thread that allows I/O.
// Thus, browser's file thread is used for this purpose. // Thus, browser's file thread is used for this purpose.
file_task_runner_->PostTask( file_task_runner_->PostTask(
FROM_HERE, base::Bind(&PlatformSensorProviderLinux::StopPollingThread, FROM_HERE, base::Bind(&PlatformSensorProviderLinux::StopPollingThread,
base::Unretained(this))); base::Unretained(this)));
} }
bool PlatformSensorProviderLinux::StartPollingThread() {
if (!polling_thread_)
polling_thread_.reset(new base::Thread("Sensor polling thread"));
if (!polling_thread_->IsRunning()) {
return polling_thread_->StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
}
return true;
}
void PlatformSensorProviderLinux::StopPollingThread() { void PlatformSensorProviderLinux::StopPollingThread() {
DCHECK(file_task_runner_); DCHECK(file_task_runner_);
DCHECK(file_task_runner_->BelongsToCurrentThread()); DCHECK(file_task_runner_->BelongsToCurrentThread());
polling_thread_->Stop(); if (polling_thread_ && polling_thread_->IsRunning())
polling_thread_->Stop();
}
void PlatformSensorProviderLinux::Shutdown() {
DCHECK(CalledOnValidThread());
const bool did_post_task = file_task_runner_->DeleteSoon(
FROM_HERE, sensor_device_manager_.release());
DCHECK(did_post_task);
sensor_nodes_enumerated_ = false;
sensor_nodes_enumeration_started_ = false;
sensor_devices_by_type_.clear();
}
SensorInfoLinux* PlatformSensorProviderLinux::GetSensorDevice(
mojom::SensorType type) {
DCHECK(CalledOnValidThread());
auto sensor = sensor_devices_by_type_.find(type);
if (sensor == sensor_devices_by_type_.end())
return nullptr;
return sensor->second.get();
}
void PlatformSensorProviderLinux::GetAllSensorDevices() {
DCHECK(CalledOnValidThread());
// TODO(maksims): implement this method once we have discovery API.
NOTIMPLEMENTED();
}
void PlatformSensorProviderLinux::SetSensorDeviceManagerForTesting(
std::unique_ptr<SensorDeviceManager> sensor_device_manager) {
DCHECK(CalledOnValidThread());
Shutdown();
sensor_device_manager_ = std::move(sensor_device_manager);
}
void PlatformSensorProviderLinux::SetFileTaskRunnerForTesting(
scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
DCHECK(CalledOnValidThread());
file_task_runner_ = std::move(task_runner);
}
void PlatformSensorProviderLinux::ProcessStoredRequests() {
DCHECK(CalledOnValidThread());
std::vector<mojom::SensorType> request_types = GetPendingRequestTypes();
if (request_types.empty())
return;
for (auto const& type : request_types) {
SensorInfoLinux* device = nullptr;
auto device_entry = sensor_devices_by_type_.find(type);
if (device_entry != sensor_devices_by_type_.end())
device = device_entry->second.get();
CreateSensorAndNotify(type, device);
}
}
void PlatformSensorProviderLinux::CreateSensorAndNotify(
mojom::SensorType type,
SensorInfoLinux* sensor_device) {
DCHECK(CalledOnValidThread());
scoped_refptr<PlatformSensorLinux> sensor;
mojo::ScopedSharedBufferMapping mapping = MapSharedBufferForType(type);
if (sensor_device && mapping && StartPollingThread()) {
sensor =
new PlatformSensorLinux(type, std::move(mapping), this, sensor_device,
polling_thread_->task_runner());
}
NotifySensorCreated(type, sensor);
}
void PlatformSensorProviderLinux::OnSensorNodesEnumerated() {
DCHECK(CalledOnValidThread());
DCHECK(!sensor_nodes_enumerated_);
sensor_nodes_enumerated_ = true;
ProcessStoredRequests();
}
void PlatformSensorProviderLinux::OnDeviceAdded(
mojom::SensorType type,
std::unique_ptr<SensorInfoLinux> sensor_device) {
DCHECK(CalledOnValidThread());
// At the moment, we support only one device per type.
if (base::ContainsKey(sensor_devices_by_type_, type)) {
DVLOG(1) << "Sensor ignored. Type " << type
<< ". Node: " << sensor_device->device_node;
return;
}
sensor_devices_by_type_[type] = std::move(sensor_device);
}
void PlatformSensorProviderLinux::OnDeviceRemoved(
mojom::SensorType type,
const std::string& device_node) {
DCHECK(CalledOnValidThread());
auto it = sensor_devices_by_type_.find(type);
if (it == sensor_devices_by_type_.end() &&
it->second->device_node == device_node)
sensor_devices_by_type_.erase(it);
} }
} // namespace device } // namespace device
...@@ -7,23 +7,35 @@ ...@@ -7,23 +7,35 @@
#include "device/generic_sensor/platform_sensor_provider.h" #include "device/generic_sensor/platform_sensor_provider.h"
#include "device/generic_sensor/linux/platform_sensor_manager.h"
namespace base { namespace base {
template <typename T>
struct DefaultSingletonTraits;
class Thread; class Thread;
} }
namespace device { namespace device {
struct SensorDataLinux; struct SensorInfoLinux;
class SensorReader;
class PlatformSensorProviderLinux : public PlatformSensorProvider { class DEVICE_GENERIC_SENSOR_EXPORT PlatformSensorProviderLinux
: public PlatformSensorProvider,
public SensorDeviceManager::Delegate {
public: public:
PlatformSensorProviderLinux();
~PlatformSensorProviderLinux() override;
static PlatformSensorProviderLinux* GetInstance(); static PlatformSensorProviderLinux* GetInstance();
// Sets another service provided by tests.
void SetSensorDeviceManagerForTesting(
std::unique_ptr<SensorDeviceManager> sensor_device_manager);
// Sets task runner for tests.
void SetFileTaskRunnerForTesting(
scoped_refptr<base::SingleThreadTaskRunner> task_runner);
protected: protected:
~PlatformSensorProviderLinux() override;
void CreateSensorInternal(mojom::SensorType type, void CreateSensorInternal(mojom::SensorType type,
mojo::ScopedSharedBufferMapping mapping, mojo::ScopedSharedBufferMapping mapping,
const CreateSensorCallback& callback) override; const CreateSensorCallback& callback) override;
...@@ -34,37 +46,70 @@ class PlatformSensorProviderLinux : public PlatformSensorProvider { ...@@ -34,37 +46,70 @@ class PlatformSensorProviderLinux : public PlatformSensorProvider {
scoped_refptr<base::SingleThreadTaskRunner> file_task_runner) override; scoped_refptr<base::SingleThreadTaskRunner> file_task_runner) override;
private: private:
void SensorReaderFound( friend struct base::DefaultSingletonTraits<PlatformSensorProviderLinux>;
using SensorDeviceMap =
std::unordered_map<mojom::SensorType, std::unique_ptr<SensorInfoLinux>>;
PlatformSensorProviderLinux();
void SensorDeviceFound(
mojom::SensorType type, mojom::SensorType type,
mojo::ScopedSharedBufferMapping mapping, mojo::ScopedSharedBufferMapping mapping,
const PlatformSensorProviderBase::CreateSensorCallback& callback, const PlatformSensorProviderBase::CreateSensorCallback& callback,
const SensorDataLinux& data, SensorInfoLinux* sensor_device);
std::unique_ptr<SensorReader> sensor_reader);
bool StartPollingThread();
// Stops a polling thread if there are no sensors left. Must be called on // Stops a polling thread if there are no sensors left. Must be called on
// a different that polling thread that allows io. // a different than the polling thread which allows I/O.
void StopPollingThread(); void StopPollingThread();
// TODO(maksims): make a separate class Manager that will // Shuts down a service that tracks events from iio subsystem.
// create provide sensors with a polling task runner, check sensors existence void Shutdown();
// and notify provider if a new sensor has appeared and it can be created if a
// request comes again for the same sensor. // Returns SensorInfoLinux structure of a requested type.
// A use case example: a request for a sensor X comes, manager checks if the // If a request cannot be processed immediately, returns nullptr and
// sensor exists on a platform and notifies a provider it is not found. // all the requests stored in |requests_map_| are processed after
// The provider stores this information into its cache and doesn't try to // enumeration is ready.
// create this specific sensor if a request comes. But when, for example, SensorInfoLinux* GetSensorDevice(mojom::SensorType type);
// the sensor X is plugged into a usb port, the manager notices that and
// notifies the provider, which updates its cache and starts handling requests // Returns all found iio devices. Currently not implemented.
// for the sensor X. void GetAllSensorDevices();
//
// Right now, this thread is used to find sensors files and poll data. // Processed stored requests in |request_map_|.
void ProcessStoredRequests();
// Called when sensors are created asynchronously after enumeration is done.
void CreateSensorAndNotify(mojom::SensorType type,
SensorInfoLinux* sensor_device);
// SensorDeviceManager::Delegate implements:
void OnSensorNodesEnumerated() override;
void OnDeviceAdded(mojom::SensorType type,
std::unique_ptr<SensorInfoLinux> sensor_device) override;
void OnDeviceRemoved(mojom::SensorType type,
const std::string& device_node) override;
// Set to true when enumeration is ready.
bool sensor_nodes_enumerated_;
// Set to true when |sensor_device_manager_| has already started enumeration.
bool sensor_nodes_enumeration_started_;
// Stores all available sensor devices by type.
SensorDeviceMap sensor_devices_by_type_;
// A thread that is used by sensor readers in case of polling strategy.
std::unique_ptr<base::Thread> polling_thread_; std::unique_ptr<base::Thread> polling_thread_;
// A task runner that is passed to polling sensors to poll data. // This manager is being used to get |SensorInfoLinux|, which represents
scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner_; // all the information of a concrete sensor provided by OS.
std::unique_ptr<SensorDeviceManager> sensor_device_manager_;
// Browser's file thread task runner passed from renderer. Used to // Browser's file thread task runner passed from renderer. Used by this
// stop a polling thread. // provider to stop a polling thread and passed to a manager that
// runs a linux device monitor service on this task runner.
scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_; scoped_refptr<base::SingleThreadTaskRunner> file_task_runner_;
DISALLOW_COPY_AND_ASSIGN(PlatformSensorProviderLinux); DISALLOW_COPY_AND_ASSIGN(PlatformSensorProviderLinux);
......
// Copyright 2016 The Chromium 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 "device/generic_sensor/platform_sensor_reader_linux.h"
#include "base/files/file_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_restrictions.h"
#include "base/timer/timer.h"
#include "device/generic_sensor/linux/sensor_data_linux.h"
#include "device/generic_sensor/platform_sensor_linux.h"
#include "device/generic_sensor/public/cpp/sensor_reading.h"
namespace device {
class PollingSensorReader : public SensorReader {
public:
PollingSensorReader(
const SensorInfoLinux* sensor_device,
PlatformSensorLinux* sensor,
scoped_refptr<base::SingleThreadTaskRunner> polling_task_runner);
~PollingSensorReader() override;
// SensorReader implements:
bool StartFetchingData(
const PlatformSensorConfiguration& configuration) override;
void StopFetchingData() override;
private:
// Initializes a read timer.
void InitializeTimer(const PlatformSensorConfiguration& configuration);
// Polls data and sends it to a |sensor_|.
void PollForData();
// Paths to sensor read files.
const std::vector<base::FilePath> sensor_file_paths_;
// Scaling value that are applied to raw data from sensors.
const double scaling_value_;
// Offset value.
const double offset_value_;
// Used to apply scalings and invert signs if needed.
const SensorPathsLinux::ReaderFunctor apply_scaling_func_;
// Owned pointer to a timer. Will be deleted on a polling thread once
// destructor is called.
base::RepeatingTimer* timer_;
base::WeakPtrFactory<PollingSensorReader> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(PollingSensorReader);
};
PollingSensorReader::PollingSensorReader(
const SensorInfoLinux* sensor_device,
PlatformSensorLinux* sensor,
scoped_refptr<base::SingleThreadTaskRunner> polling_task_runner)
: SensorReader(sensor, polling_task_runner),
sensor_file_paths_(sensor_device->device_reading_files),
scaling_value_(sensor_device->device_scaling_value),
offset_value_(sensor_device->device_offset_value),
apply_scaling_func_(sensor_device->apply_scaling_func),
timer_(new base::RepeatingTimer()),
weak_factory_(this) {}
PollingSensorReader::~PollingSensorReader() {
polling_task_runner_->DeleteSoon(FROM_HERE, timer_);
}
bool PollingSensorReader::StartFetchingData(
const PlatformSensorConfiguration& configuration) {
if (is_reading_active_)
StopFetchingData();
return polling_task_runner_->PostTask(
FROM_HERE, base::Bind(&PollingSensorReader::InitializeTimer,
weak_factory_.GetWeakPtr(), configuration));
}
void PollingSensorReader::StopFetchingData() {
is_reading_active_ = false;
timer_->Stop();
}
void PollingSensorReader::InitializeTimer(
const PlatformSensorConfiguration& configuration) {
DCHECK(polling_task_runner_->BelongsToCurrentThread());
timer_->Start(FROM_HERE, base::TimeDelta::FromMicroseconds(
base::Time::kMicrosecondsPerSecond /
configuration.frequency()),
this, &PollingSensorReader::PollForData);
is_reading_active_ = true;
}
void PollingSensorReader::PollForData() {
DCHECK(polling_task_runner_->BelongsToCurrentThread());
base::ThreadRestrictions::AssertIOAllowed();
SensorReading readings;
DCHECK_LE(sensor_file_paths_.size(), arraysize(readings.values));
int i = 0;
for (const auto& path : sensor_file_paths_) {
std::string new_read_value;
if (!base::ReadFileToString(path, &new_read_value)) {
NotifyReadError();
StopFetchingData();
return;
}
double new_value = 0;
base::TrimWhitespaceASCII(new_read_value, base::TRIM_ALL, &new_read_value);
if (!base::StringToDouble(new_read_value, &new_value)) {
NotifyReadError();
StopFetchingData();
return;
}
readings.values[i++] = new_value;
}
if (!apply_scaling_func_.is_null())
apply_scaling_func_.Run(scaling_value_, offset_value_, readings);
if (is_reading_active_) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&PlatformSensorLinux::UpdatePlatformSensorReading,
base::Unretained(sensor_), readings));
}
}
// static
std::unique_ptr<SensorReader> SensorReader::Create(
const SensorInfoLinux* sensor_device,
PlatformSensorLinux* sensor,
scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner) {
// TODO(maksims): implement triggered reading. At the moment,
// only polling read is supported.
return base::MakeUnique<PollingSensorReader>(sensor_device, sensor,
polling_thread_task_runner);
}
SensorReader::SensorReader(
PlatformSensorLinux* sensor,
scoped_refptr<base::SingleThreadTaskRunner> polling_task_runner)
: sensor_(sensor),
polling_task_runner_(polling_task_runner),
task_runner_(base::ThreadTaskRunnerHandle::Get()),
is_reading_active_(false) {}
SensorReader::~SensorReader() = default;
void SensorReader::NotifyReadError() {
if (is_reading_active_) {
task_runner_->PostTask(
FROM_HERE, base::Bind(&PlatformSensorLinux::NotifyPlatformSensorError,
base::Unretained(sensor_)));
}
}
} // namespace device
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_READER_LINUX_H_
#define DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_READER_LINUX_H_
#include "base/memory/ref_counted.h"
#include "device/generic_sensor/generic_sensor_export.h"
namespace base {
class SingleThreadTaskRunner;
}
namespace device {
class PlatformSensorLinux;
class PlatformSensorConfiguration;
struct SensorInfoLinux;
// A generic reader class that can be implemented with two different strategies:
// polling and on trigger.
class SensorReader {
public:
// Creates a new instance of SensorReader. At the moment, only polling
// reader is supported.
static std::unique_ptr<SensorReader> Create(
const SensorInfoLinux* sensor_device,
PlatformSensorLinux* sensor,
scoped_refptr<base::SingleThreadTaskRunner> polling_thread_task_runner);
virtual ~SensorReader();
// Starts fetching data based on strategy this reader has chosen.
// Only polling strategy is supported at the moment. Thread safe.
virtual bool StartFetchingData(
const PlatformSensorConfiguration& configuration) = 0;
// Stops fetching data. Thread safe.
virtual void StopFetchingData() = 0;
protected:
SensorReader(PlatformSensorLinux* sensor,
scoped_refptr<base::SingleThreadTaskRunner> polling_task_runner);
// Notifies |sensor_| about an error.
void NotifyReadError();
// Non-owned pointer to a sensor that created this reader.
PlatformSensorLinux* sensor_;
// A task runner that is used to poll data.
scoped_refptr<base::SingleThreadTaskRunner> polling_task_runner_;
// A task runner that belongs to a thread this reader is created on.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Indicates if reading is active.
bool is_reading_active_;
DISALLOW_COPY_AND_ASSIGN(SensorReader);
};
} // namespace device
#endif // DEVICE_GENERIC_SENSOR_PLATFORM_SENSOR_READER_LINUX_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment