1 /* 2 * Copyright (C) 2017 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.wifi; 18 19 import android.net.wifi.WifiInfo; 20 21 import com.android.server.wifi.util.KalmanFilter; 22 import com.android.server.wifi.util.Matrix; 23 24 /** 25 * Class used to calculate scores for connected wifi networks and report it to the associated 26 * network agent. 27 */ 28 public class VelocityBasedConnectedScore extends ConnectedScore { 29 30 private final ScoringParams mScoringParams; 31 32 private int mFrequency = ScoringParams.BAND5; 33 private double mThresholdAdjustment; 34 private final KalmanFilter mFilter; 35 private long mLastMillis; 36 VelocityBasedConnectedScore(ScoringParams scoringParams, Clock clock)37 public VelocityBasedConnectedScore(ScoringParams scoringParams, Clock clock) { 38 super(clock); 39 mScoringParams = scoringParams; 40 mFilter = new KalmanFilter(); 41 mFilter.mH = new Matrix(2, new double[]{1.0, 0.0}); 42 mFilter.mR = new Matrix(1, new double[]{1.0}); 43 } 44 45 /** 46 * Set the Kalman filter's state transition matrix F and process noise covariance Q given 47 * a time step. 48 * 49 * @param dt delta time, in seconds 50 */ setDeltaTimeSeconds(double dt)51 private void setDeltaTimeSeconds(double dt) { 52 mFilter.mF = new Matrix(2, new double[]{1.0, dt, 0.0, 1.0}); 53 Matrix tG = new Matrix(1, new double[]{0.5 * dt * dt, dt}); 54 double stda = 0.02; // standard deviation of modelled acceleration 55 mFilter.mQ = tG.dotTranspose(tG).dot(new Matrix(2, new double[]{ 56 stda * stda, 0.0, 57 0.0, stda * stda})); 58 } 59 /** 60 * Reset the filter state. 61 */ 62 @Override reset()63 public void reset() { 64 mLastMillis = 0; 65 mThresholdAdjustment = 0; 66 mFilter.mx = null; 67 } 68 69 /** 70 * Updates scoring state using RSSI and measurement noise estimate 71 * <p> 72 * This is useful if an RSSI comes from another source (e.g. scan results) and the 73 * expected noise varies by source. 74 * 75 * @param rssi signal strength (dB). 76 * @param millis millisecond-resolution time. 77 * @param standardDeviation of the RSSI. 78 */ 79 @Override updateUsingRssi(int rssi, long millis, double standardDeviation)80 public void updateUsingRssi(int rssi, long millis, double standardDeviation) { 81 if (millis <= 0) return; 82 if (mLastMillis <= 0 || millis < mLastMillis || mFilter.mx == null) { 83 double initialVariance = 9.0 * standardDeviation * standardDeviation; 84 mFilter.mx = new Matrix(1, new double[]{rssi, 0.0}); 85 mFilter.mP = new Matrix(2, new double[]{initialVariance, 0.0, 0.0, 0.0}); 86 } else { 87 double dt = (millis - mLastMillis) * 0.001; 88 mFilter.mR.put(0, 0, standardDeviation * standardDeviation); 89 setDeltaTimeSeconds(dt); 90 mFilter.predict(); 91 mFilter.update(new Matrix(1, new double[]{rssi})); 92 } 93 mLastMillis = millis; 94 mFilteredRssi = mFilter.mx.get(0, 0); 95 mEstimatedRateOfRssiChange = mFilter.mx.get(1, 0); 96 } 97 98 /** 99 * Updates the state. 100 */ 101 @Override updateUsingWifiInfo(WifiInfo wifiInfo, long millis)102 public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) { 103 int frequency = wifiInfo.getFrequency(); 104 if (frequency != mFrequency) { 105 mLastMillis = 0; // Probably roamed; reset filter but retain threshold adjustment 106 // Consider resetting or partially resetting threshold adjustment 107 // Consider checking bssid 108 mFrequency = frequency; 109 } 110 updateUsingRssi(wifiInfo.getRssi(), millis, mDefaultRssiStandardDeviation); 111 adjustThreshold(wifiInfo); 112 } 113 114 private double mFilteredRssi; 115 private double mEstimatedRateOfRssiChange; 116 117 /** 118 * Returns the most recently computed extimate of the RSSI. 119 */ getFilteredRssi()120 public double getFilteredRssi() { 121 return mFilteredRssi; 122 } 123 124 /** 125 * Returns the estimated rate of change of RSSI, in dB/second 126 */ getEstimatedRateOfRssiChange()127 public double getEstimatedRateOfRssiChange() { 128 return mEstimatedRateOfRssiChange; 129 } 130 131 /** 132 * Returns the adjusted RSSI threshold 133 */ getAdjustedRssiThreshold()134 public double getAdjustedRssiThreshold() { 135 return mScoringParams.getExitRssi(mFrequency) + mThresholdAdjustment; 136 } 137 138 private double mMinimumPpsForMeasuringSuccess = 2.0; 139 140 /** 141 * Adjusts the threshold if appropriate 142 * <p> 143 * If the (filtered) rssi is near or below the current effective threshold, and the 144 * rate of rssi change is small, and there is traffic, and the error rate is looking 145 * reasonable, then decrease the effective threshold to keep from dropping a perfectly good 146 * connection. 147 * 148 */ adjustThreshold(WifiInfo wifiInfo)149 private void adjustThreshold(WifiInfo wifiInfo) { 150 if (mThresholdAdjustment < -7) return; 151 if (mFilteredRssi >= getAdjustedRssiThreshold() + 2.0) return; 152 if (Math.abs(mEstimatedRateOfRssiChange) >= 0.2) return; 153 double txSuccessPps = wifiInfo.txSuccessRate; 154 double rxSuccessPps = wifiInfo.rxSuccessRate; 155 if (txSuccessPps < mMinimumPpsForMeasuringSuccess) return; 156 if (rxSuccessPps < mMinimumPpsForMeasuringSuccess) return; 157 double txBadPps = wifiInfo.txBadRate; 158 double txRetriesPps = wifiInfo.txRetriesRate; 159 double probabilityOfSuccessfulTx = txSuccessPps / (txSuccessPps + txBadPps + txRetriesPps); 160 if (probabilityOfSuccessfulTx > 0.2) { 161 // May want this amount to vary with how close to threshold we are 162 mThresholdAdjustment -= 0.5; 163 } 164 } 165 166 /** 167 * Velocity scorer - predict the rssi a few seconds from now 168 */ 169 @Override generateScore()170 public int generateScore() { 171 if (mFilter.mx == null) return WIFI_TRANSITION_SCORE + 1; 172 double badRssi = getAdjustedRssiThreshold(); 173 double horizonSeconds = mScoringParams.getHorizonSeconds(); 174 Matrix x = new Matrix(mFilter.mx); 175 double filteredRssi = x.get(0, 0); 176 setDeltaTimeSeconds(horizonSeconds); 177 x = mFilter.mF.dot(x); 178 double forecastRssi = x.get(0, 0); 179 if (forecastRssi > filteredRssi) { 180 forecastRssi = filteredRssi; // Be pessimistic about predicting an actual increase 181 } 182 int score = (int) (Math.round(forecastRssi) - badRssi) + WIFI_TRANSITION_SCORE; 183 return score; 184 } 185 } 186