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