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