• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.uwb;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.uwb.AngleMeasurement;
22 import android.uwb.AngleOfArrivalMeasurement;
23 import android.uwb.DistanceMeasurement;
24 import android.uwb.RangingMeasurement;
25 import android.uwb.UwbAddress;
26 
27 import com.android.server.uwb.correction.UwbFilterEngine;
28 import com.android.server.uwb.correction.math.SphericalVector;
29 
30 /**
31  * Represents a remote controlee that is involved in a session.
32  */
33 public class UwbControlee implements AutoCloseable {
34     private static final long SEC_TO_MILLI = 1000;
35     private final UwbAddress mUwbAddress;
36     private final UwbInjector mUwbInjector;
37     private final UwbFilterEngine mEngine;
38     /** Confidence to use when the engine produces a result that wasn't in the original reading. */
39     private static final double DEFAULT_CONFIDENCE = 0.0;
40     /** Error value to use when the engine produces a result that wasn't in the original reading. */
41     private static final double DEFAULT_ERROR_RADIANS = 0.0;
42     /** Error value to use when the engine produces a result that wasn't in the original reading. */
43     private static final double DEFAULT_ERROR_DISTANCE = 0.0;
44     private long mLastMeasurementInstant;
45     private long mPredictionTimeoutMilli = 3000;
46 
47     /**
48      * Creates a new UwbControlee.
49      *
50      * @param uwbAddress The address of the controlee.
51      */
UwbControlee( @onNull UwbAddress uwbAddress, @Nullable UwbFilterEngine engine, @Nullable UwbInjector uwbInjector)52     public UwbControlee(
53             @NonNull UwbAddress uwbAddress,
54             @Nullable UwbFilterEngine engine,
55             @Nullable UwbInjector uwbInjector) {
56         mUwbAddress = uwbAddress;
57         mEngine = engine;
58         mUwbInjector = uwbInjector;
59         if (mUwbInjector != null
60                 && mUwbInjector.getDeviceConfigFacade() != null) {
61             // Injector or deviceConfigFacade might be null during tests and this is fine.
62             mPredictionTimeoutMilli = mUwbInjector
63                     .getDeviceConfigFacade()
64                     .getPredictionTimeoutSeconds() * SEC_TO_MILLI;
65         }
66     }
67 
68     /**
69      * Gets the address of the controlee.
70      *
71      * @return A UwbAddress of the associated controlee.
72      */
getUwbAddress()73     public UwbAddress getUwbAddress() {
74         return mUwbAddress;
75     }
76 
77     /** Shuts down any controlee-specific work. */
78     @Override
close()79     public void close() {
80         if (mEngine != null) {
81             mEngine.close();
82         }
83     }
84 
85     /**
86      * Updates a RangingMeasurement builder to produce a filtered value. If the filter engine
87      *  is not configured, this will not affect the builder.
88      * @param rmBuilder The {@link RangingMeasurement.Builder} to reconfigure.
89      */
filterMeasurement(RangingMeasurement.Builder rmBuilder)90     public void filterMeasurement(RangingMeasurement.Builder rmBuilder) {
91         if (mEngine == null) {
92             // Engine is disabled. Don't modify the builder.
93             return;
94         }
95         RangingMeasurement rawMeasurement = rmBuilder.build();
96 
97         if (rawMeasurement.getStatus() != RangingMeasurement.RANGING_STATUS_SUCCESS) {
98             if (getTime() - mPredictionTimeoutMilli > mLastMeasurementInstant) {
99                 // It's been some time since we last got a good report. Stop reporting values.
100                 return;
101             }
102         } else {
103             mLastMeasurementInstant = getTime();
104         }
105 
106         // Gather az/el/dist
107         AngleOfArrivalMeasurement aoaMeasurement = rawMeasurement.getAngleOfArrivalMeasurement();
108         DistanceMeasurement distMeasurement = rawMeasurement.getDistanceMeasurement();
109         boolean hasAzimuth = false;
110         boolean hasElevation = false;
111         boolean hasDistance = false;
112         float azimuth = 0;
113         float elevation = 0;
114         float distance = 0;
115         long nowMs = mUwbInjector.getElapsedSinceBootMillis();
116         if (aoaMeasurement != null) {
117             if (aoaMeasurement.getAzimuth() != null
118                     && aoaMeasurement.getAzimuth().getConfidenceLevel() > 0) {
119                 hasAzimuth = true;
120                 azimuth = (float) aoaMeasurement.getAzimuth().getRadians();
121             }
122             if (aoaMeasurement.getAltitude() != null
123                     && aoaMeasurement.getAltitude().getConfidenceLevel() > 0) {
124                 hasElevation = true;
125                 elevation = (float) aoaMeasurement.getAltitude().getRadians();
126             }
127         }
128         if (distMeasurement != null) {
129             hasDistance = true;
130             distance = (float) distMeasurement.getMeters();
131         }
132         SphericalVector.Sparse sv = SphericalVector.fromRadians(azimuth, elevation, distance)
133                 .toSparse(hasAzimuth, hasElevation, hasDistance);
134 
135         // Give to the engine.
136         mEngine.add(sv, nowMs);
137 
138         SphericalVector engineResult = mEngine.compute(nowMs);
139         if (engineResult == null) {
140             // Bail early - the engine didn't compute a result, so just leave the builder alone.
141             return;
142         }
143 
144         // Now re-generate the az/el/dist readings based on engine result.
145         updateBuilder(rmBuilder, rawMeasurement, engineResult);
146     }
147 
getTime()148     private long getTime() {
149         if (mUwbInjector == null) {
150             return 0; // Can happen during testing; no time tracking will be supported.
151         }
152         return mUwbInjector.getElapsedSinceBootMillis();
153     }
154 
155     /**
156      * Replaces az/el/dist values in a RangingMeasurement builder.
157      * @param rmBuilder The RangingMeasurement builder to update.
158      * @param rawMeasurement The original raw measurements. Used for fallback and confidence values.
159      * @param replacement The filter engine's result.
160      */
updateBuilder(RangingMeasurement.Builder rmBuilder, RangingMeasurement rawMeasurement, SphericalVector replacement)161     private static void updateBuilder(RangingMeasurement.Builder rmBuilder,
162             RangingMeasurement rawMeasurement,
163             SphericalVector replacement) {
164         // This is fairly verbose because of how nested data is, the risk of nulls, and the
165         // fact that that azimuth is required up-front, even in the builder. Refactoring so the
166         // RangingMeasurement can be cloned and changed would be nice, but it would change
167         // (or at least add to) an external API.
168 
169         // Switch to success - error statuses cannot have any values.
170         rmBuilder.setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS);
171 
172         AngleOfArrivalMeasurement aoaMeasurement = rawMeasurement.getAngleOfArrivalMeasurement();
173         DistanceMeasurement distMeasurement = rawMeasurement.getDistanceMeasurement();
174 
175         AngleMeasurement azimuthMeasurement = null;
176         AngleMeasurement elevationMeasurement = null;
177 
178         // Any AoA in the original measurement?
179         if (aoaMeasurement != null) {
180             // Any azimuth in the original measurement?
181             if (aoaMeasurement.getAzimuth() != null) {
182                 // Yes - create a new azimuth based on the filter's output.
183                 azimuthMeasurement = new AngleMeasurement(
184                         replacement.azimuth,
185                         aoaMeasurement.getAzimuth().getErrorRadians(),
186                         aoaMeasurement.getAzimuth().getConfidenceLevel()
187                 );
188             }
189             // Any elevation in the original measurement?
190             if (aoaMeasurement.getAltitude() != null) {
191                 // Yes - create a new elevation based on the filter's output.
192                 elevationMeasurement = new AngleMeasurement(
193                         replacement.elevation,
194                         aoaMeasurement.getAltitude().getErrorRadians(),
195                         aoaMeasurement.getAltitude().getConfidenceLevel()
196                 );
197             }
198         }
199 
200         AngleOfArrivalMeasurement.Builder aoaBuilder = null;
201         // Only create the aoaBuilder if there was an azimuth in the original measurement.
202         if (azimuthMeasurement != null) {
203             aoaBuilder = new AngleOfArrivalMeasurement.Builder(azimuthMeasurement);
204             if (elevationMeasurement != null) {
205                 aoaBuilder.setAltitude(elevationMeasurement);
206             }
207         }
208 
209         DistanceMeasurement.Builder distanceBuilder = new DistanceMeasurement.Builder();
210         if (distMeasurement == null) {
211             // No distance value. Might have been a one-way AoA.
212 
213             // RangingMeasurement.Build requires that any non-error status has a valid
214             //  DistanceMeasurement, so we will create one.
215             distanceBuilder.setErrorMeters(DEFAULT_ERROR_DISTANCE);
216             distanceBuilder.setConfidenceLevel(DEFAULT_CONFIDENCE);
217         } else {
218             distanceBuilder.setErrorMeters(distMeasurement.getErrorMeters());
219             distanceBuilder.setConfidenceLevel(distMeasurement.getConfidenceLevel());
220         }
221         distanceBuilder.setMeters(replacement.distance);
222 
223         rmBuilder.setDistanceMeasurement(distanceBuilder.build());
224         if (aoaBuilder != null) {
225             rmBuilder.setAngleOfArrivalMeasurement(aoaBuilder.build());
226         }
227     }
228 }
229