blob: 680643fc3dd5888fc1b9c5c4574834a53f120e37 [file] [log] [blame]
Hyunsun Moondb72b8f2020-11-02 18:03:39 -08001#!/usr/bin/env python3
Hyunsun Moon53097ea2020-09-04 17:20:29 -07002
3# Copyright 2020-present Open Networking Foundation
4#
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -07005# SPDX-License-Identifier: LicenseRef-ONF-Member-Only-1.0
Hyunsun Moon53097ea2020-09-04 17:20:29 -07006
7import sys
8import os
9import json
10import logging
11import enum
Jeremy Ronquillo8d108652021-11-22 17:34:58 -080012import pycurl
Hyunsun Moon53097ea2020-09-04 17:20:29 -070013import time
14import serial
Jeremy Ronquillod996b512021-02-13 13:45:47 -080015import subprocess
Jeremy Ronquilloef17e362021-11-08 10:56:42 -080016import time
Hyunsun Moon53097ea2020-09-04 17:20:29 -070017from collections import namedtuple
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -070018from statistics import median
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -080019import xml.etree.ElementTree as ET
Hyunsun Moon53097ea2020-09-04 17:20:29 -070020
21'''
Jeremy Ronquilloa944fbc2021-03-30 10:57:45 -070022"Simple" script that checks Aether network operational status periodically
Hyunsun Moon53097ea2020-09-04 17:20:29 -070023by controlling the attached 4G/LTE modem with AT commands and
24report the result to the central monitoring server.
25'''
26
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -070027# Parse config with backwards compatibility with config.json pre 0.6.6
28config_file_contents = open(os.getenv('CONFIG_FILE', "./config.json")).read()
29config_file_contents = config_file_contents.replace("user_plane_ping_test", "dns")
30config_file_contents = config_file_contents.replace("speedtest_iperf", "iperf_server")
Jeremy Ronquilloc03ba682021-10-06 10:27:09 -070031config_file_contents = config_file_contents.replace("\"speedtest_ping_dns\": \"1.1.1.1\",", "")
Hyunsun Moon53097ea2020-09-04 17:20:29 -070032CONF = json.loads(
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -070033 config_file_contents, object_hook=lambda d: namedtuple('X', d.keys())(*d.values())
Hyunsun Moon53097ea2020-09-04 17:20:29 -070034)
35
36logging.basicConfig(
37 filename=CONF.log_file,
38 format='%(asctime)s [%(levelname)s] %(message)s',
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -070039 level=logging.getLevelName(CONF.log_level)
Hyunsun Moon53097ea2020-09-04 17:20:29 -070040)
41
Hyunsun Moon53097ea2020-09-04 17:20:29 -070042
43class State(enum.Enum):
44 error = "-1"
45 disconnected = "0"
46 connected = "1"
47
48 @classmethod
49 def has_value(cls, value):
50 return value in cls._value2member_map_
51
52
53class Modem():
54 log = logging.getLogger('aether_edge_monitoring.Modem')
55
56 read_timeout = 0.1
57
58 def __init__(self, port, baudrate):
59 self.port = port
60 self.baudrate = baudrate
61 self._response = None
62
Don Newtonbd91ae22021-05-11 14:58:18 -070063 def get_modem_port(self):
64 cmd = "ls " + CONF.modem.port
65 sp = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,
66 stderr=subprocess.PIPE, universal_newlines=True)
67 rc = sp.wait()
68 ret,err = sp.communicate()
69 if err != "" :
70 logging.error("unable to find serial port " + err)
71
72 ret = ret.replace(CONF.modem.port,"").strip()
73 logging.info("Modem.get_modem_port found " + ret)
74 return ret
75
Hyunsun Moon53097ea2020-09-04 17:20:29 -070076 def connect(self):
Don Newtonbd91ae22021-05-11 14:58:18 -070077 self.port=self.get_modem_port()
78 logging.info("modem.connect Port: %s, BaudRate: %i",self.port,self.baudrate)
Hyunsun Moon53097ea2020-09-04 17:20:29 -070079 self.serial = serial.Serial(
80 port=self.port,
81 baudrate=self.baudrate,
82 timeout=1)
83
84 def _write(self, command):
85 if self.serial.inWaiting() > 0:
86 self.serial.flushInput()
87
88 self._response = b""
89
90 self.serial.write(bytearray(command + "\r", "ascii"))
91 read = self.serial.inWaiting()
92 while True:
93 if read > 0:
94 self._response += self.serial.read(read)
95 else:
96 time.sleep(self.read_timeout)
97 read = self.serial.inWaiting()
98 if read == 0:
99 break
100 return self._response.decode("ascii").replace('\r\n', ' ')
101
102 def write(self, command, wait_resp=True):
103 response = self._write(command)
104 self.log.debug("%s: %s", command, response)
105
106 if wait_resp and "ERROR" in response:
107 return False, None
108 return True, response
109
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700110 def get_state(self):
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700111 success, result = self.write('AT+CGATT?')
112 if not success or 'CGATT:' not in result:
113 return State.error
114 state = result.split('CGATT:')[1].split(' ')[0]
115 return State(state)
116
117 def close(self):
118 self.serial.close()
119
120
121def get_control_plane_state(modem):
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700122 # Disable radio fuction
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700123 # "echo" works more stable than serial for this action
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700124 try:
Don Newtonbd91ae22021-05-11 14:58:18 -0700125 logging.debug("echo 'AT+CFUN=0' > " + modem.port)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700126 subprocess.check_output(
Don Newtonbd91ae22021-05-11 14:58:18 -0700127 "echo 'AT+CFUN=0' > " + modem.port, shell=True)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700128 except subprocess.CalledProcessError as e:
129 logging.error("Write 'AT+CFUN=0' failed")
130 return State.error
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700131
132 # Wait until the modem is fully disconnected
133 retry = 0
134 state = None
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700135 while retry < CONF.detach_timeout:
136 state = modem.get_state()
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700137 if state is State.disconnected:
138 break
139 time.sleep(1)
140 retry += 1
141
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700142 if state is not State.disconnected:
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700143 logging.error("Failed to disconnect")
144 return State.error
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700145
146 time.sleep(2)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700147 # Enable radio function
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700148 # "echo" works more stable than serial for this action
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700149 try:
Don Newtonbd91ae22021-05-11 14:58:18 -0700150 logging.debug("echo 'AT+CFUN=1' > " + modem.port)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700151 subprocess.check_output(
Don Newtonbd91ae22021-05-11 14:58:18 -0700152 "echo 'AT+CFUN=1' > " + modem.port, shell=True)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700153 except subprocess.CalledProcessError as e:
154 logging.error("Write 'AT+CFUN=1' failed")
155 return State.error
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700156
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700157 # Wait attach_timeout sec for the modem to be fully connected
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700158 retry = 0
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700159 while retry < CONF.attach_timeout:
160 state = modem.get_state()
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700161 if state is State.connected:
162 break
163 time.sleep(1)
164 retry += 1
165 # CGATT sometimes returns None
166 if state is State.error:
167 state = State.disconnected
168
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700169 return state
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700170
171
172def get_user_plane_state(modem):
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800173 if "dry_run" in CONF.ips._fields and CONF.ips.dry_run: # run dry_run latency test as user plane test
174 dry_run_latency, dry_run_passed = run_ping_test(CONF.ips.dry_run, 3)
175 if dry_run_passed:
176 return State.connected, dry_run_latency
177 else:
178 logging.warning("User plane test failed")
179 return State.disconnected, dry_run_latency
180 else: # run default user plane test
181 try:
182 subprocess.check_output(
183 "ping -c 3 " + CONF.ips.dns + ">/dev/null 2>&1",
184 shell=True)
185 return State.connected, None
186 except subprocess.CalledProcessError as e:
187 logging.warning("User plane test failed")
188 return State.disconnected, None
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700189
190
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800191def run_ping_test(ip, count):
192 '''
193 Runs the ping test
194 Input: IP to ping, # times to ping
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700195 Returns: Transmitted packets
196 Received packets
197 Median ping ms
198 Min ping ms
199 Avg ping ms
200 Max ping ms
201 Std Dev ping ms
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800202 '''
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700203 result = {'transmitted': 0,
204 'received': 0,
205 'median': 0.0,
206 'min': 0.0,
Jeremy Ronquilloa944fbc2021-03-30 10:57:45 -0700207 'avg': 0.0,
208 'max': 0.0,
209 'stddev': 0.0}
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700210 if not ip:
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700211 return result, True
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800212 try:
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700213 pingOutput = subprocess.check_output(
214 "ping -c " + str(count) + " " + \
215 ip, shell=True).decode("UTF-8").split()
216 result['transmitted'] = int(pingOutput[-15])
217 result['received'] = int(pingOutput[-12])
218 if result['received'] > 0:
219 pingValues = []
220
221 # Hack for getting all ping values for median
222 for word in pingOutput:
223 if "time=" in word:
224 pingValues.append(float(word.split("=")[1]))
225 result['median'] = round(median(pingValues), 3)
226
227 pingResult = pingOutput[-2].split('/')
228 result['min'] = float(pingResult[0])
229 result['avg'] = float(pingResult[1])
230 result['max'] = float(pingResult[2])
231 result['stddev'] = float(pingResult[3])
232 else:
233 logging.error("No packets received during ping " + ip)
234 return result, False
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800235 except Exception as e:
236 logging.error("Ping test failed for " + ip + ": %s", e)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700237 return result, False
238 return result, True
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800239
240
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800241def get_ping_test(modem, dry_run_latency=None):
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800242 '''
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700243 Prepares the ping test.
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700244 Runs ping tests from 'ips' entry in config.json in order.
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800245 Note: 'dry_run' is not run here; it is run during the user plane test.
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800246 '''
247 speedtest_ping = {}
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700248 status = True
249 ping_test_passed = True
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700250
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800251 if dry_run_latency:
252 speedtest_ping["dry_run"] = dry_run_latency
253
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700254 for i in range(0, len(CONF.ips)):
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800255 if CONF.ips._fields[i] == "dry_run":
256 continue
257 count = 5
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700258 speedtest_ping[CONF.ips._fields[i]], status = run_ping_test(CONF.ips[i], count)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700259 if not status:
260 ping_test_passed = False
261 logging.error("Ping test failed. Not running further tests.")
262 return speedtest_ping, ping_test_passed
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800263
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700264def run_iperf_test(ip, port, time_duration, is_downlink):
265 '''
266 Runs iperf test to specified IP in the config file.
267 - Runs for 10 seconds (10 iterations)
268 - Retrieves downlink and uplink test results from json output
269 '''
270 result = 0.0
Jeremy Ronquilloc03ba682021-10-06 10:27:09 -0700271 if not ip or port == 0:
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700272 return result
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700273 maxRetries = 2
Jeremy Ronquillo79c3e672021-09-03 12:54:55 -0700274 err = None
Jeremy Ronquillo012ac662021-08-06 12:13:43 -0700275 for _ in range(0, maxRetries):
276 try:
277 iperfResult = json.loads(subprocess.check_output(
278 "iperf3 -c " + ip +
279 " -p " + str(port) +
280 " -t " + str(time_duration) +
281 (" -R " if is_downlink else "") +
282 " --json", shell=True).decode("UTF-8"))
283 received_mbps = iperfResult['end']['sum_received']['bits_per_second'] / 1000000
284 sent_mbps = iperfResult['end']['sum_sent']['bits_per_second'] / 1000000.0
285 result = received_mbps if is_downlink else sent_mbps
286 return result
287 except Exception as e:
Jeremy Ronquillo79c3e672021-09-03 12:54:55 -0700288 err = e
Jeremy Ronquillo012ac662021-08-06 12:13:43 -0700289 time.sleep(5)
290 pass
Jeremy Ronquillo79c3e672021-09-03 12:54:55 -0700291 logging.error("After " + str(maxRetries) + " retries, iperf test failed for " + ip + ": %s", err)
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700292 return result
293
294
295def get_iperf_test(modem):
296 '''
297 Prepares the iperf test.
298 '''
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800299 global hour_iperf_scheduled_time_last_ran
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700300 speedtest_iperf = {}
301 speedtest_iperf['cluster'] = {}
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800302
303 if "iperf_schedule" in CONF._fields and len(CONF.iperf_schedule) > 0:
304 if int(time.strftime("%H")) not in CONF.iperf_schedule: # not in the schedule
305 hour_iperf_scheduled_time_last_ran = -1
306 return None
307 elif int(time.strftime("%H")) == hour_iperf_scheduled_time_last_ran: # already ran this hour
308 return None
309 hour_iperf_scheduled_time_last_ran = int(time.strftime("%H"))
310
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700311 speedtest_iperf['cluster']['downlink'] = run_iperf_test(CONF.ips.iperf_server, CONF.iperf_port, 10, True)
312 speedtest_iperf['cluster']['uplink'] = run_iperf_test(CONF.ips.iperf_server, CONF.iperf_port, 10, False)
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700313
314 return speedtest_iperf
315
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800316
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700317def get_signal_quality(modem):
318 success, result = modem.write('AT+CESQ')
Don Newtonbd91ae22021-05-11 14:58:18 -0700319 logging.debug("get_signal_quality success %i result %s",success,result)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700320 if not success or 'CESQ: ' not in result:
321 logging.error("Failed to get signal quality")
Don Newtonbd91ae22021-05-11 14:58:18 -0700322
323 return {'rsrq':0, 'rsrp':0}
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700324
325 logging.debug("%s", result)
326 tmp_rsrq = result.split('CESQ:')[1].split(',')[4]
327 tmp_rsrp = result.split('CESQ:')[1].split(',')[5]
328
329 rsrq = int(tmp_rsrq.strip())
330 rsrp = int(tmp_rsrp.strip().split(' ')[0])
331 result = {
332 'rsrq': 0 if rsrq is 255 else rsrq,
333 'rsrp': 0 if rsrp is 255 else rsrp
334 }
335
336 return result
337
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800338def get_dongle_stats(modem):
339 result = {'SuccessfulFetch' : False}
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800340 if "report_in_band" in CONF._fields:
341 result['inBandReporting'] = CONF.report_in_band
342 else:
343 result['inBandReporting'] = False
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800344 XMLkeys = ["MAC",
345 "PLMNStatus",
346 "UICCStatus",
347 "IMEI",
348 "IMSI",
349 "PLMNSelected",
350 "MCC",
351 "MNC",
352 "PhyCellID",
353 "CellGlobalID",
354 "Band",
355 "EARFCN",
356 "BandWidth",
357 "ServCellState",
358 "Connection",
359 "IPv4Addr"]
360 dongleStatsXML = None
361 try:
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800362 dongleStatsXML = ET.fromstring(subprocess.check_output("curl -u admin:admin -s 'http://192.168.0.1:8080/cgi-bin/ltestatus.cgi?Command=Status'", shell=True).decode("UTF-8"))
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800363 except Exception as e:
364 logging.error("Failed to fetch dongle stats from URL: " + str(e))
365 return result
366 try:
367 for key in XMLkeys:
368 try:
369 result[key] = dongleStatsXML.find(key).text
370 except AttributeError as e:
371 logging.warn("Failed to find " + key + " in XML.")
372 result[key] = ""
373 result["SuccessfulFetch"] = True
374 except Exception as e:
375 logging.error("Failed to fetch dongle stats from XML: " + str(e))
376 return result
377 return result
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700378
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800379
380def report_status(signal_quality, dongle_stats, cp_state=None, up_state=None, speedtest_ping=None, speedtest_iperf=None):
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700381 report = {
382 'name': CONF.edge_name,
383 'status': {
384 'control_plane': "disconnected",
385 'user_plane': "disconnected"
386 },
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800387 'dongle_stats': {
388 'SuccessfulFetch' : False
389 },
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700390 'speedtest': {
391 'ping': {
392 'dns': {
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700393 'transmitted' : 0,
394 'received' : 0,
395 'median' : 0.0,
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700396 'min': 0.0,
397 'avg': 0.0,
398 'max': 0.0,
399 'stddev': 0.0
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700400 }
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700401 },
402 'iperf': {
403 'cluster': {
404 'downlink': 0.0,
405 'uplink': 0.0
406 }
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700407 }
408 },
409 'signal_quality': {
410 'rsrq': 0,
411 'rsrp': 0
412 }
413 }
414
415 if cp_state is not None:
416 report['status']['control_plane'] = cp_state.name
417 if up_state is not None:
418 report['status']['user_plane'] = up_state.name
419 if speedtest_ping is not None:
420 report['speedtest']['ping'] = speedtest_ping
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700421 if speedtest_iperf is not None:
422 report['speedtest']['iperf'] = speedtest_iperf
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700423 report['signal_quality'] = signal_quality
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800424 report['dongle_stats'] = dongle_stats
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700425
426 logging.info("Sending report %s", report)
Don Newtonbd91ae22021-05-11 14:58:18 -0700427 global cycles
428 cycles += 1
429 logging.info("Number of cycles since modem restart %i",cycles)
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800430
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700431 try:
Jeremy Ronquilloeff2e6d2021-12-06 10:39:37 -0800432 interface = None
433 report_via_modem = "report_in_band" in CONF._fields and CONF.report_in_band and \
434 "iface" in CONF.modem._fields and CONF.modem.iface
435 report_via_given_iface = "report_iface" in CONF._fields and CONF.report_iface
436
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800437 c = pycurl.Curl()
438 c.setopt(pycurl.URL, CONF.report_url)
439 c.setopt(pycurl.POST, True)
440 c.setopt(pycurl.HTTPHEADER, ['Content-Type: application/json'])
441 c.setopt(pycurl.TIMEOUT, 10)
442 c.setopt(pycurl.POSTFIELDS, json.dumps(report))
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800443 c.setopt(pycurl.WRITEFUNCTION, lambda x: None) # don't output to console
Jeremy Ronquilloeff2e6d2021-12-06 10:39:37 -0800444
445 if report_via_modem: # report in-band
446 interface = CONF.modem.iface
447 c.setopt(pycurl.INTERFACE, interface)
448 elif report_via_given_iface: # report over given interface
449 interface = CONF.report_iface
450 c.setopt(pycurl.INTERFACE, interface)
451 # else, reports over default interface
452
453 try:
454 c.perform()
455 logging.info("Report sent via " + interface + "!")
456 except Exception as e:
457 if report_via_modem and report_via_given_iface:
458 logging.warning("Sending report via modem failed. Attempting to send report via " + str(CONF.report_iface) + ".")
459 interface = CONF.report_iface
460 c.setopt(pycurl.INTERFACE, interface)
461 c.perform()
462 logging.info("Report sent via " + interface + "!")
463 else:
464 logging.error("Failed to send report: " + str(e))
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800465 c.close()
466 except Exception as e:
467 logging.error("Failed to send report: " + str(e))
Jeremy Ronquilloeff2e6d2021-12-06 10:39:37 -0800468 c.close()
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800469
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700470 time.sleep(CONF.report_interval)
Jeremy Ronquillo82a14612021-10-08 12:08:20 -0700471
Don Newtonbd91ae22021-05-11 14:58:18 -0700472def reset_usb():
Jeremy Ronquillo82a14612021-10-08 12:08:20 -0700473 try:
474 # Attempt to run uhubctl
475 if (int(subprocess.call("which uhubctl",shell=True)) == 0):
476 cmd = "/usr/sbin/uhubctl -a 0 -l 2" # -a 0 = action is shutdown -l 2 location = bus 2 on pi controls power to all hubs
477 ret = subprocess.call(cmd,shell=True)
478 logging.info("Shutting down usb hub 2 results %s" , ret)
479 time.sleep(10)# let power down process settle out
480 cmd = "/usr/sbin/uhubctl -a 1 -l 2" # -a 1 = action is start -l 2 location = bus 2 on pi controls power to all hubs
481 ret = subprocess.call(cmd,shell=True)
482 logging.info("Starting up usb hub 2 results %s" , ret)
483 time.sleep(10) #allow dbus to finish
484 global cycles
485 cycles = 0
486 else:
487 reboot(120)
488 except Exception as e:
489 logging.error("Failed to run uhubctl: %s", e)
490 reboot(120)
491
492def reboot(delay):
493 logging.error("Failed to run uhubctl. Reboot system in " + str(delay) + " second(s).")
494 time.sleep(delay)
495 subprocess.check_output("sudo shutdown -r now", shell=True)
Don Newtonbd91ae22021-05-11 14:58:18 -0700496
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700497def main():
Don Newtonbd91ae22021-05-11 14:58:18 -0700498 global cycles
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800499 global hour_iperf_scheduled_time_last_ran
Don Newtonbd91ae22021-05-11 14:58:18 -0700500 cycles = 0
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800501 hour_iperf_scheduled_time_last_ran = -1
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800502
503 try:
504 if "report_in_band" in CONF._fields and \
505 "iface" in CONF.modem._fields and CONF.modem.iface:
506 if CONF.report_in_band: # need to add default gateway if reporting in-band
507 subprocess.check_output("sudo route add default gw " + CONF.modem.ip_addr + " " + CONF.modem.iface + " || true", shell=True)
508 else:
509 subprocess.check_output("sudo route del default gw " + CONF.modem.ip_addr + " " + CONF.modem.iface + " || true", shell=True)
510 except Exception as e:
511 logging.error("Failed to change default route for modem: " + str(e))
512
Don Newtonbd91ae22021-05-11 14:58:18 -0700513 for ip in CONF.ips:
514 if not ip:
515 continue
516 try:
517 subprocess.check_output("sudo ip route replace {}/32 via {}".format(
518 ip, CONF.modem.ip_addr), shell=True)
519 except subprocess.CalledProcessError as e:
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800520 logging.error("Failed to add routes: " + str(e.returncode) + str(e.output))
Jeremy Ronquillo82a14612021-10-08 12:08:20 -0700521 time.sleep(10) # Sleep for 10 seconds before retry
Don Newtonbd91ae22021-05-11 14:58:18 -0700522 sys.exit(1)
523
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700524 modem = Modem(CONF.modem.port, CONF.modem.baud)
525 try:
526 modem.connect()
527 except serial.serialutil.SerialException as e:
528 logging.error("Failed to connect the modem for %s", e)
Hyunsun Moon2b7d3e12021-01-04 13:30:19 -0800529 sys.exit(1)
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700530
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700531 while True:
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700532 cp_state = get_control_plane_state(modem)
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800533 signal_quality = get_signal_quality(modem)
534 dongle_stats = get_dongle_stats(modem)
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700535 if cp_state is State.error:
536 logging.error("Modem is in error state.")
Don Newtonbd91ae22021-05-11 14:58:18 -0700537 reset_usb()
Hyunsun Moonf4242372020-10-04 23:32:38 -0700538 sys.exit(1)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700539 if cp_state is State.disconnected:
540 # Failed to attach, don't need to run other tests
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800541 report_status(signal_quality, dongle_stats)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700542 continue
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700543
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800544 up_state, dry_run_latency = get_user_plane_state(modem)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700545 if up_state is State.disconnected:
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700546 # Basic user plane test failed, don't need to run the rest of tests
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800547 report_status(signal_quality, dongle_stats, cp_state)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700548 continue
549
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800550 speedtest_ping, speedtest_status = get_ping_test(modem, dry_run_latency)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700551 if speedtest_status:
552 speedtest_iperf = get_iperf_test(modem)
553 else:
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800554 report_status(signal_quality, dongle_stats, cp_state, up_state, speedtest_ping)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700555 continue
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700556
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800557 report_status(signal_quality, dongle_stats, cp_state, up_state, speedtest_ping, speedtest_iperf)
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700558
559 modem.close()
560
561
562if __name__ == "__main__":
563 main()