blob: 1256a64dc937a4a98684a6748697b572864328fe [file] [log] [blame]
// Copyright 2024 The Fuchsia 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 <fidl/fuchsia.hardware.sensors/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/zx/vmo.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/clock.h>
#include <gtest/gtest.h>
using fuchsia_hardware_sensors::Driver;
using fuchsia_hardware_sensors::Playback;
using fuchsia_math::Vec3F;
using fuchsia_hardware_sensors::ConfigureSensorRateError;
using fuchsia_hardware_sensors::FixedValuesPlaybackConfig;
using fuchsia_hardware_sensors::PlaybackSourceConfig;
using fuchsia_sensors_types::EventPayload;
using fuchsia_sensors_types::SensorEvent;
using fuchsia_sensors_types::SensorId;
using fuchsia_sensors_types::SensorInfo;
using fuchsia_sensors_types::SensorRateConfig;
using fuchsia_sensors_types::SensorReportingMode;
using fuchsia_sensors_types::SensorType;
using fuchsia_sensors_types::SensorWakeUpType;
constexpr SensorId kAccelSensorId = 1;
constexpr int kAccelEventLimit = 100;
constexpr SensorId kGyroSensorId = 2;
constexpr int kGyroEventLimit = 10;
class PlaybackEventHandler : public fidl::AsyncEventHandler<Playback> {
public:
PlaybackEventHandler(async::Loop& loop) : loop_(loop) {}
void handle_unknown_event(fidl::UnknownEventMetadata<Playback> metadata) override {
FX_LOGS(ERROR) << "Saw unknown event.";
saw_error_ = true;
loop_.Quit();
}
void on_fidl_error(fidl::UnbindInfo error) override {
if (expecting_error_) {
FX_LOGS(INFO) << "Saw aync error: " << error;
} else {
FX_LOGS(ERROR) << "Saw async error: " << error;
}
saw_error_ = true;
error_ = error;
loop_.Quit();
}
bool saw_error() { return saw_error_; }
fidl::UnbindInfo error() { return error_; }
void reset_error() {
saw_error_ = false;
error_ = fidl::UnbindInfo();
}
void expecting_error(bool expecting) { expecting_error_ = expecting; }
private:
bool expecting_error_ = false;
bool saw_error_ = false;
fidl::UnbindInfo error_;
async::Loop& loop_;
};
class DriverEventHandler : public fidl::AsyncEventHandler<Driver> {
public:
DriverEventHandler(async::Loop& loop) : loop_(loop) {}
void OnSensorEvent(fidl::Event<Driver::OnSensorEvent>& e) override {
const SensorEvent& event = e.event();
sensor_events_[event.sensor_id()].push_back(event);
event_arrival_timestamps_[event.sensor_id()].push_back(zx::time(zx_clock_get_monotonic()));
if (sensor_events_[event.sensor_id()].size() == 1) {
FX_LOGS(INFO) << "Got first event from sensor " << event.sensor_id();
}
if (sensor_events_[kAccelSensorId].size() >= kAccelEventLimit &&
sensor_events_[kGyroSensorId].size() >= kGyroEventLimit && !saw_final_event_) {
FX_LOGS(INFO) << "Got final event from sensor " << event.sensor_id();
loop_.Quit();
saw_final_event_ = true;
}
}
void handle_unknown_event(fidl::UnknownEventMetadata<Driver> metadata) override {
FX_LOGS(ERROR) << "Saw unknown event.";
saw_error_ = true;
loop_.Quit();
}
void on_fidl_error(fidl::UnbindInfo error) override {
if (expecting_error_) {
FX_LOGS(INFO) << "Saw aync error: " << error;
} else {
FX_LOGS(ERROR) << "Saw async error: " << error;
}
saw_error_ = true;
error_ = error;
loop_.Quit();
}
const std::unordered_map<SensorId, std::vector<SensorEvent>>& sensor_events() {
return sensor_events_;
}
const std::unordered_map<SensorId, std::vector<zx::time>>& event_arrival_timestamps() {
return event_arrival_timestamps_;
}
bool saw_error() { return saw_error_; }
fidl::UnbindInfo error() { return error_; }
void reset_error() {
saw_error_ = false;
error_ = fidl::UnbindInfo();
}
void expecting_error(bool expecting) { expecting_error_ = expecting; }
private:
bool expecting_error_ = false;
bool saw_error_ = false;
fidl::UnbindInfo error_;
bool saw_final_event_ = false;
std::unordered_map<SensorId, std::vector<SensorEvent>> sensor_events_;
std::unordered_map<SensorId, std::vector<zx::time>> event_arrival_timestamps_;
async::Loop& loop_;
};
SensorInfo CreateAccelerometer(SensorId accel_sensor_id) {
SensorInfo accelerometer_info;
accelerometer_info.sensor_id(accel_sensor_id);
accelerometer_info.name("accelerometer");
accelerometer_info.vendor("Accelomax");
accelerometer_info.version(1);
accelerometer_info.sensor_type(SensorType::kAccelerometer);
accelerometer_info.wake_up(SensorWakeUpType::kWakeUp);
accelerometer_info.reporting_mode(SensorReportingMode::kContinuous);
return accelerometer_info;
}
SensorInfo CreateGyroscope(SensorId gyro_sensor_id) {
SensorInfo gyroscope_info;
gyroscope_info.sensor_id(gyro_sensor_id);
gyroscope_info.name("gyroscope");
gyroscope_info.vendor("Gyrocorp");
gyroscope_info.version(1);
gyroscope_info.sensor_type(SensorType::kGyroscope);
gyroscope_info.wake_up(SensorWakeUpType::kWakeUp);
gyroscope_info.reporting_mode(SensorReportingMode::kContinuous);
return gyroscope_info;
}
void CreateAccelerometerEvents(SensorId accel_sensor_id, std::vector<SensorEvent>& event_list) {
for (int i = 0; i < 3; i++) {
Vec3F sample{{
.x = i % 3 == 0 ? 1.0f : 0.0f,
.y = i % 3 == 1 ? 1.0f : 0.0f,
.z = i % 3 == 2 ? 1.0f : 0.0f,
}};
SensorEvent event{{.payload = EventPayload::WithVec3(sample)}};
event.sensor_id() = accel_sensor_id;
event.sensor_type() = SensorType::kAccelerometer;
event_list.push_back(event);
}
}
void CreateGyroscopeEvents(SensorId gyro_sensor_id, std::vector<SensorEvent>& event_list) {
for (int i = 0; i < 3; i++) {
Vec3F sample{{
.x = i % 3 == 1 ? 1.0f : 0.0f,
.y = i % 3 == 2 ? 1.0f : 0.0f,
.z = i % 3 == 0 ? 1.0f : 0.0f,
}};
SensorEvent event{{.payload = EventPayload::WithVec3(sample)}};
event.sensor_id() = gyro_sensor_id;
event.sensor_type() = SensorType::kGyroscope;
event_list.push_back(event);
}
}
PlaybackSourceConfig CreateFixedValuesPlaybackConfig(std::vector<SensorInfo>& sensor_list) {
std::vector<SensorEvent> fixed_event_list;
sensor_list.push_back(CreateAccelerometer(kAccelSensorId));
CreateAccelerometerEvents(kAccelSensorId, fixed_event_list);
sensor_list.push_back(CreateGyroscope(kGyroSensorId));
CreateGyroscopeEvents(kGyroSensorId, fixed_event_list);
FixedValuesPlaybackConfig fixed_config;
fixed_config.sensor_list(sensor_list);
fixed_config.sensor_events(fixed_event_list);
return PlaybackSourceConfig::WithFixedValuesConfig(fixed_config);
}
struct TimestampDiffHistogram {
// Number of bins.
int num_bins;
// Lower limit of lowest bin.
int64_t lowest_bin_lower_limit;
// Upper limit of the highest bin.
int64_t highest_bin_upper_limit;
// Width of each bin.
uint64_t bin_width;
// Total number of points
unsigned long total_points;
// Number of points below all the bins.
int outliers_below;
// List of (bin_index, bin_point_count) pairs.
std::vector<std::pair<int, int>> bin_point_counts;
// Number of points above all the bins.
int outliers_above;
};
// Assumes timestamp_diffs is sorted in ascending order.
TimestampDiffHistogram CreateTimestampDiffHistogram(const std::vector<int64_t>& timestamp_diffs,
int64_t lowest_bin_lower_limit,
uint64_t bin_width, int num_bins) {
TimestampDiffHistogram hist;
hist.num_bins = num_bins;
hist.lowest_bin_lower_limit = lowest_bin_lower_limit;
hist.highest_bin_upper_limit = lowest_bin_lower_limit + (bin_width * num_bins);
hist.bin_width = bin_width;
hist.total_points = timestamp_diffs.size();
hist.outliers_below = 0;
hist.outliers_above = 0;
int current_bin = 0;
int current_bin_point_count = 0;
int64_t current_bin_lower_limit = lowest_bin_lower_limit;
int64_t current_bin_upper_limit = current_bin_lower_limit + bin_width;
for (unsigned long i = 0; i < timestamp_diffs.size(); i++) {
if (timestamp_diffs[i] < lowest_bin_lower_limit) {
hist.outliers_below += 1;
continue;
}
if (timestamp_diffs[i] >= hist.highest_bin_upper_limit) {
hist.outliers_above += 1;
continue;
}
if (timestamp_diffs[i] >= current_bin_lower_limit &&
timestamp_diffs[i] < current_bin_upper_limit) {
current_bin_point_count += 1;
} else if (timestamp_diffs[i] >= current_bin_upper_limit) {
// Store all the points we've counted so far for the bin (if there were any).
if (current_bin_point_count > 0) {
hist.bin_point_counts.push_back(std::make_pair(current_bin, current_bin_point_count));
}
// Move to the next bin and reconsider this data point.
current_bin += 1;
current_bin_point_count = 0;
current_bin_lower_limit += bin_width;
current_bin_upper_limit += bin_width;
i -= 1;
} else {
FX_LOGS(ERROR) << "Saw data value out of order when making histogram.";
}
}
if (current_bin_point_count > 0) {
hist.bin_point_counts.push_back(std::make_pair(current_bin, current_bin_point_count));
}
return hist;
}
// Tests that the playback component only allows one Playback protocol client at a time.
TEST(SensorsPlaybackTest, MultiplePlaybackClientsRejected) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
PlaybackEventHandler handler(loop);
PlaybackEventHandler handler2(loop);
handler2.expecting_error(true);
// Create first connection.
zx::result playback_client_end = component::Connect<Playback>();
ASSERT_TRUE(playback_client_end.is_ok());
fidl::Client playback_client(std::move(*playback_client_end), dispatcher, &handler);
// Run an operation on the connection to make sure this one is actually connected first.
std::vector<SensorInfo> sensor_list;
std::optional<bool> result_ok;
playback_client->ConfigurePlayback(CreateFixedValuesPlaybackConfig(sensor_list))
.ThenExactlyOnce([&](fidl::Result<Playback::ConfigurePlayback>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_FALSE(handler.saw_error());
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
// Create the second connection.
zx::result playback_client_end2 = component::Connect<Playback>();
ASSERT_TRUE(playback_client_end2.is_ok());
fidl::Client playback_client2(std::move(*playback_client_end2), dispatcher, &handler2);
// Let the looper run until we get the asynchronous Playback disconnect error.
loop.Run();
loop.ResetQuit();
EXPECT_FALSE(handler.saw_error());
ASSERT_TRUE(handler2.saw_error());
}
// Tests that the driver component only allows one Driver protocol client at a time.
TEST(SensorsDriverTest, MultipleDriverClientsRejected) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
PlaybackEventHandler playback_handler(loop);
DriverEventHandler handler(loop);
DriverEventHandler handler2(loop);
handler2.expecting_error(true);
// Connect to the Playback protocol and set a valid config just to keep the flow nominal up till
// the second Driver connection.
zx::result playback_client_end = component::Connect<Playback>();
ASSERT_TRUE(playback_client_end.is_ok());
fidl::Client playback_client(std::move(*playback_client_end), dispatcher, &playback_handler);
std::vector<SensorInfo> sensor_list;
std::optional<bool> result_ok;
playback_client->ConfigurePlayback(CreateFixedValuesPlaybackConfig(sensor_list))
.ThenExactlyOnce([&](fidl::Result<Playback::ConfigurePlayback>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_FALSE(handler.saw_error());
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
// Create the first connection.
zx::result driver_client_end = component::Connect<Driver>();
ASSERT_TRUE(driver_client_end.is_ok());
fidl::Client driver_client(std::move(*driver_client_end), dispatcher, &handler);
// Run an operation on the connection to make sure this one is actually connected first.
result_ok = std::nullopt;
handler.reset_error();
driver_client->GetSensorsList().ThenExactlyOnce(
[&](fidl::Result<Driver::GetSensorsList>& result) {
result_ok = result.is_ok();
if (!result.is_ok()) {
FX_LOGS(INFO) << "Saw error: " << result.error_value();
}
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Create the second connection.
zx::result driver_client_end2 = component::Connect<Driver>();
ASSERT_TRUE(driver_client_end2.is_ok());
fidl::Client driver_client2(std::move(*driver_client_end2), dispatcher, &handler2);
// Let the looper run until we get the asynchronous Driver disconnect error.
loop.Run();
loop.ResetQuit();
EXPECT_FALSE(handler.saw_error());
ASSERT_TRUE(handler2.saw_error());
}
// Tests that the playback component disconnects the Driver client if the Playback client
// disconnects.
TEST(SensorsPlaybackTest, DriverDisconnectsIfPlaybackDisconnects) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
DriverEventHandler handler(loop);
// Connect to the Playback protocol and set a valid playback configuration.
zx::result playback_client_end = component::Connect<Playback>();
ASSERT_TRUE(playback_client_end.is_ok());
std::optional<fidl::Client<Playback>> playback_client =
fidl::Client<Playback>(std::move(*playback_client_end), dispatcher);
std::optional<bool> result_ok;
std::vector<SensorInfo> sensor_list;
(*playback_client)
->ConfigurePlayback(CreateFixedValuesPlaybackConfig(sensor_list))
.ThenExactlyOnce([&](fidl::Result<Playback::ConfigurePlayback>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_FALSE(handler.saw_error());
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
zx::result driver_client_end = component::Connect<Driver>();
ASSERT_TRUE(driver_client_end.is_ok());
fidl::Client driver_client(std::move(*driver_client_end), dispatcher, &handler);
result_ok = std::nullopt;
handler.reset_error();
driver_client->GetSensorsList().ThenExactlyOnce(
[&](fidl::Result<Driver::GetSensorsList>& result) {
result_ok = result.is_ok();
if (!result.is_ok()) {
FX_LOGS(INFO) << "Saw error: " << result.error_value();
}
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Let the Playback connection go out of scope. This will cause the playback configuration in
// the playback component to be cleared and the Driver connection to be closed remotely.
// Set the handler to expect an asynchronous error (the remote Driver connection closure).
handler.expecting_error(true);
playback_client = std::nullopt;
// Let the looper run until we get the asynchronous Driver disconnect error.
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(handler.saw_error());
}
// Tests the following features of the playback component:
// - Play back a bunch of fixed valued sensor events provided by the client.
// - Generate "ideal" timestamps based on the configured sampling period.
// This does not test sensor data buffering (max reporting latency is set to 0).
TEST(SensorsPlaybackTest, FixedValues_Unbuffered_GeneratedTimestamps) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
DriverEventHandler handler(loop);
zx::result playback_client_end = component::Connect<Playback>();
ASSERT_TRUE(playback_client_end.is_ok());
zx::result driver_client_end = component::Connect<Driver>();
ASSERT_TRUE(driver_client_end.is_ok());
fidl::Client playback_client(std::move(*playback_client_end), dispatcher);
fidl::Client driver_client(std::move(*driver_client_end), dispatcher, &handler);
// Set fixed playback mode with the generated sensor list and set of events.
std::vector<SensorInfo> sensor_list;
std::optional<bool> result_ok;
playback_client->ConfigurePlayback(CreateFixedValuesPlaybackConfig(sensor_list))
.ThenExactlyOnce([&](fidl::Result<Playback::ConfigurePlayback>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Check that we get back the sensor list we just configured.
result_ok = std::nullopt;
driver_client->GetSensorsList().ThenExactlyOnce(
[&](fidl::Result<Driver::GetSensorsList>& result) {
result_ok = sensor_list == result->sensor_list();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Accelerometer rate.
SensorRateConfig accel_rate_config;
accel_rate_config.sampling_period_ns(1e7); // One hundredth of a second.
accel_rate_config.max_reporting_latency_ns(0); // No buffering.
result_ok = std::nullopt;
driver_client->ConfigureSensorRate({kAccelSensorId, accel_rate_config})
.ThenExactlyOnce([&](fidl::Result<Driver::ConfigureSensorRate>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Gyroscope rate.
SensorRateConfig gyro_rate_config;
gyro_rate_config.sampling_period_ns(1e8); // One tenth of a second.
gyro_rate_config.max_reporting_latency_ns(0); // No buffering.
result_ok = std::nullopt;
driver_client->ConfigureSensorRate({kGyroSensorId, gyro_rate_config})
.ThenExactlyOnce([&](fidl::Result<Driver::ConfigureSensorRate>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Activate the accelerometer.
std::optional<bool> accel_result_ok;
driver_client->ActivateSensor({kAccelSensorId})
.ThenExactlyOnce(
[&](fidl::Result<Driver::ActivateSensor>& result) { accel_result_ok = result.is_ok(); });
// Activate the gyroscope.
std::optional<bool> gyro_result_ok;
driver_client->ActivateSensor({kGyroSensorId})
.ThenExactlyOnce(
[&](fidl::Result<Driver::ActivateSensor>& result) { gyro_result_ok = result.is_ok(); });
// Set deadline of 10 seconds and run.
FX_LOGS(INFO) << "Waiting for sensor events.";
zx_status_t run_result = loop.Run(zx::deadline_after(zx::duration(static_cast<long>(10 * 1e9))));
loop.ResetQuit();
if (run_result == ZX_ERR_TIMED_OUT) {
FX_LOGS(ERROR) << "Timed out waiting for sensor events.";
}
ASSERT_TRUE(accel_result_ok.has_value());
ASSERT_TRUE(*accel_result_ok);
ASSERT_TRUE(gyro_result_ok.has_value());
ASSERT_TRUE(*gyro_result_ok);
ASSERT_EQ(run_result, ZX_ERR_CANCELED);
ASSERT_FALSE(handler.saw_error());
ASSERT_GE(handler.sensor_events().at(kAccelSensorId).size(), 100ul);
ASSERT_GE(handler.sensor_events().at(kGyroSensorId).size(), 10ul);
// Deactivate the accelerometer.
accel_result_ok = std::nullopt;
driver_client->DeactivateSensor({kAccelSensorId})
.ThenExactlyOnce([&](fidl::Result<Driver::DeactivateSensor>& result) {
accel_result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(accel_result_ok.has_value());
ASSERT_TRUE(*accel_result_ok);
ASSERT_FALSE(handler.saw_error());
// Deactivate the gyroscope.
gyro_result_ok = std::nullopt;
driver_client->DeactivateSensor({kGyroSensorId})
.ThenExactlyOnce([&](fidl::Result<Driver::DeactivateSensor>& result) {
gyro_result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(gyro_result_ok.has_value());
ASSERT_TRUE(*gyro_result_ok);
ASSERT_FALSE(handler.saw_error());
// Do some statistics on the timestamps to see if they make sense.
const std::vector<zx::time>& accel_arrival_times =
handler.event_arrival_timestamps().at(kAccelSensorId);
// Calculate the differences between all the timestamps, then sort the differences.
std::vector<int64_t> accel_arrival_diffs;
for (unsigned long i = 1; i < accel_arrival_times.size(); ++i) {
zx::duration arrival_diff = accel_arrival_times.at(i) - accel_arrival_times.at(i - 1);
accel_arrival_diffs.push_back(arrival_diff.get());
}
std::sort(accel_arrival_diffs.begin(), accel_arrival_diffs.end());
// Create a histogram of the timestamp differences. The bins will be 1e7 nanoseconds (0.01
// seconds) wide. The bottom end of the lowest bin will start at -5e6 nanoseconds (or -0.005
// seconds) and thus the lowest bin will be centered around 0. There will be 11 bins in total,
// with the highest bin centered on 1e8 nanoseconds (0.1 seconds) and topping out at 1.05e8
// nanoseconds (0.105 seconds).
const int64_t kBottomBinLowerLimit = -5e6;
const uint64_t kBinWidth = 1e7;
const int64_t kNumBins = 11;
TimestampDiffHistogram hist =
CreateTimestampDiffHistogram(accel_arrival_diffs, kBottomBinLowerLimit, kBinWidth, kNumBins);
/* The accelerometer is sampling at 100 Hz with no buffering.
The expected histogram with perfect timing would be this:
Bin | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
-------*----*----*----*----*----*----*----*----*----*----*----*
Points | 00 | 99 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 |
Since buffering is not enabled, the events should be coming evenly spaced by the sampling
period, which is 1e7 nanoseconds (0.01 seconds). Thus we would expect all 99 data points to
fall into bin 1.
Rather than assert that all of the timestamp differences fit exactly into their ideal bins,
this test will merely assert that bin 1 contains the most points, followed by any outliers
above. This will ensure the general shape of the timing is correct but reduce flakes by being
incredibly robust to all but the most massive and repeated test environment performance hiccups
hiccups (theoretically anyways).
*/
// Outliers below the bottom bin should not be possible as all of the timestamp differences should
// be positive.
ASSERT_EQ(hist.outliers_below, 0);
// Sort the per-bin point counts in descending order by count.
std::sort(hist.bin_point_counts.begin(), hist.bin_point_counts.end(),
[](const std::pair<int, int>& bin1, const std::pair<int, int>& bin2) {
return bin1.second >= bin2.second;
});
// There should be data points in at least one bin.
ASSERT_GE(hist.bin_point_counts.size(), 1u);
// Assert the bin with the highest count is bin 1.
ASSERT_EQ(hist.bin_point_counts[0].first, 1);
// Outliers above the top bin could conceivably exist but it would be exceptionally unlikely for
// there to be more of them than data points in bin 1.
ASSERT_LT(hist.outliers_above, hist.bin_point_counts[0].second);
}
// Tests the following features of the playback component:
// - Play back a bunch of fixed valued sensor events provided by the client.
// - Generate "ideal" timestamps based on the configured sampling period.
// - Simulate hardware buffering of sensor data (use a maximum reporting
// latency greater than 0).
TEST(SensorsPlaybackTest, FixedValues_Buffered_GeneratedTimestamps) {
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
async_dispatcher_t* dispatcher = loop.dispatcher();
DriverEventHandler handler(loop);
zx::result playback_client_end = component::Connect<Playback>();
ASSERT_TRUE(playback_client_end.is_ok());
zx::result driver_client_end = component::Connect<Driver>();
ASSERT_TRUE(driver_client_end.is_ok());
fidl::Client playback_client(std::move(*playback_client_end), dispatcher);
fidl::Client driver_client(std::move(*driver_client_end), dispatcher, &handler);
// Set fixed playback mode with the generated sensor list and set of events.
std::vector<SensorInfo> sensor_list;
std::optional<bool> result_ok;
playback_client->ConfigurePlayback(CreateFixedValuesPlaybackConfig(sensor_list))
.ThenExactlyOnce([&](fidl::Result<Playback::ConfigurePlayback>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Check that we get back the sensor list we just configured.
result_ok = std::nullopt;
driver_client->GetSensorsList().ThenExactlyOnce(
[&](fidl::Result<Driver::GetSensorsList>& result) {
result_ok = sensor_list == result->sensor_list();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Accelerometer rate.
// Set a non-zero maximum reporting latency (one tenth of a second). This will
// cause the playback component to simulate hardware FIFO buffering.
SensorRateConfig accel_rate_config;
accel_rate_config.sampling_period_ns(1e7); // One hundredth of a second.
accel_rate_config.max_reporting_latency_ns(1e8); // One tenth of a second.
result_ok = std::nullopt;
driver_client->ConfigureSensorRate({kAccelSensorId, accel_rate_config})
.ThenExactlyOnce([&](fidl::Result<Driver::ConfigureSensorRate>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Gyroscope rate.
SensorRateConfig gyro_rate_config;
gyro_rate_config.sampling_period_ns(1e8); // One tenth of a second.
gyro_rate_config.max_reporting_latency_ns(0); // No buffering.
result_ok = std::nullopt;
driver_client->ConfigureSensorRate({kGyroSensorId, gyro_rate_config})
.ThenExactlyOnce([&](fidl::Result<Driver::ConfigureSensorRate>& result) {
result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(result_ok.has_value());
ASSERT_TRUE(*result_ok);
ASSERT_FALSE(handler.saw_error());
// Activate the accelerometer.
std::optional<bool> accel_result_ok;
driver_client->ActivateSensor({kAccelSensorId})
.ThenExactlyOnce(
[&](fidl::Result<Driver::ActivateSensor>& result) { accel_result_ok = result.is_ok(); });
// Activate the gyroscope.
std::optional<bool> gyro_result_ok;
driver_client->ActivateSensor({kGyroSensorId})
.ThenExactlyOnce(
[&](fidl::Result<Driver::ActivateSensor>& result) { gyro_result_ok = result.is_ok(); });
// Set deadline of 10 seconds and run.
FX_LOGS(INFO) << "Waiting for sensor events.";
zx_status_t run_result = loop.Run(zx::deadline_after(zx::duration(static_cast<long>(10 * 1e9))));
loop.ResetQuit();
if (run_result == ZX_ERR_TIMED_OUT) {
FX_LOGS(ERROR) << "Timed out waiting for sensor events.";
}
ASSERT_TRUE(accel_result_ok.has_value());
ASSERT_TRUE(*accel_result_ok);
ASSERT_TRUE(gyro_result_ok.has_value());
ASSERT_TRUE(*gyro_result_ok);
ASSERT_EQ(run_result, ZX_ERR_CANCELED);
ASSERT_FALSE(handler.saw_error());
ASSERT_GE(handler.sensor_events().at(kAccelSensorId).size(), 100ul);
ASSERT_GE(handler.sensor_events().at(kGyroSensorId).size(), 10ul);
// Deactivate the accelerometer.
accel_result_ok = std::nullopt;
driver_client->DeactivateSensor({kAccelSensorId})
.ThenExactlyOnce([&](fidl::Result<Driver::DeactivateSensor>& result) {
accel_result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(accel_result_ok.has_value());
ASSERT_TRUE(*accel_result_ok);
ASSERT_FALSE(handler.saw_error());
// Deactivate the gyroscope.
gyro_result_ok = std::nullopt;
driver_client->DeactivateSensor({kGyroSensorId})
.ThenExactlyOnce([&](fidl::Result<Driver::DeactivateSensor>& result) {
gyro_result_ok = result.is_ok();
loop.Quit();
});
loop.Run();
loop.ResetQuit();
ASSERT_TRUE(gyro_result_ok.has_value());
ASSERT_TRUE(*gyro_result_ok);
ASSERT_FALSE(handler.saw_error());
// Do some statistics on the timestamps to see if they make sense.
const std::vector<zx::time>& accel_arrival_times =
handler.event_arrival_timestamps().at(kAccelSensorId);
// Calculate the differences between all the timestamps, then sort the differences.
std::vector<int64_t> accel_arrival_diffs;
for (unsigned long i = 1; i < accel_arrival_times.size(); ++i) {
zx::duration arrival_diff = accel_arrival_times.at(i) - accel_arrival_times.at(i - 1);
accel_arrival_diffs.push_back(arrival_diff.get());
}
std::sort(accel_arrival_diffs.begin(), accel_arrival_diffs.end());
// Create a histogram of the timestamp differences. The bins will be 1e7 nanoseconds (0.01
// seconds) wide. The bottom end of the lowest bin will start at -5e6 nanoseconds (or -0.005
// seconds) and thus the lowest bin will be centered around 0. There will be 11 bins in total,
// with the highest bin centered on 1e8 nanoseconds (0.1 seconds) and topping out at 1.05e8
// nanoseconds (0.105 seconds).
const int64_t kBottomBinLowerLimit = -5e6;
const uint64_t kBinWidth = 1e7;
const int64_t kNumBins = 11;
TimestampDiffHistogram hist =
CreateTimestampDiffHistogram(accel_arrival_diffs, kBottomBinLowerLimit, kBinWidth, kNumBins);
/* The accelerometer is sampling at 100 Hz, but with a buffer that is flushed at 10 Hz.
The expected histogram with perfect timing would be this:
Bin | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 |
-------*----*----*----*----*----*----*----*----*----*----*----*
Points | 90 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 00 | 09 |
Since buffering is enabled, the events are coming in 10 bursts of 10. The timestamp difference
between events in a burst will be very small, and thus we expect bin 0 to contain the most data
points (90 ideally). In the 9 gaps between the 10 bursts we expect to see timestamp differences
close to 0.1 seconds, and so ideally there should be 9 data points in bin 10.
Rather than assert that all of the timestamp differences fit exactly into their ideal bins,
this test will merely assert that bin 0 contains the most points, followed by bin 10, followed
by any other bin or outliers above. This will ensure the general shape of the timing is correct
but reduce flakes by being incredibly robust to all but the most massive and repeated test
environment performance hiccups (theoretically anyways).
*/
// Outliers below the bottom bin should not be possible as all of the timestamp differences should
// be positive.
ASSERT_EQ(hist.outliers_below, 0);
// Sort the per-bin point counts in descending order by count.
std::sort(hist.bin_point_counts.begin(), hist.bin_point_counts.end(),
[](const std::pair<int, int>& bin1, const std::pair<int, int>& bin2) {
return bin1.second >= bin2.second;
});
// There should be data points in at least two bins.
ASSERT_GE(hist.bin_point_counts.size(), 2u);
// Assert the bin with the highest count is bin 0.
ASSERT_EQ(hist.bin_point_counts[0].first, 0);
// Assert the bin with the next highest couint is bin 10.
ASSERT_EQ(hist.bin_point_counts[1].first, 10);
}