def findGrantsInsideNeighborhood(grants, protection_point, entity_type): """Finds grants inside protection entity neighborhood. Args: grants: An iterable of CBSD grants of type |data.CbsdGrantInfo|. protection_point: The location of a protected entity as (longitude, latitude) tuple. entity_type: The entity type (|data.ProtectedEntityType|). Returns: grants_inside: a list of grants, each one being a namedtuple of type |data.CbsdGrantInfo|, of all CBSDs inside the neighborhood of the protection constraint. """ # Initialize an empty list grants_inside = [] # Loop over each CBSD grant for grant in grants: # Compute distance from CBSD location to protection constraint location dist_km, _, _ = vincenty.GeodesicDistanceBearing( grant.latitude, grant.longitude, protection_point[1], protection_point[0]) # Check if CBSD is inside the neighborhood of protection constraint if dist_km <= _DISTANCE_PER_PROTECTION_TYPE[entity_type][ grant.cbsd_category == 'B']: grants_inside.append(grant) return grants_inside
def getFssNeighboringGwbl(gwbl_records, fss_records): """Returns the list of all FSS within 150km of GWBL and operating below 3700MHz. Args: gwbl_records: List of GWBL record dictionaries. fss_records: List of FSS records dictionaries. """ list_of_fss_neighboring_gwbl = [] gwbl_locations = [ (gwbl_record['record']['deploymentParam'][0]['installationParam'] ['longitude'], gwbl_record['record']['deploymentParam'][0] ['installationParam']['latitude']) for gwbl_record in gwbl_records ] for fss_record in fss_records: fss_params = fss_record['record']['deploymentParam'][0] fss_low_freq = fss_params['operationParam']['operationFrequencyRange'][ 'lowFrequency'] if fss_low_freq >= 3700e6: continue fss_latitude = fss_params['installationParam']['latitude'] fss_longitude = fss_params['installationParam']['longitude'] for gwbl_longitude, gwbl_latitude in gwbl_locations: # TODO: If speed limiting, quick filtering to speed up things with approx dist bound. # Exact distance filtering # Get the distance of the FSS entity from the GWBL polygon distance, _, _ = vincenty.GeodesicDistanceBearing( fss_latitude, fss_longitude, gwbl_latitude, gwbl_longitude) # Get the list of FSS entity that are 150kms from the GWBL area if distance <= FSS_GWBL_PROTECTION_DISTANCE_KM: list_of_fss_neighboring_gwbl.append(fss_record) break return list_of_fss_neighboring_gwbl
def __call__(self, lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, cbsd_indoor=False, reliability=0.5, freq_mhz=3625., its_elev=None, region=None, is_height_cbsd_amsl=False): """See `CalcItmPropagationLoss()` for specification.""" if self.dist_type == 'L1': dist = np.abs(lat_rx - lat_cbsd) + np.abs(lon_rx - lon_cbsd) elif self.dist_type == 'L2': dist = np.sqrt((lat_rx - lat_cbsd)**2 + (lon_rx - lon_cbsd)**2) else: dist, _, _ = vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd, lat_rx, lon_rx) path_loss = self.factor * dist + self.offset bearing_cbsd = np.arctan2(lon_rx - lon_cbsd, lat_rx - lat_cbsd) * 180. / np.pi bearing_rx = 180 + bearing_cbsd if bearing_cbsd < 0: bearing_cbsd +=360 return wf_itm._PropagResult( db_loss=(path_loss if np.isscalar(reliability) else np.ones(len(reliability)) * path_loss), incidence_angles=wf_itm._IncidenceAngles( hor_cbsd=bearing_cbsd, ver_cbsd=0, hor_rx=bearing_rx, ver_rx=0), internals={})
def GetClosestCanadianBorderPoint(latitude, longitude, max_dist_km): """Returns the closest point on the canadian border within some distance. Args: latitude: The point latitude (degrees). longitude: The point longitude (degrees). max_dist_km: The max distance for the border point (km). Returns: Either: None if no border point is within the requested `max dist_km` or a (lat, lon, distance_km, bearing) tuple of the closest point in US/Canada border: * lat, lon: closest point coordinates. * distance_km: the distance to closest point (km). * bearing: the bearing of the closest point (degrees, clockwise relative to north). """ # Prefiltering of the border for quicker processing one_degree = WGS_EQUATORIAL_RADIUS_KM2 * 2 * np.pi / 360. * np.cos(latitude*np.pi/180.) max_delta = max_dist_km / one_degree * 1.1 border_cap = zones.GetUsCanadaBorder().intersection( sgeo.Point(longitude, latitude).buffer(max_delta)) if not border_cap or 'LineString' not in border_cap.type: return None # Find closest point points_dists = [(vincenty.GeodesicDistanceBearing(latitude, longitude, point[1], point[0]), point) for point in zip(*border_cap.xy)] closest = min(points_dists) closest_dist = closest[0][0] if closest_dist > max_dist_km: return None closest_bearing = closest[0][1] return closest[1][1], closest[1][0], closest_dist, closest_bearing
def ResampleUsCanadaBorderLineString(ls, step_m): """Resamples a |shapely.LineString|. This keeps original linestring vertices, and add new vertices in between so as 2 vertices are no more than `step` meters, using either: - vincenty great circle method - linear interpolation for specific latitude of 49 degree Args: ls: A |shapely.LineString| step: The maximum distance between 2 vertices """ vertices = zip(*ls.xy) step_km = step_m / 1000. out_vertices = [] for vertex0, vertex1 in itertools.izip(vertices[0:-1], vertices[1:]): dist_km, _, _ = vincenty.GeodesicDistanceBearing( vertex0[1], vertex0[0], vertex1[1], vertex1[0]) if dist_km < step_km: out_vertices.append(vertex0) else: num_points = int(dist_km / step_km) + 2 if abs(vertex0[1] - 49) < 1e-3 and abs(vertex1[1] - 49) < 1e-3: # departing less than 70m from the 49degree north latitude lats = np.interp(range(num_points), [0, num_points-1], [vertex0[1], vertex1[1]]) lons = np.interp(range(num_points), [0, num_points-1], [vertex0[0], vertex1[0]]) else: lats, lons = vincenty.GeodesicSampling( vertex0[1], vertex0[0], vertex1[1], vertex1[0], num_points) points = zip(lats, lons) out_vertices.extend([(point[1], point[0]) for point in points[:-1]]) out_vertices.append(vertex1) # add the last vertex return sgeo.LineString(out_vertices)
def TerrainProfile(self, lat1, lon1, lat2, lon2, target_res_meter=-1, target_res_arcsec=1, do_interp=True, max_points=-1): """Returns the terrain profile between two points. The path is calculated using Vincenty method for obtaining the geodesic between the 2 points. The profile is returned at equally spaced points along the geodesic as close as possible of the target resolution. The target resolution can be either passed in meters, or in arcseconds (in which case it is converted to an equivalent resolution at equator). Inputs: lat1, lon1: coordinates of starting point (in degrees). lat2, lon2: coordinates of final point (in degrees). target_res_meter: target resolution between points (in meters). If unspecified, uses 'target_res_arcsec' instead. target_res_arcsec: target resolution between 2 point (in arcsec). Only used if 'target_res_meter' unspecified. do_interp: if True (default), use bilinear interpolation on terrain data. max_points: if positive, resolution extended if number of points is beyond this number. Returns: an elevation profile in the ITS format as an array: elev[0] = number of terrain points - 1 (i.e., number of intervals) elev[1] = distance between sample points (meters) elev[2]...elev[npts+1] = Terrain elevation (meters) """ if target_res_meter < 0: target_res_meter = _RADIUS_EARTH_METERS * np.radians( target_res_arcsec / 3600.) # Distance between end points (m) dist, _, _ = vincenty.GeodesicDistanceBearing(lat1, lon1, lat2, lon2) dist *= 1000. num_points = np.ceil(dist / float(target_res_meter)) + 1 if max_points > 0 and num_points > max_points: num_points = max_points if num_points < 2: num_points = 2 resolution = dist / float(num_points - 1) lats, lons = vincenty.GeodesicSampling(lat1, lon1, lat2, lon2, num_points) elev = [num_points - 1, resolution] elev.extend(self.GetTerrainElevation(lats, lons, do_interp)) return elev
def findGrantsInsideNeighborhood(grants, constraint, dpa_type, neighbor_distances): """Identify the CBSD grants in the neighborhood of protection constraint. Inputs: grants: a list of CBSD |data.CbsdGrantInfo| grants. constraint: protection constraint of type |data.ProtectionConstraint| dpa_type: an enum member of class DpaType neighbor_distances: the neighborhood distances (Km) as a sequence: [cata_dist, catb_dist, cata_oob_dist, catb_oob_dist] Returns: A tuple of: grants_inside: a list of |data.CbsdGrantInfo| grants, being inside the neighborhood of that protection `constraint`. idxs_inside: the indices of `grants_inside` in original `grant` list. """ # Initialize an empty list grants_inside = [] idxs_inside = [] neighbor_dists = neighbor_distances[0:2] if dpa_type is DpaType.OUT_OF_BAND: neighbor_dists = neighbor_distances[2:] # Loop over each CBSD grant and filter the ones inside the neighborhood for k, grant in enumerate(grants): # Check frequency range if dpa_type is not DpaType.OUT_OF_BAND: overlapping_bw = ( min(grant.high_frequency, constraint.high_frequency) - max(grant.low_frequency, constraint.low_frequency)) if overlapping_bw <= 0: continue # Compute distance from CBSD location to protection constraint location dist_km, _, _ = vincenty.GeodesicDistanceBearing( grant.latitude, grant.longitude, constraint.latitude, constraint.longitude) # Check if CBSD is inside the neighborhood of protection constraint if dist_km > neighbor_dists[grant.cbsd_category == 'B']: continue grants_inside.append(grant) idxs_inside.append(k) return grants_inside, idxs_inside
def getFssNeighboringCbsdsWithGrants( cbsds, fss_point, distance_km=FSS_GWBL_PROTECTION_DISTANCE_KM): """Returns the list of all CBSDs in the neighborhood of a FSS. The neighborhood is typically defined by all CBSD within 150 KMs and having at least one grant. Args: cbsds : List of |CbsdData| dictionaries as defined in the SAS-SAS specification. fss_point: A tuple (longitude, latitude) of the FSS location. distance_km: The neighboring distance (km). """ neighboring_cbsds_with_grants = [] for cbsd in cbsds: if not cbsd['grants']: continue distance, _, _ = vincenty.GeodesicDistanceBearing( fss_point[1], fss_point[0], cbsd['registration']['installationParam']['latitude'], cbsd['registration']['installationParam']['longitude']) # Get the list of cbsds that are within 150kms from the FSS entity if distance <= distance_km and cbsd['grants']: neighboring_cbsds_with_grants.append(cbsd) return neighboring_cbsds_with_grants
def CalcItmPropagationLoss(lat_cbsd, lon_cbsd, height_cbsd, lat_rx, lon_rx, height_rx, cbsd_indoor=False, reliability=0.5, freq_mhz=3625., its_elev=None, is_height_cbsd_amsl=False, return_internals=False): """Implements the WinnForum-compliant ITM point-to-point propagation model. According to WinnForum spec R2-SGN-17, R2-SGN-22 and R2-SGN-5 to 10. One can use this routine in 3 ways: reliability = -1 : to get the average path loss reliability in [0,1] : to get a pathloss for given quantile sequence of reliabilities: to get an array of pathloss. Used to obtain inverse CDF of the pathloss. Inputs: lat_cbsd, lon_cbsd, height_cbsd: Lat/lon (deg) and height AGL (m) of CBSD lat_rx, lon_rx, height_rx: Lat/lon (deg) and height AGL (m) of Rx point cbsd_indoor: CBSD indoor status - Default=False. reliability: Reliability. Default is 0.5 (median value) Different options: value in [0,1]: returns the CDF quantile -1: returns the mean path loss iterable sequence: returns a list of path losses freq_mhz: Frequency (MHz). Default is mid-point of band. its_elev: Optional profile to use (in ITM format). Default=None If not specified, it is extracted from the terrain. is_height_cbsd_amsl: If True, the CBSD height shall be considered as AMSL (Average mean sea level). return_internals: If True, returns internal variables. Returns: A namedtuple of: db_loss Path Loss in dB, either a scalar if reliability is scalar or a list of path losses if reliability is an iterable. incidence_angles: A namedtuple of hor_cbsd: Horizontal departure angle (bearing) from CBSD to Rx ver_cbsd: Vertical departure angle at CBSD hor_rx: Horizontal incidence angle (bearing) from Rx to CBSD ver_rx: Vertical incidence angle at Rx internals: A dictionary of internal data for advanced analysis (only if return_internals=True): itm_err_num: ITM error code from ItmErrorCode (see GetInfoOnItmCode). itm_str_mode: String containing description of dominant prop mode. dist_km: Distance between end points (km). prof_d_km ndarray of distances (km) - x values to plot terrain. prof_elev ndarray of terrain heightsheights (m) - y values to plot terrain, Raises: Exception if input parameters invalid or out of range. """ # Case of same points if (lat_cbsd == lat_rx and lon_cbsd == lon_rx): return _PropagResult(db_loss=0 if np.isscalar(reliability) else [0] * len(reliability), incidence_angles=_IncidenceAngles(0, 0, 0, 0), internals=None) # Sanity checks on input parameters if freq_mhz < 40.0 or freq_mhz > 10000: raise Exception('Frequency outside range [40MHz - 10GHz]') if is_height_cbsd_amsl: altitude_cbsd = drive.terrain_driver.GetTerrainElevation( lat_cbsd, lon_cbsd) height_cbsd = height_cbsd - altitude_cbsd # Ensure minimum height of 1 meter if height_cbsd < 1: height_cbsd = 1 if height_rx < 1: height_rx = 1 # Internal ITM parameters are always set to following values in WF version: confidence = 0.5 # Confidence (always 0.5) dielec = 25. # Dielectric constant (always 25.) conductivity = 0.02 # Conductivity (always 0.02) polarization = 1 # Polarization (always vertical = 1) mdvar = 13 # Get the terrain profile, using Vincenty great circle route, and WF # standard (bilinear interp; 1500 pts for all distances over 45 km) if its_elev is None: its_elev = drive.terrain_driver.TerrainProfile(lat1=lat_cbsd, lon1=lon_cbsd, lat2=lat_rx, lon2=lon_rx, target_res_meter=30., do_interp=True, max_points=1501) # Find the midpoint of the great circle path dist_km, bearing_cbsd, bearing_rx = vincenty.GeodesicDistanceBearing( lat_cbsd, lon_cbsd, lat_rx, lon_rx) latmid, lonmid, _ = vincenty.GeodesicPoint(lat_cbsd, lon_cbsd, dist_km / 2., bearing_cbsd) # Determine climate value, based on ITU-R P.617 method: climate = drive.climate_driver.TropoClim(latmid, lonmid) # If the common volume lies over the sea, the climate value to use depends # on the climate values at either end. A simple min() function should # properly implement the logic, since water is the max. if climate == 7: climate = min(drive.climate_driver.TropoClim(lat_cbsd, lon_cbsd), drive.climate_driver.TropoClim(lat_rx, lon_rx)) # Look up the refractivity at the path midpoint, if not explicitly provided refractivity = drive.refract_driver.Refractivity(latmid, lonmid) # Call ITM prop loss. reliabilities = reliability do_avg = False if np.isscalar(reliabilities) and reliability == -1: # Pathloss mean: average the value for 1% to 99% included reliabilities = np.arange(0.01, 1.0, 0.01) do_avg = True db_loss, ver_cbsd, ver_rx, str_mode, err_num = itm.point_to_point( its_elev, height_cbsd, height_rx, dielec, conductivity, refractivity, freq_mhz, climate, polarization, confidence, reliabilities, mdvar, False) if do_avg: db_loss = -10 * np.log10(np.mean(10**(-np.array(db_loss) / 10.))) # Add indoor losses if cbsd_indoor: if np.isscalar(db_loss): db_loss += 15 else: db_loss = [loss + 15 for loss in db_loss] # Create distance/terrain arrays for plotting if desired internals = None if return_internals: prof_d_km = (its_elev[1] / 1000.) * np.arange(len(its_elev) - 2) prof_elev = np.asarray(its_elev[2:]) internals = { 'itm_err_num': err_num, 'itm_str_mode': str_mode, 'dist_km': dist_km, 'prof_d_km': prof_d_km, 'prof_elev': prof_elev } return _PropagResult(db_loss=db_loss, incidence_angles=_IncidenceAngles( hor_cbsd=bearing_cbsd, ver_cbsd=ver_cbsd, hor_rx=bearing_rx, ver_rx=ver_rx), internals=internals)
entities.CBSD_TEMPLATE_CAT_B, fss_info.latitude, fss_info.longitude, max_distance_km=200) all_cbsds = [] all_cbsds.extend(cbsds_cat_a_indoor) all_cbsds.extend(cbsds_cat_a_outdoor) all_cbsds.extend(cbsds_cat_b) #--------------------------------------------------------------- # Calculate the aggregated interference calculation seen by the FSS # - first select all CBSD within 150km according to R2-SGN-16 cbsds = [] for cbsd in all_cbsds: distance_km, _, _ = vincenty.GeodesicDistanceBearing( base_fss.latitude, base_fss.longitude, cbsd.latitude, cbsd.longitude) if distance_km <= 150: cbsds.append(cbsd) # - then calculate all path losses and received RSSI at FSS # (without FSS antenna taken into account) cbsd_rssi = np.zeros(len(cbsds)) cbsd_incidence_angles = [] for k, cbsd in enumerate(cbsds): if not (k%10): sys.stdout.write('*') sys.stdout.flush() db_loss, incidence_angles, _ = wf_itm.CalcItmPropagationLoss( cbsd.latitude, cbsd.longitude, cbsd.height_agl,
def test_WINNF_FT_S_QPR_8(self, config_filename): """[Configurable] Unsuccessful Grant Request from CBSDs within 4.8 km of the FCC Field Offices with multiple grants. """ config = loadConfig(config_filename) self.assertValidConfig( config, { 'registrationRequest': dict, 'conditionalRegistrationData': list, 'grantRequest1': dict, 'grantRequest2': dict }) # Whitelist FCC ID and User ID. self._sas_admin.InjectFccId( {'fccId': config['registrationRequest']['fccId']}) self._sas_admin.InjectUserId( {'userId': config['registrationRequest']['userId']}) # Pre-load conditional registration data. if config['conditionalRegistrationData']: self._sas_admin.PreloadRegistrationData( {'registrationData': config['conditionalRegistrationData']}) # Step 1: Register CBSD. request = {'registrationRequest': [config['registrationRequest']]} response = self._sas.Registration(request)['registrationResponse'] # Check registration response. self.assertEqual(len(response), 1) if response[0]['response']['responseCode'] != 0: return # SAS passes immediately in this case. cbsd_id = response[0]['cbsdId'] del request, response # Step 2: Request first grant. config['grantRequest1']['cbsdId'] = cbsd_id grant_request = config['grantRequest1'] request = {'grantRequest': [grant_request]} response = self._sas.Grant(request)['grantResponse'][0] # Check if the first grant response is successful. grant1_approved = response['response']['responseCode'] == 0 del request, response # Step 3: Request second grant config['grantRequest2']['cbsdId'] = cbsd_id grant_request = config['grantRequest2'] request = {'grantRequest': [grant_request]} response = self._sas.Grant(request)['grantResponse'][0] # Check if the second grant response is successful. grant2_approved = response['response']['responseCode'] == 0 del request, response if not (grant1_approved or grant2_approved): logging.info('Both grant requests were rejected') return # SAS passes immediately in this case. # Calculate the closest FCC office lat_cbsd = config['conditionalRegistrationData'][0][ 'installationParam']['latitude'] lon_cbsd = config['conditionalRegistrationData'][0][ 'installationParam']['longitude'] distance_offices = [ vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd, office['latitude'], office['longitude'])[0] for office in self.fcc_offices ] index_closest = np.argmin(distance_offices) closest_fcc_office = self.fcc_offices[index_closest] logging.info('Closest FCC Office Lat: %f', closest_fcc_office['latitude']) logging.info('Closest FCC Office Long: %f', closest_fcc_office['longitude']) # Calculate bearing and ant_gain _, bearing, _ = vincenty.GeodesicDistanceBearing( lat_cbsd, lon_cbsd, closest_fcc_office['latitude'], closest_fcc_office['longitude']) logging.info('Bearing: %f', bearing) ant_gain_dbi = antenna.GetStandardAntennaGains( bearing, config['conditionalRegistrationData'][0] ['installationParam']['antennaAzimuth'], config['conditionalRegistrationData'][0]['installationParam'] ['antennaBeamwidth'], config['conditionalRegistrationData'][0] ['installationParam']['antennaGain']) logging.info('Ant Gain: %f dBi', ant_gain_dbi) max_ant_gain_dbi = (config['conditionalRegistrationData'][0] ['installationParam']['antennaGain']) logging.info('Max Ant Gain: %f dBi', max_ant_gain_dbi) # Gather values required for calculating Total EIRP grant1_eirp = 0 if grant1_approved: p1 = config['grantRequest1']['operationParam']['maxEirp'] logging.info('Grant 1 Max Eirp: %f dBm/MHz', p1) bw1 = (config['grantRequest1']['operationParam'] ['operationFrequencyRange']['highFrequency'] - config['grantRequest1']['operationParam'] ['operationFrequencyRange']['lowFrequency']) / 1.e6 logging.info('Grant 1 Bandwidth: %f', bw1) grant1_eirp = (10**(p1 / 10.0)) * bw1 logging.info('Grant 1 nominal EIRP is %f', grant1_eirp) grant2_eirp = 0 if grant2_approved: p2 = config['grantRequest2']['operationParam']['maxEirp'] logging.info('Grant 2 Max Eirp: %f dBm/MHz', p2) bw2 = (config['grantRequest2']['operationParam'] ['operationFrequencyRange']['highFrequency'] - config['grantRequest2']['operationParam'] ['operationFrequencyRange']['lowFrequency']) / 1.e6 logging.info('Grant 2 Bandwidth: %f', bw2) grant2_eirp = (10**(p2 / 10.0)) * bw2 logging.info('Grant 2 nominal EIRP is %f', grant2_eirp) # Step 4: Calculate Total EIRP total_eirp_dbm = ant_gain_dbi - max_ant_gain_dbi + ( 10 * np.log10(grant1_eirp + grant2_eirp)) logging.info('Total EIRP is %f dBm', total_eirp_dbm) # CHECK: Total EIRP of all approved grants is <= 49.15 dBm self.assertLessEqual(total_eirp_dbm, 49.15)
def test_WINNF_FT_S_QPR_7(self, config_filename): """[Configurable] Unsuccessful Grant Request from CBSDs within 4.8 km of the FCC Field Offices. """ config = loadConfig(config_filename) # Very light checking of the config file. self.assertValidConfig( config, { 'registrationRequest': dict, 'conditionalRegistrationData': list, 'grantRequest': dict }) # Whitelist FCC ID and User ID. self._sas_admin.InjectFccId( {'fccId': config['registrationRequest']['fccId']}) self._sas_admin.InjectUserId( {'userId': config['registrationRequest']['userId']}) # Pre-load conditional registration data. if config['conditionalRegistrationData']: self._sas_admin.PreloadRegistrationData( {'registrationData': config['conditionalRegistrationData']}) # Register CBSD. request = {'registrationRequest': [config['registrationRequest']]} response = self._sas.Registration(request)['registrationResponse'] # Check registration response. self.assertEqual(len(response), 1) if response[0]['response']['responseCode'] != 0: return # SAS passes immediately in this case. cbsd_id = response[0]['cbsdId'] del request, response # Calculate the closest FCC office lat_cbsd = config['conditionalRegistrationData'][0][ 'installationParam']['latitude'] lon_cbsd = config['conditionalRegistrationData'][0][ 'installationParam']['longitude'] distance_offices = [ vincenty.GeodesicDistanceBearing(lat_cbsd, lon_cbsd, office['latitude'], office['longitude'])[0] for office in self.fcc_offices ] index_closest = np.argmin(distance_offices) closest_fcc_office = self.fcc_offices[index_closest] logging.info('Closest FCC office Lat: %f', closest_fcc_office['latitude']) logging.info('Closest FCC office Long: %f', closest_fcc_office['longitude']) # Calculate bearing and ant_gain _, bearing, _ = vincenty.GeodesicDistanceBearing( lat_cbsd, lon_cbsd, closest_fcc_office['latitude'], closest_fcc_office['longitude']) ant_gain = antenna.GetStandardAntennaGains( bearing, config['conditionalRegistrationData'][0] ['installationParam']['antennaAzimuth'], config['conditionalRegistrationData'][0]['installationParam'] ['antennaBeamwidth'], config['conditionalRegistrationData'][0] ['installationParam']['antennaGain']) logging.info('ant_gain is %f dBi', ant_gain) # Gather values required for calculating EIRP p = config['grantRequest']['operationParam']['maxEirp'] logging.info('Grant maxEirp is %f', p) max_ant_gain = (config['conditionalRegistrationData'][0] ['installationParam']['antennaGain']) logging.info('max_ant_gain is %f dBi', max_ant_gain) bw = (config['grantRequest']['operationParam'] ['operationFrequencyRange']['highFrequency'] - config['grantRequest']['operationParam'] ['operationFrequencyRange']['lowFrequency']) / 1.e6 logging.info('bw is %f MHz', bw) # Calculate EIRP to verify grant response eirp = (p - max_ant_gain + ant_gain + (10 * np.log10(bw))) logging.info('EIRP is %f dBm', eirp) # If successfully registered, CBSD sends a grant request config['grantRequest']['cbsdId'] = cbsd_id grant_request = config['grantRequest'] request = {'grantRequest': [grant_request]} response = self._sas.Grant(request)['grantResponse'] # Check grant response self.assertEqual(len(response), 1) # If EIRP <= 49.15 dBm = SUCCESS if eirp <= 49.15: self.assertEqual(response[0]['response']['responseCode'], 0) # If EIRP > 49.15 dBm = INTERFERENCE else: self.assertEqual(response[0]['response']['responseCode'], 400)