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