diff options
author | Shreshta Manu <shreshtabm@google.com> | 2024-05-14 21:54:07 +0000 |
---|---|---|
committer | Shreshta Manu <shreshtabm@google.com> | 2024-05-14 22:06:09 +0000 |
commit | 6ab71d40b93ba02abbea47a5752e50bd7df7fc8e (patch) | |
tree | b51d05c9a54585338e66c3816df219e79bbc6984 | |
parent | dabf6196834edc862a526eb156c2d63c5c24ce5f (diff) | |
download | Uwb-6ab71d40b93ba02abbea47a5752e50bd7df7fc8e.tar.gz |
[generic-ranging] Add multi sensor interface to generic ranging
Arcore apk portion is commented out.
Bug: 331206299
Test: Compiles
Change-Id: I47c0ed89a93e2321a923e8554e39b06e8600122f
15 files changed, 1492 insertions, 80 deletions
diff --git a/generic_ranging/Android.bp b/generic_ranging/Android.bp index 7d129147..454cb50b 100644 --- a/generic_ranging/Android.bp +++ b/generic_ranging/Android.bp @@ -23,16 +23,7 @@ java_library { min_sdk_version: "34", installable: false, srcs: [ - //"src/**/*.java", - "src/**/RangingTechnology.java", - "src/**/RangingData.java", - "src/**/RangingAdapter.java", - "src/**/FusionData.java", - "src/**/PrecisionData.java", - "src/**/PrecisionRanging.java", - "src/**/PrecisionRangingConfig.java", - "src/**/UwbAdapter.java", - "src/**/PrecisionRangingImpl.java", + "src/**/*.java", ], plugins: [ "auto_value_plugin", @@ -48,6 +39,7 @@ java_library { "dagger2", "framework-annotations-lib", "guava", + "multi-sensor-finder-configuration-java-proto", "uwb_androidx_backend", ], visibility: [ diff --git a/generic_ranging/proto/Android.bp b/generic_ranging/proto/Android.bp new file mode 100644 index 00000000..4c08354a --- /dev/null +++ b/generic_ranging/proto/Android.bp @@ -0,0 +1,36 @@ +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_team: "trendy_team_fwk_uwb", + default_applicable_licenses: ["Android-Apache-2.0"], +} + +filegroup { + name: "multi-sensor-proto", + srcs: [ + "src/*.proto", + ], +} + +java_library { + name: "multi-sensor-finder-configuration-java-proto", + proto: { + type: "lite", + }, + sdk_version: "system_current", + min_sdk_version: "34", + srcs: [ + "src/estimate.proto", + "src/debug_log.proto", + "src/multi_sensor_finder_configuration.proto", + ], + apex_available: [ + "com.android.uwb", + ], +} diff --git a/generic_ranging/proto/src/debug_log.proto b/generic_ranging/proto/src/debug_log.proto new file mode 100644 index 00000000..6946eff1 --- /dev/null +++ b/generic_ranging/proto/src/debug_log.proto @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package com.android.ranging.generic.proto; + +import "packages/modules/Uwb/generic_ranging/proto/src/estimate.proto"; + +option java_package = "com.android.ranging.generic.proto"; +option java_multiple_files = true; + +message Event { + enum Type { + NONE = 0; + START = 1; + STOP = 2; + } + + Type type = 1; + float timestamp_sec = 2; +} + +message UwbDatum { + float range_m = 1; + int32 rssi_dbm = 2; + float timestamp_sec = 3; +} + +message OdometryDatum { + float x_m = 1; + float y_m = 2; + float z_m = 3; + float timestamp_sec = 8; +} + +message InputContainer { + oneof input { + UwbDatum uwb_datum = 1; + OdometryDatum odometry_datum = 3; + } +} + +message LeanEstimate { + com.android.ranging.generic.proto.Estimate.Status status = 1; + float range_m = 2; + float bearing_rad = 3; + float estimated_beacon_position_error_std_dev_m = 4; + float timestamp_sec = 5; +} + +message DebugLog { + repeated InputContainer inputs = 1; + repeated LeanEstimate outputs = 2; + repeated Event events = 3; +}
\ No newline at end of file diff --git a/generic_ranging/proto/src/estimate.proto b/generic_ranging/proto/src/estimate.proto new file mode 100644 index 00000000..8d86f294 --- /dev/null +++ b/generic_ranging/proto/src/estimate.proto @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package com.android.ranging.generic.proto; + +option java_package = "com.android.ranging.generic.proto"; +option java_multiple_files = true; + + // Next ID: 14 + message Estimate { + enum Status { + UNSPECIFIED = 0; + // An estimate was successfully computed. + OK = 1; + // Could not produce an estimate. For example, no synchronized set of data + // is available. + ESTIMATE_NOT_AVAILABLE = 2; + // The filter has diverged and is attempting to recover. + RECOVERING = 3; + // Odometry failed and cannot recover. + ODOMETRY_ERROR = 4; + // The beacon is probably moving, and so cannot be tracked. + BEACON_MOVING_ERROR = 5; + // The configuration file contains an error and Finder can't be started. + CONFIGURATION_ERROR = 6; + // Permissions not granted to required sensors. + SENSOR_PERMISSION_DENIED = 7; + UNKNOWN_ERROR = 8; + // Tracking failed due to insufficient light. This can occur when using + // camera based odometry. The filter will automatically recover and produce + // an estimate when possible. + RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT = 9; + // Tracking failed due to excessive motion. The filter will automatically + // recover and produce an estimate when possible. + RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION = 10; + // Tracking failed due to insufficient features in the camera images. This + // can occur when using camera based odometry. The filter will automatically + // recover and produce an estimate when possible. + RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES = 11; + // Tracking failed because something else is using the camera. Tracking will + // recover automatically, but with a new origin. + RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY = 12; + // Tracking failed due to a bad odometry state. The filter will + // automatically recover and produce an estimate when possible. + RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE = 13; + } + Status status = 1; + double range_m = 2; + double range_error_std_dev_m = 3; + // The bearing is with respect to the device Y-axis, positive ccw. + double bearing_rad = 4; + // This measure usually increases as you move closer to the beacon. + double bearing_error_std_dev_rad = 5; + // This measure does not vary with the distance to the beacon. + double estimated_beacon_position_error_std_dev_m = 7; + int64 timestamp_nanos = 6; + } diff --git a/generic_ranging/proto/src/multi_sensor_finder_configuration.proto b/generic_ranging/proto/src/multi_sensor_finder_configuration.proto new file mode 100644 index 00000000..985bcead --- /dev/null +++ b/generic_ranging/proto/src/multi_sensor_finder_configuration.proto @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto3"; + +package com.android.ranging.generic.proto; + +option java_package = "com.android.ranging.generic.proto"; +option java_multiple_files = true; + +enum ConfidenceLevel { +CL_UNSPECIFIED = 0; +CL_95 = 1; // 95 % +CL_97P5 = 2; // 97.5 % +CL_99 = 3; // 99 % +} + +// Configuration for MultiSensorFinder including the configuration of sub +// components. +message MultiSensorFinderConfig { +// If unset, default values will be used. +ParticleFilterConfig particle_filter_config = 1; + +// If enabled, the filter will use UWB measurements. +bool use_uwb_measurements = 2; + +// If enabled, the filter will use Wi-Fi RTT measurements. +bool use_wifi_rtt_measurements = 3; + +// If unset, default values will be used. +RangeMeasurementConfig uwb_range_measurement_config = 4; + +// If unset, default values will be used. +RangeMeasurementConfig wifi_rtt_range_measurement_config = 5; + +// If unset, default values will be used. +FuzzyUpdateSchedulerConfig fuzzy_update_scheduler_configuration = 6; + +double default_xy_update_process_noise_stddev_m = 7; + +// If unset, default values will be used. +OdometryNoiseAdderConfig odometry_noise_adder_config = 8; + +// If unset, trilateration measurement updater will not be used. +TrilaterationMeasurementUpdaterConfig +trilateration_measurement_updater_config = 9; + +// The rate at which finder will poll the odometry provider, which is also +// the maximum rate at which finder will generate estimates. +uint32 odometry_polling_rate_hz = 10; + +// If unset, odometry throttler will not be used. +OdometryThrottlerConfig odometry_throttler_config = 11; + +// If unset, OBEP will not be used. +OdometryBasedEstimatePropagatorConfig +odometry_based_estimate_propagator_config = 12; + +// If unset, the NIS divergence detector will not be used. +NisDivergenceDetectorConfig nis_divergence_detector_config = 13; + +InitialStateSamplerConfig uwb_initial_state_sampler_config = 14; + +InitialStateSamplerConfig wifi_rtt_initial_state_sampler_config = 15; + +// Used to input and output logs for development and debugging. If unset, +// the debug logger will not be used. +DebugLoggerConfiguration debug_logger_configuration = 16; +} + +// Configuration for the generic sensor model, which is simply a combination of +// a Gaussian and a Uniform distribution. +message GenericRangeSensorModelConfig { +double gaussian_std_dev_m = 1; +double max_sensor_range_m = 2; +} + +// Configuration for the trilateration measurement updater, which uses +// trilateration on a set of UWB range measurements to determine if the incoming +// range measurements are consistent over some spatial region. +message TrilaterationMeasurementUpdaterConfig { +int32 num_measurements_for_trilateration = 2; +double max_trilateration_rmse_m = 3; +} + +// Configuration for the sensor model associated with a range measurement and +// any additional checks/modifiers. Currently, only the GenericRangeSensorModel +// is supported. +message RangeMeasurementConfig { +// Currently supported models for a range sensor. If unspecified, the generic +// model will be used. +enum RangeSensorModelType { +UNSPECIFIED = 0; +// A generic model that consists of a Gaussian + Uniform distribution. +// The configuration message associated with this model is +// GenericRangeSensorModelConfig. +GENERIC = 1; +// Switches the measurement model based on the variance heuristic. +VARIANCE_BASED_SWITCHING = 2; +} +RangeSensorModelType sensor_model_type = 1; +GenericRangeSensorModelConfig generic_range_sensor_model_configuration = 2; +VarianceBasedSwitchingMeasurementModelConfig +variance_based_switching_measurement_model_config = 4; +// If unset, the distance traveled check will not be used. +DistanceTraveledCheckConfig distance_traveled_check_config = 3; +} + +// Configuration for a scheduler which determines when a motion + measurement +// update should be done. +// +// For each odometry sample, it creates a frame (or window) of +- +// max_frame_size_nanos, and waits max_wait_time_nanos for all data to show up +// in this frame. +// +// max_wait_time_nanos is directly proportional to the filter latency. +// Increasing max_frame_size_nanos will increase the number of measurements +// used, but can decrease accuracy because of a greater tolerance on the +// mismatch in timestamps between odometry and range sensor data. +message FuzzyUpdateSchedulerConfig { +uint64 max_wait_time_nanos = 1; +uint64 max_frame_size_nanos = 2; +int32 max_buffer_size = 3; +} + +// The distance traveled check blocks measurements from being continually +// absorbed if a user stands still. It's a simple way to prevent particle +// depletion. +message DistanceTraveledCheckConfig { +double distance_traveled_threshold_m = 2; +} + +message ParticleFilterConfig { +int32 number_of_particles = 1; +} + +// The odometry noise adder adds noise during a motion update based on the speed +// of the user. +// +// The mapping from speed to noise stays flat at min_noise_std_dev until the +// speed reaches min_speed_mps, after which it ramps linearly up to +// max_speed_mps. For speed greater than max_speed_mps, it stays at +// max_noise_std_dev. +// +// +// max_speed_mps +// / +// ^ * +// | ------------ max_noise_std_dev +// sigma m | / +// | / +// | ------/ min_noise_std_dev +// ----------------------------------> Speed m/s` +// * +// / +// min_speed_mps +message OdometryNoiseAdderConfig { +int32 num_speed_filter_taps = 2; +double min_noise_std_dev_m = 3; +double max_noise_std_dev_m = 4; +double min_speed_mps = 5; +double max_speed_mps = 6; +} + +message OdometryThrottlerConfig { +int64 throttling_dt_nanos = 1; +} + +message OdometryBasedEstimatePropagatorConfig { +// The size of the odometry buffer. Set this based on the odometry polling +// rate. +int32 buffer_size = 1; +} + +// Uses the Normalized Innovation Squared criteria to determine if the filter +// has diverged. The Filter will reset if divergence is detected. +message NisDivergenceDetectorConfig { +// A larger buffer reduces sensitivity to noisy measurements. +int32 nis_buffer_size = 1; + +// A higher level yields fewer false positives. +ConfidenceLevel confidence_level = 2; + +// Caps the NIS score to reduce sensitivity to outliers. For example, a value +// of 1 sigma will cap the NIS score to the 68.3 % Gaussian interval about the +// mean. +double nis_sigma_bound = 3; + +// The detector will only be active if the std dev of the filter's error in +// the estimated beacon position is below this threshold. +double activation_threshold_m = 4; + +// The default noise covariance used when computing the NIS for UWB if an +// online computed value is not provided to the detector. +double default_uwb_noise_covariance = 5; + +// The default noise covariance used when computing the NIS for Wi-Fi RTT if +// an online computed value is not provided to the detector. +double default_wifi_rtt_noise_covariance = 6; +} + +message UniformModelConfig { +double min_value = 1; +double max_value = 2; +} + +message GaussianModelConfig { +double loc = 1; +double scale = 2; +} + +message ExponentiallyWeightedGaussianModelConfig { +double lambda_scaled = 1; +double loc = 2; +double scale = 3; +} + +message ModelConfigContainer { +oneof model { +UniformModelConfig uniform_model_config = 1; +GaussianModelConfig gaussian_model_config = 2; +ExponentiallyWeightedGaussianModelConfig +exponentially_weighted_gaussian_model_config = 3; +} +} + +// Configuration for the variance based switching model. +message VarianceBasedSwitchingMeasurementModelConfig { +double switching_threshold = 1; + +ModelConfigContainer low_variance_model_config = 2; + +// If unset, measurement updates will not be done while the sensor range +// variance is above the variance_threshold. +ModelConfigContainer high_variance_model_config = 3; + +// The size of the window over which the variance used by this model is +// computed. +int32 variance_window_size = 4; +} + +// Configuration for an initial state sampler. +// +// Please note that the mean of the distribution specified in the sampler +// configs are about the measured value; if a Gaussian sampler is used with loc +// = 0.1 and scale = 10, then the samples will be generated using: +// +// measured_range + Gaussian(loc, scale). +// +// Additionally, note that the exponential distribution will be flipped such +// that it is skewed towardes -ve axis rather than the standard which is skewed +// towardes the +ve axis. +message InitialStateSamplerConfig { +ModelConfigContainer range_sampler_config = 1; +ModelConfigContainer bearing_sampler_config = 2; +} + +message DebugLoggerConfiguration { +// Debug logs will be automatically emitted to the sink when they exceed this +// size. +int32 autodump_size_threshold_bytes = 1; +// The rate at which odometry inputs will be throttled. Throttling is disabled +// if set to 0. +int64 odometry_throttling_nanos = 2; +// The rate at which UWB range measurements will be throttled. Throttling is +// disabled if set to 0. +int64 uwb_throttling_nanos = 3; +// The rate at which output estimates will be throttled. Throttling is +// disabled if set to 0. +int64 estimate_throttling_nanos = 4; +} diff --git a/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java b/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java index a6475210..7b1416b9 100644 --- a/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java +++ b/generic_ranging/src/com/android/ranging/generic/ranging/DefaultFusionConfig.java @@ -16,21 +16,21 @@ package com.android.ranging.generic.ranging; -import location.bluemoon.finder.ConfidenceLevel; -import location.bluemoon.finder.DistanceTraveledCheckConfig; -import location.bluemoon.finder.ExponentiallyWeightedGaussianModelConfig; -import location.bluemoon.finder.FuzzyUpdateSchedulerConfig; -import location.bluemoon.finder.InitialStateSamplerConfig; -import location.bluemoon.finder.ModelConfigContainer; -import location.bluemoon.finder.MultiSensorFinderConfig; -import location.bluemoon.finder.NisDivergenceDetectorConfig; -import location.bluemoon.finder.OdometryBasedEstimatePropagatorConfig; -import location.bluemoon.finder.OdometryNoiseAdderConfig; -import location.bluemoon.finder.OdometryThrottlerConfig; -import location.bluemoon.finder.ParticleFilterConfig; -import location.bluemoon.finder.RangeMeasurementConfig; -import location.bluemoon.finder.RangeMeasurementConfig.RangeSensorModelType; -import location.bluemoon.finder.VarianceBasedSwitchingMeasurementModelConfig; +import com.android.ranging.generic.proto.ConfidenceLevel; +import com.android.ranging.generic.proto.DistanceTraveledCheckConfig; +import com.android.ranging.generic.proto.ExponentiallyWeightedGaussianModelConfig; +import com.android.ranging.generic.proto.FuzzyUpdateSchedulerConfig; +import com.android.ranging.generic.proto.InitialStateSamplerConfig; +import com.android.ranging.generic.proto.ModelConfigContainer; +import com.android.ranging.generic.proto.MultiSensorFinderConfig; +import com.android.ranging.generic.proto.NisDivergenceDetectorConfig; +import com.android.ranging.generic.proto.OdometryBasedEstimatePropagatorConfig; +import com.android.ranging.generic.proto.OdometryNoiseAdderConfig; +import com.android.ranging.generic.proto.OdometryThrottlerConfig; +import com.android.ranging.generic.proto.ParticleFilterConfig; +import com.android.ranging.generic.proto.RangeMeasurementConfig; +import com.android.ranging.generic.proto.RangeMeasurementConfig.RangeSensorModelType; +import com.android.ranging.generic.proto.VarianceBasedSwitchingMeasurementModelConfig; /** Default configuration for the Fusion algorithm. */ public final class DefaultFusionConfig { diff --git a/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java b/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java index f81a2afa..af13fb2a 100644 --- a/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java +++ b/generic_ranging/src/com/android/ranging/generic/ranging/FusionData.java @@ -16,9 +16,10 @@ package com.android.ranging.generic.ranging; +import com.android.sensor.Estimate; +import com.android.sensor.Status; + import com.google.auto.value.AutoValue; -//import com.google.hardware.techeng.sensors.finder.Estimate; -//import com.google.hardware.techeng.sensors.finder.Status; /** * Fusion data represents a fusion of data received from ranging technologies and data received from @@ -63,40 +64,40 @@ public abstract class FusionData { public abstract FusionData build(); } -// public static FusionData fromFusionAlgorithmEstimate(Estimate estimate) { -// return FusionData.builder() -// .setFusionRange(estimate.getRangeM()) -// .setFusionRangeErrorStdDev(estimate.getRangeErrorStdDevM()) -// .setFusionBearing(estimate.getBearingRad()) -// .setFusionBearingErrorStdDev(estimate.getBearingErrorStdDevRad()) -// .setArCoreState(convertToArCoreStateFromStatus(estimate.getStatus())) -// .build(); -// } - -// private static ArCoreState convertToArCoreStateFromStatus(Status status) { -// switch (status) { -// case OK: -// return ArCoreState.OK; -// case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT: -// return ArCoreState.POOR_LIGHTNING; -// case RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION: -// return ArCoreState.EXCESSIVE_MOTION; -// case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES: -// return ArCoreState.INSUFFICIENT_FEATURES; -// case RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY: -// return ArCoreState.CAMERA_UNAVAILABLE; -// case ESTIMATE_NOT_AVAILABLE: -// case RECOVERING: -// case RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE: -// case ODOMETRY_ERROR: -// case BEACON_MOVING_ERROR: -// case CONFIGURATION_ERROR: -// case SENSOR_PERMISSION_DENIED_ERROR: -// case UNKNOWN_ERROR: -// return ArCoreState.BAD_STATE; -// } -// return ArCoreState.BAD_STATE; -// } + public static FusionData fromFusionAlgorithmEstimate(Estimate estimate) { + return FusionData.builder() + .setFusionRange(estimate.getRangeM()) + .setFusionRangeErrorStdDev(estimate.getRangeErrorStdDevM()) + .setFusionBearing(estimate.getBearingRad()) + .setFusionBearingErrorStdDev(estimate.getBearingErrorStdDevRad()) + .setArCoreState(convertToArCoreStateFromStatus(estimate.getStatus())) + .build(); + } + + private static ArCoreState convertToArCoreStateFromStatus(Status status) { + switch (status) { + case OK: + return ArCoreState.OK; + case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT: + return ArCoreState.POOR_LIGHTNING; + case RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION: + return ArCoreState.EXCESSIVE_MOTION; + case RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES: + return ArCoreState.INSUFFICIENT_FEATURES; + case RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY: + return ArCoreState.CAMERA_UNAVAILABLE; + case ESTIMATE_NOT_AVAILABLE: + case RECOVERING: + case RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE: + case ODOMETRY_ERROR: + case BEACON_MOVING_ERROR: + case CONFIGURATION_ERROR: + case SENSOR_PERMISSION_DENIED_ERROR: + case UNKNOWN_ERROR: + return ArCoreState.BAD_STATE; + } + return ArCoreState.BAD_STATE; + } /** State of ArCore */ public enum ArCoreState { diff --git a/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java b/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java index 6d9fe1f3..12dd5660 100644 --- a/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java +++ b/generic_ranging/src/com/android/ranging/generic/ranging/PrecisionRangingConfig.java @@ -17,14 +17,14 @@ package com.android.ranging.generic.ranging; import com.android.ranging.generic.RangingTechnology; +import com.android.ranging.generic.proto.MultiSensorFinderConfig; import com.google.auto.value.AutoValue; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.time.Duration; -//import java.util.Optional; -//import location.bluemoon.finder.MultiSensorFinderConfig; +import java.util.Optional; /** Configuration for Precision Ranging. */ @AutoValue @@ -67,7 +67,7 @@ public abstract class PrecisionRangingConfig { public abstract Duration getNoUpdateTimeout(); /** Returns the fusion algorithm configuration if present. */ - //public abstract Optional<MultiSensorFinderConfig> getFusionAlgorithmConfig(); + public abstract Optional<MultiSensorFinderConfig> getFusionAlgorithmConfig(); /** Returns a builder for {@link PrecisionRangingConfig}. */ public static Builder builder() { @@ -90,8 +90,8 @@ public abstract class PrecisionRangingConfig { public abstract Builder setInitTimeout(Duration duration); - //public abstract Builder setFusionAlgorithmConfig(MultiSensorFinderConfig - // fusionAlgorithmConfig); + public abstract Builder setFusionAlgorithmConfig(MultiSensorFinderConfig + fusionAlgorithmConfig); abstract PrecisionRangingConfig autoBuild(); @@ -101,20 +101,18 @@ public abstract class PrecisionRangingConfig { !config.getRangingTechnologiesToRangeWith().isEmpty(), "Ranging technologies to range with must contain at least one ranging " + "technology."); -// Preconditions.checkArgument( -// config.getUseFusingAlgorithm() == config.getFusionAlgorithmConfig() -// .isPresent(), -// "Fusion algorithm config must be set when and only when useFusingAlgorithm -// is set to" -// + " true."); -// if (config.getUseFusingAlgorithm() -// && config.getRangingTechnologiesToRangeWith().contains(RangingTechnology -// .UWB)) { -// Preconditions.checkArgument( -// config.getFusionAlgorithmConfig().get().getUseUwbMeasurements(), -// "Fusion algorithm should accept UWB measurements since UWB was -// requested."); -// } + Preconditions.checkArgument( + config.getUseFusingAlgorithm() == config.getFusionAlgorithmConfig() + .isPresent(), + "Fusion algorithm config must be set when and only when useFusingAlgorithm" + + "is set to"); + if (config.getUseFusingAlgorithm() + && config.getRangingTechnologiesToRangeWith().contains(RangingTechnology + .UWB)) { + Preconditions.checkArgument( + config.getFusionAlgorithmConfig().get().getUseUwbMeasurements(), + "Fusion algorithm should accept UWB measurements since UWB was requested."); + } return config; } } diff --git a/generic_ranging/src/com/android/sensor/AndroidMultiSensorFinderInterface.java b/generic_ranging/src/com/android/sensor/AndroidMultiSensorFinderInterface.java new file mode 100644 index 00000000..5f729109 --- /dev/null +++ b/generic_ranging/src/com/android/sensor/AndroidMultiSensorFinderInterface.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sensor; + +import android.app.Activity; +import android.app.Application.ActivityLifecycleCallbacks; +import android.content.Context; + +import java.util.concurrent.FutureTask; + +/** + * Precision Finder fuses range measurements from UWB, Wifi-RTT, and BLE HADM etc. with odometry to + * provide a range and bearing to the beacon. + * + * <p>Sample Usage: + * + * <p><code> + * // --- To create finder --- + * MultiSensorFinder finder = new MultiSensorFinder(config, args, ...); + * finder.SubscribeToEstimates("name_of_user", listener); + * + * // --- Checking availability of finder --- + * // IMPORTANT: Before calling start, please make that it is possible to run a particular finder + * // implementation on the device, and that the finder implementation has all prerequisites met: + * if (!finder.checkAvailability(context)) { + * return; // Can't use finder here. + * } + * + * switch (finder.requestInstall(activity)) { + * case USER_PROMPTED_TO_INSTALL_DEPENDENCIES: + * // The activity was switched and the user was requested to install dependencies. + * return; + * case USER_DECLINED_TO_INSTALL_DEPENDENCIES: + * // User declined to install dependencies. + * return; + * case DEVICE_INCOMPATIBLE: + * break; + * case OK: + * // Everything is good to go! + * break; + * } + * + * // --- Starting finder --- // + * finder.Start(); + * + * // --- Providing data to finder --- // + * finder.updateWithUwbMeasurement(rangeMeters, timestampNanos); + * finder.updateWithWifiRTTMeasurement(rangeMeters, errorStdDevMeters, rssiDbm, timestampNanos); + * ... + * + * // --- Destroying finder --- // + * finder.stop(); + * finder.delete(); + * </code> + * + * <p>Please note that finder should be stopped when the activity in which it is running is switched + * to the background, so that all sensor streams that finder is using are stopped. This can be done + * in two ways: + * + * <ul> + * <li>Manually call finder.stop() in your activity's onPause method. + * <li>Call getApplication().registerActivityLifecycleCallbacks(finder) in your activity's + * onCreate, and finder will automatically call stop when the application goes to bg. + * </ul> + * + * Additionally, finder.delete() should be called when the application is destroyed. Again, this can + * be manually done in your applications onDestroy, or you can register finder to the activity's + * ActivityLifecycleCallbacks to do this for you automatically. + */ +public interface AndroidMultiSensorFinderInterface extends ActivityLifecycleCallbacks { + + /** + * Checks if the device meets the requirements for running precision finder e.g. it has all the + * sensors etc. + */ + FutureTask<Boolean> checkAvailability(Context context); + + /** + * Checks if all dependencies have been installed, and if not, switch the activity and prompt + * the + * user to install them. + */ + InstallStatus requestInstall(Activity activity); + + /** + * Resets underlying variables, and starts odometry. Once started, MultiSensorFinder will accept + * measurements. + * + * <p>If MultiSensorFinder is already started, this method is a no-op. + * + * @return Status.OK if successful, and Status.ERROR_* otherwise. + */ + Status start(Context context); + + /** + * Stops producing estimates. Once stopped, MultiSensorFinder cannot accept measurements. + * + * <p>If already stopped, this method is a no-op. + * + * <p>@return Status.OK if successful, and Status.ERROR_* otherwise. + */ + Status stop(); + + /** + * Adds a UWB measurement, which will be fused with other data to produce an estimate. + * + * <p>If MultiSensorFinder is stopped, this method is a no-op. + * + * <p>Note: The Android stack does not provide low level information on the received UWB + * signal, + * e.g. the number of peaks in the impulse response, or how much multipath is in the + * environment. + * It's not clear if the Fira API exposes this information, but it might be useful. + * + * @param rangeMeters The range measurement from UWB. + * @param timestampNanos The timestamp in nanoseconds associated with the measurement. + */ + void updateWithUwbMeasurement(double rangeMeters, long timestampNanos); + + /** + * Adds a Wifi-RTT measurement, which will be fused with other data to produce an estimate. + * + * <p>If MultiSensorFinder is stopped, this method is a no-op. + * + * @param rangeMeters The range measurement from Wifi-RTT. + * @param errorStdDevMeters The error bounds on the range measurement. + * @param rssiDbm Beacon to finder signal strength. + * @param timestampNanos The timestamp in nanoseconds associated with the measurement. + */ + void updateWithWifiRttMeasurement( + double rangeMeters, double errorStdDevMeters, double rssiDbm, long timestampNanos); + + /** + * Adds a subscriber that will be notified when a new Estimate is available. Note that Finder + * will + * not generate any estimates if it is stopped. + * + * @param listener The subscriber that will be registered. + */ + void subscribeToEstimates(MultiSensorFinderListener listener); + + /** + * Frees all native resources. This should be called when the application is destroyed, unless + * this class is registered to ActivityLifecycleCallbacks of the application using it, in which + * case it will be called automatically on the application's onDestroy. + */ + void delete(); +} diff --git a/generic_ranging/src/com/android/sensor/ArCoreMultiSensorFinder.java b/generic_ranging/src/com/android/sensor/ArCoreMultiSensorFinder.java new file mode 100644 index 00000000..db434775 --- /dev/null +++ b/generic_ranging/src/com/android/sensor/ArCoreMultiSensorFinder.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sensor; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; + +import androidx.annotation.GuardedBy; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import com.android.ranging.generic.proto.MultiSensorFinderConfig; + +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; + +/** + * A MultiSensorFinder implementation that uses ARCore as the source of odometry. + * + * <p>The ArCoreMultiSensorFinder can be in two states: stopped and started. + * + * <p>Finder can transition between the started and stopped state via the start() and stop() + * methods. Any unrecoverable error or calling the delete method will transition finder to the + * stopped state. Every time finder enters the stopped state it wipes the state and the algorithm + * starts from scratch on a subsequent start. If finder encounters a recoverable error, it will + * return a RECOVERING_FROM_* status, in which case finder will not enter the stopped state and will + * continually provide feedback to the client on how to help it recover (e.g. the room is too dark). + * Once finder recovers, it will start finding the beacon from scratch. + * + * <p>All algo work and listener processing (except ARCore odometry which has its own thread) + * happens in a single thread, which can be user provided. + */ +public class ArCoreMultiSensorFinder implements AndroidMultiSensorFinderInterface { + + private static final String TAG = ArCoreMultiSensorFinder.class.getSimpleName(); + private static final int DEFAULT_ODOMETRY_POLLING_RATE_HZ = 10; + + private static final long INVALID_POINTER_ADDRESS = 0; + + /** + * ARCore's checkAvailability method can return UNKNOWN_CHECKING, meaning that it is still + * waiting + * on network to figure out if ARCore is available for a particular device. In that case, we + * will + * wait 200 milliseconds before trying again. + */ + private static final long CHECK_AVAILABILITY_RETRY_DELAY_DURATION = 200; + + /** + * How long we will wait for ARCore's checkAvailability to return a result before we give up + * . + */ + private static final Duration CHECK_AVAILABILITY_RETRY_TIMEOUT_DURATION = Duration.ofSeconds(2); + + /** + * The thread in which all work (except ARCore work) is done if the user did not provide a + * handler. + */ + @Nullable + private HandlerThread mHandlerThread; + + /** + * Handler to the thread in which all work (except ARCore work) is done. This can be provided by + * the user during construction. + */ + private Handler mProcessingThreadHandler; + + /** + * A variable for controlling ARCore's requestInstall behaviour: On first call, ask the user to + * install preqrequisites if required, and do not ask again on subsequent calls. + */ + private boolean userRequestedInstall = false; + + private final MultiSensorFinderConfig mConfig; + + // Used to indicate whether this class should run its own thread for processing, or use a + // handler + // that was provided by the client. + private final boolean processInOwnThread; + + /** + * Although all processing is done in a single thread, the user can still call stop/delete + * methods + * from any threads they wish. This mutex is used to guard against that. + */ + private final Object mutex = new Object(); + + private final long odometryPollingDelayMs; + + private final SessionWithArCoreNative sessionWithArCoreNative; + + // Tracks whether the odometry polling runnable is running or not. This is used to guard against + // the case where a stop and start are called in rapid succession, and the previous odometry + // polling runnable did not have time to stop. + @GuardedBy("mutex") + private boolean odometryPollingRunnableIsActive = false; + + /** + * To avoid generating garbage, native methods will populate their results in this member + * variable. + */ + @GuardedBy("mutex") + private Estimate latestEstimate = new Estimate(); + + /** Check whether finder has been started. */ + @GuardedBy("mutex") + private boolean started = false; + + @GuardedBy("mutex") + private long nativeSessionPointer = INVALID_POINTER_ADDRESS; + + @GuardedBy("mutex") + private final List<MultiSensorFinderListener> listeners = new ArrayList<>(); + + // A runnable that polls odometry and feeds it to the algorithm to generate an Estimate. This + // runnable is executed repeatedly at odometryPollingDelayMs. + private final Runnable pollAndProcessOdometryInLooperRunnable = + this::pollAndProcessOdometryInLooper; + + // This executor is used to execute the checkAvailability method, which may take up to + // CHECK_AVAILABILITY_RETRY_TIMEOUT_DURATION to execute. + private final ExecutorService executorService; + + /** + * Constructs ArCoreMultiSensorFinder with a user provided handler to which all algorithm and + * listener processing tasks will be submitted. + */ + public ArCoreMultiSensorFinder( + MultiSensorFinderConfig config, + Handler processingThreadHandler, + SessionWithArCoreNative sessionWithArCoreNative) { + this.mConfig = config; + this.executorService = Executors.newSingleThreadExecutor(); + this.sessionWithArCoreNative = sessionWithArCoreNative; + + if (processingThreadHandler == null) { + processInOwnThread = true; + } else { + processInOwnThread = false; + this.mHandlerThread = null; + this.mProcessingThreadHandler = processingThreadHandler; + } + + if (config.getOdometryPollingRateHz() > 0.0) { + this.odometryPollingDelayMs = (long) (1000.0 / config.getOdometryPollingRateHz()); + } else { + this.odometryPollingDelayMs = (long) (1000.0 / DEFAULT_ODOMETRY_POLLING_RATE_HZ); + } + } + + /** + * Constructs ArCoreMultiSensorFinder which will create and use its own thread and associated + * handler to which all algorithm and listener processing tasks will be submitted. + */ + public ArCoreMultiSensorFinder(MultiSensorFinderConfig config) { + this(config, null, new SessionWithArCoreNative()); + } + + // For testing purposes only. A constructor for passing in a mocked SessionWithArCoreNative. + @VisibleForTesting + public ArCoreMultiSensorFinder( + MultiSensorFinderConfig config, + SessionWithArCoreNative sessionWithArCoreNative) { + this(config, null, sessionWithArCoreNative); + } + + @Override + public FutureTask<Boolean> checkAvailability(Context context) { + FutureTask<Boolean> future = + new FutureTask<>( + () -> { + Instant startTime = Instant.now(); + Instant deadline = startTime.plus( + CHECK_AVAILABILITY_RETRY_TIMEOUT_DURATION); + +// try { +// while (Instant.now().isBefore(deadline)) { +// ArCoreApk.Availability availability = +// ArCoreApk.getInstance().checkAvailability(context); +// if (availability.isTransient()) { +// // Wait until we get a response. +// TimeUnit.MILLISECONDS.sleep +// (CHECK_AVAILABILITY_RETRY_DELAY_DURATION); +// } else { +// return availability.isSupported(); +// } +// } +// } catch (InterruptedException e) { +// return false; +// } + + Log.w(TAG, "Deadline expired while checking for availability"); + return false; + }); + + executorService.execute(future); + return future; + } + + @Override + public InstallStatus requestInstall(Activity activity) { +// try { +// ArCoreApk.InstallStatus installStatus = +// ArCoreApk.getInstance().requestInstall(activity, !userRequestedInstall); +// switch (installStatus) { +// case INSTALLED: +// return InstallStatus.OK; +// case INSTALL_REQUESTED: +// userRequestedInstall = true; +// return InstallStatus.USER_PROMPTED_TO_INSTALL_DEPENDENCIES; +// } +// return InstallStatus.UNKNOWN_ERROR; +// } catch (UnavailableUserDeclinedInstallationException e) { +// return InstallStatus.USER_DECLINED_TO_INSTALL_DEPENDENCIES; +// } catch (UnavailableDeviceNotCompatibleException e) { +// return InstallStatus.DEVICE_INCOMPATIBLE; +// } catch (FatalException e) { +// return InstallStatus.UNKNOWN_ERROR; +// } + return InstallStatus.UNKNOWN_ERROR; + } + + @Override + public Status start(Context context) { + synchronized (mutex) { + if (started) { + return Status.OK; + } + + // Create and start the processing thread if it has not already been started. + if (processInOwnThread && this.mHandlerThread == null) { + this.mHandlerThread = new HandlerThread("MultiSensorFinder"); + this.mHandlerThread.start(); + this.mProcessingThreadHandler = new Handler(this.mHandlerThread.getLooper()); + } + + // Create the session if it hasn't already been created. + if (nativeSessionPointer == INVALID_POINTER_ADDRESS) { + nativeSessionPointer = sessionWithArCoreNative.createSession(mConfig.toByteArray()); + } + + if (nativeSessionPointer == INVALID_POINTER_ADDRESS) { + Log.w(TAG, "Could not create session."); + return Status.UNKNOWN_ERROR; + } + + // Start the session. + Status status = sessionWithArCoreNative.start(nativeSessionPointer, context); + if (status != Status.OK) { + Log.w(TAG, "Could not start session: " + status); + return status; + } + started = true; + + // Start odometry polling loop if it is not already running. + if (!odometryPollingRunnableIsActive) { + mProcessingThreadHandler.post(pollAndProcessOdometryInLooperRunnable); + odometryPollingRunnableIsActive = true; + } + } + + return Status.OK; + } + + @Override + public void updateWithUwbMeasurement(double rangeMeters, long timestampNanos) { + mProcessingThreadHandler.post( + () -> updateWithUwbMeasurementInLooper(rangeMeters, timestampNanos)); + } + + @Override + public void updateWithWifiRttMeasurement( + double rangeMeters, double errorStdDevMeters, double rssiDbm, long timestampNanos) { + mProcessingThreadHandler.post( + () -> + updateWithWifiRttMeasurementInLooper( + rangeMeters, errorStdDevMeters, rssiDbm, timestampNanos)); + } + + @Override + public void subscribeToEstimates(MultiSensorFinderListener listener) { + synchronized (mutex) { + listeners.add(listener); + } + } + + @Override + public Status stop() { + synchronized (mutex) { + if (!started) { + return Status.OK; + } + + Status status = sessionWithArCoreNative.stop(nativeSessionPointer); + if (status != Status.OK) { + Log.w(TAG, "Could not stop session: " + status); + } + started = false; + return status; + } + } + + @Override + public void delete() { + synchronized (mutex) { + if (nativeSessionPointer != INVALID_POINTER_ADDRESS) { + Status status = stop(); + if (status != Status.OK) { + Log.w(TAG, "Could not stop session: " + status); + } + sessionWithArCoreNative.deleteSession(nativeSessionPointer); + nativeSessionPointer = INVALID_POINTER_ADDRESS; + } + + if (mHandlerThread != null) { + mHandlerThread.quit(); + mHandlerThread = null; + } + } + } + + @GuardedBy("mutex") + private void publishEstimate(Estimate estimate) { + for (MultiSensorFinderListener listener : listeners) { + listener.onUpdatedEstimate(estimate); + } + } + + // A method that polls odometry at regular intervals and uses to it generate Estimates. This + // is intended to be called with other processing functions in one looper to avoid issues with + // multi-threading. + private void pollAndProcessOdometryInLooper() { + synchronized (mutex) { + if (!started) { + odometryPollingRunnableIsActive = false; + return; + } + + // Attempt to update the estimate with new odometry data. + sessionWithArCoreNative.pollAndProcessOdometryUpdate(nativeSessionPointer); + sessionWithArCoreNative.getEstimate(nativeSessionPointer, latestEstimate); + + // Publish estimate to all listeners. + if (latestEstimate.getStatus() != Status.ESTIMATE_NOT_AVAILABLE) { + publishEstimate(latestEstimate); + } + + checkForErrors(latestEstimate); + } + + mProcessingThreadHandler.postDelayed( + pollAndProcessOdometryInLooperRunnable, odometryPollingDelayMs); + } + + private void updateWithUwbMeasurementInLooper(double rangeMeters, long timestampNanos) { + synchronized (mutex) { + if (!started) { + return; + } + sessionWithArCoreNative.updateWithUwbMeasurement( + nativeSessionPointer, rangeMeters, timestampNanos); + + sessionWithArCoreNative.getEstimate(nativeSessionPointer, latestEstimate); + if (latestEstimate.getStatus() != Status.ESTIMATE_NOT_AVAILABLE) { + publishEstimate(latestEstimate); + } + + checkForErrors(latestEstimate); + } + } + + private void updateWithWifiRttMeasurementInLooper( + double rangeMeters, double errorStdDevMeters, double rssiDbm, long timestampNanos) { + synchronized (mutex) { + if (!started) { + return; + } + sessionWithArCoreNative.updateWithWifiRttMeasurement( + nativeSessionPointer, rangeMeters, errorStdDevMeters, rssiDbm, timestampNanos); + + sessionWithArCoreNative.getEstimate(nativeSessionPointer, latestEstimate); + if (latestEstimate.getStatus() != Status.ESTIMATE_NOT_AVAILABLE) { + publishEstimate(latestEstimate); + } + + checkForErrors(latestEstimate); + } + } + + /** + * Checks if the latest estimate indicated an error, and transitions finder to stopped state if + * so. + */ + @GuardedBy("mutex") + private void checkForErrors(Estimate estimate) { + // If we got anything but OK, ESTIMATE_NOT_AVAILABLE, or RECOVERING_*, that means there + // was an + // error, and we should quit. + if (estimate.getStatus() == Status.OK + || estimate.getStatus() == Status.ESTIMATE_NOT_AVAILABLE + || estimate.getStatus() == Status.RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT + || estimate.getStatus() == Status.RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION + || estimate.getStatus() + == Status.RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES + || estimate.getStatus() + == Status.RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY + || estimate.getStatus() + == Status.RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE) { + return; + } + Status unused = stop(); + } + + @Override + public void onActivityCreated(Activity activity, Bundle bundle) { + } + + @Override + public void onActivityStarted(Activity activity) { + } + + @Override + public void onActivityResumed(Activity activity) { + } + + public void onActivityResumeFragments(Activity activity) { + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { + } + + @Override + public void onActivityStopped(Activity activity) { + } + + @Override + public void onActivityPaused(Activity activity) { + Status unused = stop(); + } + + @Override + public void onActivityDestroyed(Activity activity) { + delete(); + } + + // For test purposes only. The estimate object is updated on the native side, which is mocked + // out + // for tests. + @VisibleForTesting + public void setEstimate(Estimate estimate) { + synchronized (mutex) { + latestEstimate = estimate; + } + } + + @VisibleForTesting + public boolean isStarted() { + synchronized (mutex) { + return started; + } + } +} diff --git a/generic_ranging/src/com/android/sensor/Estimate.java b/generic_ranging/src/com/android/sensor/Estimate.java new file mode 100644 index 00000000..f1ed841e --- /dev/null +++ b/generic_ranging/src/com/android/sensor/Estimate.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sensor; + +/** + * Estimate of range and bearing returned by Finder. This is in 1:1 correspondence with + * location.bluemoon.Estimate proto. This class is usually populated from the native side. + */ +public class Estimate { + + private Status status; + + private double rangeM; + + private double rangeErrorStdDevM; + + // The bearing is with respect to the device Y-axis, positive ccw. + private double bearingRad; + + private double bearingErrorStdDevRad; + + private double estimatedBeaconPositionErrorStdDevM; + + private long timestampNanos; + + /** Create an "empty" estimate. */ + public Estimate() { + status = Status.UNKNOWN_ERROR; + rangeM = 0.0; + rangeErrorStdDevM = 0.0; + bearingRad = 0.0; + bearingErrorStdDevRad = 0.0; + estimatedBeaconPositionErrorStdDevM = 0.0; + timestampNanos = 0; + } + + public void setStatus(Status status) { + this.status = status; + } + + public void setRangeM(double rangeM) { + this.rangeM = rangeM; + } + + public void setRangeErrorStdDevM(double rangeErrorStdDevM) { + this.rangeErrorStdDevM = rangeErrorStdDevM; + } + + public void setBearingRad(double bearingRad) { + this.bearingRad = bearingRad; + } + + public void setBearingErrorStdDevRad(double bearingErrorStdDevRad) { + this.bearingErrorStdDevRad = bearingErrorStdDevRad; + } + + public void setEstimatedBeaconPositionErrorStdDevM(double estimatedBeaconPositionErrorStdDevM) { + this.estimatedBeaconPositionErrorStdDevM = estimatedBeaconPositionErrorStdDevM; + } + + public void setTimestampNanos(long timestampNanos) { + this.timestampNanos = timestampNanos; + } + + public Status getStatus() { + return status; + } + + public double getRangeM() { + return rangeM; + } + + public double getRangeErrorStdDevM() { + return rangeErrorStdDevM; + } + + /** The bearing is with respect to the device Y-axis, positive ccw. */ + public double getBearingRad() { + return bearingRad; + } + + public double getBearingErrorStdDevRad() { + return bearingErrorStdDevRad; + } + + public double getEstimatedBeaconPositionErrorStdDevM() { + return estimatedBeaconPositionErrorStdDevM; + } + + public long getTimestampNanos() { + return timestampNanos; + } +}
\ No newline at end of file diff --git a/generic_ranging/src/com/android/sensor/InstallStatus.java b/generic_ranging/src/com/android/sensor/InstallStatus.java new file mode 100644 index 00000000..a80e7801 --- /dev/null +++ b/generic_ranging/src/com/android/sensor/InstallStatus.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sensor; + +/** The status returned by MultiSensorFinder when requestInstall was called. */ +public enum InstallStatus { + /** Everything is installed and start can be called. */ + OK, + /** + * The activity was switched, and the user was prompted to install dependencies. requestInstall + * must be called again before starting the session. + */ + USER_PROMPTED_TO_INSTALL_DEPENDENCIES, + /** + * The user was asked to install dependencies, and the user rejected the request. The user will + * not be asked again until the app is restarted. + */ + USER_DECLINED_TO_INSTALL_DEPENDENCIES, + /** ARCore cannot be installed on this device. */ + DEVICE_INCOMPATIBLE, + UNKNOWN_ERROR +} diff --git a/generic_ranging/src/com/android/sensor/MultiSensorFinderListener.java b/generic_ranging/src/com/android/sensor/MultiSensorFinderListener.java new file mode 100644 index 00000000..505eb51d --- /dev/null +++ b/generic_ranging/src/com/android/sensor/MultiSensorFinderListener.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sensor; + +/** Listener interface to receive Estimates computed by MultiSensorFinderInterface classes. */ +public interface MultiSensorFinderListener { + void onUpdatedEstimate(Estimate estimate); +} diff --git a/generic_ranging/src/com/android/sensor/SessionWithArCoreNative.java b/generic_ranging/src/com/android/sensor/SessionWithArCoreNative.java new file mode 100644 index 00000000..a6db4cd2 --- /dev/null +++ b/generic_ranging/src/com/android/sensor/SessionWithArCoreNative.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sensor; + +import android.content.Context; + +/** This class contains the Jave methods corresponding to Finder native APIs. */ +public class SessionWithArCoreNative { + /** + * The native library is loaded in the constructor so that the load can be mocked out in tests. + */ + public SessionWithArCoreNative() { + System.loadLibrary("precisionfindingsessionwitharcorejni"); + } + + /** + * Creates the session. The memory allocated by this method must be freed by calling + * deleteSession. + * + * @return A non-zero value if the session was successfully created, zero otherwise. + */ + public native long createSession(byte[] config); + + /** + * Starts consuming sensor data streams. Once started, the session can be provided with range + * measurements to produce estimates. + * + * <p>IMPORTANT: Before calling start, the user must ensure that ARCore is available, and all + * prerequisites have been installed. This is done by using the checkAvailability and + * requestInstall methods. + * https://developers.google.com/ar/reference/java/com/google/ar/core/ArCoreApk + */ + public native Status start(long sessionPointer, Context context); + + /** + * Stops consuming all sensor data streams. Calling start after stop will start the session from + * scratch. + */ + public native Status stop(long sessionPointer); + + /** + * Polls and uses the latest odometry from ARCore. The result of this call can be obtained by + * calling getEstimate. + */ + public native void pollAndProcessOdometryUpdate(long sessionPointer); + + /** + * Forwards a UWB measurement to the underlying estimator. The result of this call can be + * obtained + * by calling getEstimate. + */ + public native void updateWithUwbMeasurement( + long sessionPointer, double range, long timestampNanos); + + /** + * Forwards a Wifi-RTT measurement to the underlying estimator. The result of this call can be + * obtained by calling getEstimate. + */ + public native void updateWithWifiRttMeasurement( + long sessionPointer, double rangeM, double stdDevM, double rssi, long timestampNanos); + + /** + * Returns the result of the latest call to pollAndProcessOdometryUpdate, + * updateWithUwbMeasurement, or updateWithWifiRttMeasurement. + */ + public native void getEstimate(long sessionPointer, Estimate estimate); + + /** Frees all native memory allocated by this session. */ + public native void deleteSession(long sessionPointer); +} diff --git a/generic_ranging/src/com/android/sensor/Status.java b/generic_ranging/src/com/android/sensor/Status.java new file mode 100644 index 00000000..a415ca49 --- /dev/null +++ b/generic_ranging/src/com/android/sensor/Status.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.sensor; + +public enum Status { + /** An estimate was successfully computed. */ + OK, + /** Could not produce an estimate. For example, no synchronized set of data is available. */ + ESTIMATE_NOT_AVAILABLE, + /** The filter has diverged and is attempting to recover. */ + RECOVERING, + /** + * Tracking failed due to insufficient light. This can occur when using camera based odometry. + * The + * filter will automatically recover and produce an estimate when possible. + */ + RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_LIGHT, + /** + * Tracking failed due to excessive motion. The filter will automatically recover and produce an + * estimate when possible. + */ + RECOVERING_FROM_FAILURE_DUE_TO_EXCESSIVE_MOTION, + /** + * Tracking failed due to insufficient features in the camera images. This can occur when using + * camera based odometry. The filter will automatically recover and produce an estimate when + * possible. + */ + RECOVERING_FROM_FAILURE_DUE_TO_INSUFFICIENT_FEATURES, + /** + * Tracking failed because something else is using the camera. Tracking will recover + * automatically, but with a new origin. + */ + RECOVERING_FROM_FAILURE_DUE_TO_CAMERA_UNAVAILABILITY, + /** + * Tracking failed due to a bad odometry state. The filter will automatically recover and + * produce + * an estimate when possible. + */ + RECOVERING_FROM_FAILURE_DUE_TO_BAD_ODOMETRY_STATE, + /** Odometry failed and cannot be recovered. */ + ODOMETRY_ERROR, + /** The beacon is probably moving, and so cannot be tracked. */ + BEACON_MOVING_ERROR, + /** The configuration file contains an error and Finder can't be started. */ + CONFIGURATION_ERROR, + /** Permissions not granted to required sensors. */ + SENSOR_PERMISSION_DENIED_ERROR, + UNKNOWN_ERROR, +}
\ No newline at end of file |