• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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