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