1#!/usr/bin/env python3 2# 3# Copyright 2020 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the 'License'); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an 'AS IS' BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import collections.abc 18from acts.controllers import access_point 19from acts.controllers.ap_lib import bridge_interface 20from acts.controllers.ap_lib import hostapd_security 21from acts.controllers.ap_lib import hostapd_ap_preset 22from acts_contrib.test_utils.wifi.wifi_retail_ap import WifiRetailAP 23 24 25class GoogleWifiAP(WifiRetailAP): 26 """ Class that implements Google Wifi AP. 27 28 This class is a work in progress 29 """ 30 def __init__(self, ap_settings): 31 super().__init__(ap_settings) 32 # Initialize AP 33 if self.ap_settings['2G']['status'] and self.ap_settings['5G_1'][ 34 'status']: 35 raise ValueError('Error initializing Google Wifi AP. ' 36 'Only one interface can be enabled at a time.') 37 38 self.capabilities = { 39 'interfaces': ['2G', '5G_1'], 40 'channels': { 41 '2G': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], 42 '5G_1': [ 43 36, 40, 44, 48, 52, 56, 60, 64, 100, 104, 108, 112, 116, 44 120, 124, 128, 132, 136, 140, 149, 153, 157, 161, 165 45 ] 46 }, 47 'modes': { 48 '2G': ['VHT20', 'VHT40'], 49 '5G_1': ['VHT20', 'VHT40', 'VHT80'] 50 }, 51 'default_mode': 'VHT' 52 } 53 for interface in self.capabilities['interfaces']: 54 self.ap_settings.setdefault(interface, {}) 55 56 self.BW_MODE_MAP = { 57 'legacy': 20, 58 'VHT20': 20, 59 'VHT40': 40, 60 'VHT80': 80 61 } 62 self.default_settings = { 63 'region': 'United States', 64 'brand': 'Google', 65 'model': 'Wifi', 66 'hostapd_profile': 'whirlwind', 67 '2G': { 68 'status': 0, 69 'ssid': 'GoogleWifi_2G', 70 'channel': 11, 71 'bandwidth': 'VHT20', 72 'power': 'auto', 73 'mode': None, 74 'num_streams': None, 75 'rate': 'auto', 76 'short_gi': 0, 77 'security_type': 'Open', 78 'password': 'password', 79 'subnet': '192.168.1.0/24' 80 }, 81 '5G_1': { 82 'status': 0, 83 'ssid': 'GoogleWifi_2G', 84 'channel': 11, 85 'bandwidth': 'VHT20', 86 'power': 'auto', 87 'mode': None, 88 'num_streams': None, 89 'rate': 'auto', 90 'short_gi': 0, 91 'security_type': 'Open', 92 'password': 'password', 93 'subnet': '192.168.9.0/24' 94 } 95 } 96 97 for interface in self.capabilities['interfaces']: 98 for setting in self.default_settings[interface].keys(): 99 if setting not in self.ap_settings[interface]: 100 self.log.debug( 101 '{0} {1} not found during init. Setting {1} = {2}'. 102 format(interface, setting, 103 self.default_settings[interface][setting])) 104 self.ap_settings[interface][ 105 setting] = self.default_settings[interface][setting] 106 init_settings = self.ap_settings.copy() 107 init_settings['ap_subnet'] = { 108 '2g': self.ap_settings['2G']['subnet'], 109 '5g': self.ap_settings['5G_1']['subnet'] 110 } 111 self.access_point = access_point.AccessPoint(init_settings) 112 self.configure_ap() 113 114 def read_ap_settings(self): 115 """Function that reads current ap settings.""" 116 return self.ap_settings.copy() 117 118 def update_ap_settings(self, dict_settings={}, **named_settings): 119 """Function to update settings of existing AP. 120 121 Function copies arguments into ap_settings and calls configure_ap 122 to apply them. 123 124 Args: 125 dict_settings: single dictionary of settings to update 126 **named_settings: named settings to update 127 Note: dict and named_settings cannot contain the same settings. 128 """ 129 settings_to_update = dict(dict_settings, **named_settings) 130 if len(settings_to_update) != len(dict_settings) + len(named_settings): 131 raise KeyError('The following keys were passed twice: {}'.format( 132 (set(dict_settings.keys()).intersection( 133 set(named_settings.keys()))))) 134 135 updating_2G = '2G' in settings_to_update.keys() 136 updating_5G_1 = '5G_1' in settings_to_update.keys() 137 if updating_2G and updating_5G_1: 138 raise ValueError( 139 'Error updating Google WiFi AP. ' 140 'One interface can be activated and updated at a time') 141 elif updating_2G: 142 # If updating an interface and not explicitly setting its status, 143 # it is assumed that the interface is to be ENABLED and updated 144 if 'status' not in settings_to_update['2G']: 145 settings_to_update['2G']['status'] = 1 146 settings_to_update['5G_1'] = {'status': 0} 147 elif updating_5G_1: 148 if 'status' not in settings_to_update['5G_1']: 149 settings_to_update['2G'] = {'status': 0} 150 settings_to_update['5G_1']['status'] = 1 151 self.ap_settings, updates_requested, status_toggle_flag = self._update_settings_dict( 152 self.ap_settings, settings_to_update) 153 if updates_requested: 154 self.configure_ap() 155 156 def configure_ap(self): 157 """Function to configure Google Wifi.""" 158 self.log.info('Stopping Google Wifi interfaces.') 159 print(self.ap_settings) 160 self.access_point.stop_all_aps() 161 162 if self.ap_settings['2G']['status'] == 1: 163 interface = '2G' 164 self.log.info('Bringing up 2.4 GHz interface.') 165 elif self.ap_settings['5G_1']['status'] == 1: 166 interface = '5G_1' 167 self.log.info('Bringing up 5 GHz interface.') 168 else: 169 return 170 171 bss_settings = [] 172 ssid = self.ap_settings[interface]['ssid'] 173 security_mode = self.ap_settings[interface]['security_type'].lower() 174 if 'wpa' in security_mode: 175 password = self.ap_settings[interface]['password'] 176 security = hostapd_security.Security(security_mode=security_mode, 177 password=password) 178 else: 179 security = hostapd_security.Security(security_mode=None, 180 password=None) 181 channel = int(self.ap_settings[interface]['channel']) 182 bandwidth = self.BW_MODE_MAP[self.ap_settings[interface]['bandwidth']] 183 config = hostapd_ap_preset.create_ap_preset( 184 channel=channel, 185 ssid=ssid, 186 security=security, 187 bss_settings=bss_settings, 188 vht_bandwidth=bandwidth, 189 profile_name=self.ap_settings['hostapd_profile'], 190 iface_wlan_2g=self.access_point.wlan_2g, 191 iface_wlan_5g=self.access_point.wlan_5g) 192 config_bridge = self.access_point.generate_bridge_configs(channel) 193 brconfigs = bridge_interface.BridgeInterfaceConfigs( 194 config_bridge[0], 'lan0', config_bridge[2]) 195 self.access_point.bridge.startup(brconfigs) 196 self.access_point.start_ap(config) 197 self.set_power(interface, self.ap_settings[interface]['power']) 198 self.set_rate(interface, 199 mode=self.ap_settings[interface]['mode'], 200 num_streams=self.ap_settings[interface]['num_streams'], 201 rate=self.ap_settings[interface]['rate'], 202 short_gi=self.ap_settings[interface]['short_gi']) 203 self.log.info('AP started on channel {} with SSID {}'.format( 204 channel, ssid)) 205 206 def set_power(self, interface, power): 207 """Function that sets interface transmit power. 208 209 Args: 210 interface: string containing interface identifier (2G, 5G_1) 211 power: power level in dBm 212 """ 213 if power == 'auto': 214 power_string = 'auto' 215 else: 216 if not float(power).is_integer(): 217 self.log.info( 218 'Power in dBm must be an integer. Setting to {}'.format( 219 int(power))) 220 power = int(power) 221 power_string = 'fixed {}'.format(int(power) * 100) 222 223 if '2G' in interface: 224 interface_long = self.access_point.wlan_2g 225 self.ap_settings[interface]['power'] = power 226 elif '5G_1' in interface: 227 interface_long = self.access_point.wlan_5g 228 self.ap_settings[interface]['power'] = power 229 self.access_point.ssh.run('iw dev {} set txpower {}'.format( 230 interface_long, power_string)) 231 232 def set_rate(self, 233 interface, 234 mode=None, 235 num_streams=None, 236 rate='auto', 237 short_gi=0): 238 """Function that sets rate. 239 240 Args: 241 interface: string containing interface identifier (2G, 5G_1) 242 mode: string indicating the WiFi standard to use 243 num_streams: number of MIMO streams. used only for VHT 244 rate: data rate of MCS index to use 245 short_gi: boolean controlling the use of short guard interval 246 """ 247 if '2G' in interface: 248 interface_long = self.access_point.wlan_2g 249 interface_short = '2.4' 250 elif '5G_1' in interface: 251 interface_long = self.access_point.wlan_5g 252 interface_short = '5' 253 self.ap_settings[interface]['mode'] = mode 254 self.ap_settings[interface]['num_streams'] = num_streams 255 self.ap_settings[interface]['rate'] = rate 256 self.ap_settings[interface]['short_gi'] = short_gi 257 258 if rate == 'auto': 259 cmd_string = 'iw dev {0} set bitrates'.format(interface_long) 260 elif 'legacy' in mode.lower(): 261 cmd_string = 'iw dev {0} set bitrates legacy-{1} {2} ht-mcs-{1} vht-mcs-{1}'.format( 262 interface_long, interface_short, rate) 263 elif 'vht' in mode.lower(): 264 cmd_string = 'iw dev {0} set bitrates legacy-{1} ht-mcs-{1} vht-mcs-{1} {2}:{3}'.format( 265 interface_long, interface_short, num_streams, rate) 266 if short_gi: 267 cmd_string = cmd_string + ' sgi-{}'.format(interface_short) 268 elif 'ht' in mode.lower(): 269 cmd_string = 'iw dev {0} set bitrates legacy-{1} ht-mcs-{1} {2} vht-mcs-{1}'.format( 270 interface_long, interface_short, rate) 271 if short_gi: 272 cmd_string = cmd_string + ' sgi-{}'.format(interface_short) 273 self.access_point.ssh.run(cmd_string) 274