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. 16import re 17from acts import signals 18from collections import defaultdict 19 20SVID_RANGE = { 21 'GPS': [(1, 32)], 22 'SBA': [(120, 192)], 23 'GLO': [(1, 24), (93, 106)], 24 'QZS': [(193, 200)], 25 'BDS': [(1, 63)], 26 'GAL': [(1, 36)], 27 'NIC': [(1, 14)] 28} 29 30CARRIER_FREQUENCIES = { 31 'GPS': { 32 'L1': [1575.42], 33 'L5': [1176.45] 34 }, 35 'SBA': { 36 'L1': [1575.42] 37 }, 38 'GLO': { 39 'L1': [round((1602 + i * 0.5625), 3) for i in range(-7, 7)] 40 }, 41 'QZS': { 42 'L1': [1575.42], 43 'L5': [1176.45] 44 }, 45 'BDS': { 46 'B1': [1561.098], 47 'B2a': [1176.45] 48 }, 49 'GAL': { 50 'E1': [1575.42], 51 'E5a': [1176.45] 52 }, 53 'NIC': { 54 'L5': [1176.45] 55 } 56} 57 58 59class RegexParseException(Exception): 60 pass 61 62 63class GnssSvidContainer: 64 """A class to hold the satellite svid information 65 66 Attributes: 67 used_in_fix: A dict contains unique svid used in fixing location 68 not_used_in_fix: A dict contains unique svid not used in fixing location 69 """ 70 71 def __init__(self): 72 self.used_in_fix = defaultdict(set) 73 self.not_used_in_fix = defaultdict(set) 74 75 def add_satellite(self, gnss_status): 76 """Add satellite svid into container 77 78 According to the attributes gnss_status.used_in_fix 79 True: add svid into self.used_in_fix container 80 False: add svid into self.not_used_in_fix container 81 82 Args: 83 gnss_status: A GnssStatus object 84 """ 85 key = f'{gnss_status.constellation}_{gnss_status.frequency_band}' 86 if gnss_status.used_in_fix: 87 self.used_in_fix[key].add(gnss_status.svid) 88 else: 89 self.not_used_in_fix[key].add(gnss_status.svid) 90 91 92class GnssStatus: 93 """GnssStatus object, it will create an obj with a raw gnssstatus line. 94 95 Attributes: 96 raw_message: (string) The raw log from GSPTool 97 example: 98 Fix: true Type: NIC SV: 4 C/No: 45.10782, 40.9 Elevation: 78.0 99 Azimuth: 291.0 100 Signal: L5 Frequency: 1176.45 EPH: true ALM: false 101 Fix: false Type: GPS SV: 27 C/No: 34.728134, 30.5 Elevation: 102 76.0 Azimuth: 15.0 103 Signal: L1 Frequency: 1575.42 EPH: true ALM: true 104 used_in_fix: (boolean) Whether or not this satellite info is used to fix 105 location 106 constellation: (string) The constellation type i.e. GPS 107 svid: (int) The unique id of the constellation 108 cn: (float) The C/No value from antenna 109 base_cn: (float) The C/No value from baseband 110 elev: (float) The value of elevation 111 azim: (float) The value of azimuth 112 frequency_band: (string) The frequency_type of the constellation i.e. L1 113 / L5 114 """ 115 116 gnssstatus_re = ( 117 r'Fix: (.*) Type: (.*) SV: (.*) C/No: (.*), (.*) ' 118 r'Elevation: (.*) Azimuth: (.*) Signal: (.*) Frequency: (.*) EPH') 119 failures = [] 120 121 def __init__(self, gnssstatus_raw): 122 status_res = re.search(self.gnssstatus_re, gnssstatus_raw) 123 if not status_res: 124 raise RegexParseException(f'Gnss raw msg parse fail:\n{gnssstatus_raw}\n' 125 f'Please check it manually.') 126 self.raw_message = gnssstatus_raw 127 self.used_in_fix = status_res.group(1).lower() == 'true' 128 self.constellation = status_res.group(2) 129 self.svid = int(status_res.group(3)) 130 self.cn = float(status_res.group(4)) 131 self.base_cn = float(status_res.group(5)) 132 self.elev = float(status_res.group(6)) 133 self.azim = float(status_res.group(7)) 134 self.frequency_band = status_res.group(8) 135 self.carrier_frequency = float(status_res.group(9)) 136 137 def validate_gnssstatus(self): 138 """A validate function for each property.""" 139 self._validate_sv() 140 self._validate_cn() 141 self._validate_elev() 142 self._validate_azim() 143 self._validate_carrier_frequency() 144 if self.failures: 145 failure_info = '\n'.join(self.failures) 146 raise signals.TestFailure( 147 f'Gnsstatus validate failed:\n{self.raw_message}\n{failure_info}' 148 ) 149 150 def _validate_sv(self): 151 """A validate function for SV ID.""" 152 if not self.constellation in SVID_RANGE.keys(): 153 raise signals.TestFailure( 154 f'Satellite identify fail: {self.constellation}') 155 for id_range in SVID_RANGE[self.constellation]: 156 if id_range[0] <= self.svid <= id_range[1]: 157 break 158 else: 159 fail_details = f'{self.constellation} ID {self.svid} not in SV Range' 160 self.failures.append(fail_details) 161 162 def _validate_cn(self): 163 """A validate function for CN value.""" 164 if not 0 <= self.cn <= 63: 165 self.failures.append(f'Ant CN not in range: {self.cn}') 166 if not 0 <= self.base_cn <= 63: 167 self.failures.append(f'Base CN not in range: {self.base_cn}') 168 169 def _validate_elev(self): 170 """A validate function for Elevation (should between 0-90).""" 171 if not 0 <= self.elev <= 90: 172 self.failures.append(f'Elevation not in range: {self.elev}') 173 174 def _validate_azim(self): 175 """A validate function for Azimuth (should between 0-360).""" 176 if not 0 <= self.azim <= 360: 177 self.failures.append(f'Azimuth not in range: {self.azim}') 178 179 def _validate_carrier_frequency(self): 180 """A validate function for carrier frequency (should fall in below range). 181 182 'GPS': L1:1575.42, L5:1176.45 183 'SBA': L1:1575.42 184 'GLO': L1:Between 1598.0625 and 1605.375 185 'QZS': L1:1575.42, L5:1176.45 186 'BDS': B1:1561.098, B2a:1176.45 187 'GAL': E1:1575.42, E5a:1176.45 188 'NIC': L5:1176.45 189 """ 190 if self.frequency_band in CARRIER_FREQUENCIES[ 191 self.constellation].keys(): 192 target_freq = CARRIER_FREQUENCIES[self.constellation][ 193 self.frequency_band] 194 else: 195 raise signals.TestFailure( 196 f'Carrier frequency identify fail: {self.frequency_band}') 197 if not self.carrier_frequency in target_freq: 198 self.failures.append( 199 f'{self.constellation}_{self.frequency_band} carrier' 200 f'frequency not in range: {self.carrier_frequency}') 201