1#!/usr/bin/env python3
2#
3# Copyright (C) 2021 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"""
17Python module for General abstract GNSS Simulator.
18@author: Clay Liao (jianhsiungliao@)
19"""
20from time import sleep
21from acts.controllers.spectracom_lib import gsg6
22from acts.controllers.spirent_lib import gss7000
23from acts import logger
24from acts.utils import ping
25from acts.libs.proc import job
26
27
28class AbstractGnssSimulator:
29    """General abstract GNSS Simulator"""
30
31    def __init__(self, simulator, ip_addr, ip_port, ip_port_ctrl=7717):
32        """Init AbstractGnssSimulator
33
34        Args:
35            simulator: GNSS simulator name,
36                Type, str
37                Option 'gss7000/gsg6'
38            ip_addr: IP Address.
39                Type, str
40            ip_port: TCPIP Port,
41                Type, str
42            ip_port_ctrl: TCPIP port,
43                Type, int
44                Default, 7717
45        """
46        self.simulator_name = str(simulator).lower()
47        self.ip_addr = ip_addr
48        self.ip_port = ip_port
49        self.ip_port_ctrl = ip_port_ctrl
50        self._logger = logger.create_tagged_trace_logger(
51            '%s %s:%s' % (simulator, self.ip_addr, self.ip_port))
52        if self.simulator_name == 'gsg6':
53            self._logger.info('GNSS simulator is GSG6')
54            self.simulator = gsg6.GSG6(self.ip_addr, self.ip_port)
55        elif self.simulator_name == 'gss7000':
56            self._logger.info('GNSS simulator is GSS7000')
57            self.simulator = gss7000.GSS7000(self.ip_addr, self.ip_port,
58                                             self.ip_port_ctrl)
59        else:
60            self._logger.error('No matched GNSS simulator')
61            raise AttributeError(
62                'The GNSS simulator in config file is {} which is not supported.'
63                .format(self.simulator_name))
64
65    def connect(self):
66        """Connect to GNSS Simulator"""
67        self._logger.debug('Connect to GNSS Simulator {}'.format(
68            self.simulator_name.upper()))
69        self.simulator.connect()
70
71    def close(self):
72        """Disconnect from GNSS Simulator"""
73        self._logger.debug('Disconnect from GNSS Simulator {}'.format(
74            self.simulator_name.upper()))
75        self.simulator.close()
76
77    def start_scenario(self, scenario=''):
78        """Start the running scenario.
79
80        Args:
81            scenario: path of scenario,
82                Type, str
83        """
84        self._logger.info('Start GNSS Scenario {}'.format(scenario))
85        self.simulator.start_scenario(scenario)
86
87    def stop_scenario(self):
88        """Stop the running scenario."""
89        self._logger.debug('Stop playing scenario')
90        self.simulator.stop_scenario()
91
92    def set_power(self, power_level=-130):
93        """Set scenario power level.
94        Args:
95            power_level: target power level in dBm for gsg6 or gss7000,
96                gsg6 power_level range is [-160, -65],
97                gss7000 power_level range is [-170, -115]
98                Type, float,
99        """
100        self.simulator.set_power(power_level)
101
102    def set_power_offset(self, gss7000_ant=1, pwr_offset=0):
103        """Set scenario power level offset based on reference level.
104           The default reference level is -130dBm for GPS L1.
105        Args:
106            ant: target gss7000 RF port,
107                Type, int
108            pwr_offset: target power offset in dB,
109                Type, float
110        """
111        if self.simulator_name == 'gsg6':
112            power_level = -130 + pwr_offset
113            self.simulator.set_power(power_level)
114        elif self.simulator_name == 'gss7000':
115            self.simulator.set_power_offset(gss7000_ant, pwr_offset)
116        else:
117            self._logger.error('No GNSS simulator is available')
118
119    def set_scenario_power(self,
120                           power_level,
121                           sat_id='',
122                           sat_system='',
123                           freq_band=''):
124        """Set dynamic power for the running scenario.
125
126        Args:
127            power_level: transmit power level
128                Type, float.
129                Decimal, unit [dBm]
130            sat_id: set power level for specific satellite identifiers
131                Type, str.
132                Option
133                    For GSG-6: 'Gxx/Rxx/Exx/Cxx/Jxx/Ixx/Sxxx'
134                    where xx is satellite identifiers number
135                    e.g.: G10
136                    For GSS7000: Provide SVID.
137                Default, '', assumed All.
138            sat_system: to set power level for all Satellites
139                Type, str
140                Option [GPS, GLO, GAL, BDS, QZSS, IRNSS, SBAS]
141                Default, '', assumed All.
142            freq_band: Frequency band to set the power level
143                Type, str
144                Default, '', assumed to be L1.
145         Raises:
146            RuntimeError: raise when instrument does not support this function.
147        """
148        self.simulator.set_scenario_power(power_level=power_level,
149                                          sat_id=sat_id,
150                                          sat_system=sat_system,
151                                          freq_band=freq_band)
152
153    def toggle_scenario_power(self,
154                              toggle_onoff='ON',
155                              sat_id='',
156                              sat_system=''):
157        """Toggle ON OFF scenario.
158
159        Args:
160            toggle_onoff: turn on or off the satellites
161                Type, str. Option ON/OFF
162                Default, 'ON'
163            sat_id: satellite identifiers
164                Type, str.
165                Option 'Gxx/Rxx/Exx/Cxx/Jxx/Ixx/Sxxx'
166                where xx is satellite identifiers no.
167                e.g.: G10
168            sat_system: to toggle On/OFF for all Satellites
169                Type, str
170                Option 'GPS/GLO/GAL'
171        """
172        # TODO: [b/208719212] Currently only support GSG-6. Will implement GSS7000 feature.
173        if self.simulator_name == 'gsg6':
174            self.simulator.toggle_scenario_power(toggle_onoff=toggle_onoff,
175                                                 sat_id=sat_id,
176                                                 sat_system=sat_system)
177        else:
178            raise RuntimeError('{} does not support this function'.format(
179                self.simulator_name))
180
181    def ping_inst(self, retry=3, wait=1):
182        """Ping IP of instrument to check if the connection is stable.
183        Args:
184            retry: Retry times.
185                Type, int.
186                Default, 3.
187            wait: Wait time between each ping command when ping fail is met.
188                Type, int.
189                Default, 1.
190        Return:
191            True/False of ping result.
192        """
193        for i in range(retry):
194            ret = ping(job, self.ip_addr)
195            self._logger.debug(f'Ping return results: {ret}')
196            if ret.get('packet_loss') == '0':
197                return True
198            self._logger.warning(f'Fail to ping GNSS Simulator: {i+1}')
199            sleep(wait)
200        return False
201