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