2018-12-13 14:33:16 -04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2024-04-01 20:49:01 -03:00
|
|
|
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
2018-12-13 14:33:16 -04:00
|
|
|
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; version 3.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License along
|
|
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import curses
|
2024-04-01 20:49:01 -03:00
|
|
|
import msgpack
|
2018-12-13 14:33:16 -04:00
|
|
|
import tempfile
|
2024-03-24 10:00:09 -03:00
|
|
|
import signal
|
|
|
|
import termios
|
2024-04-08 14:28:14 -03:00
|
|
|
import struct
|
|
|
|
import fcntl
|
2018-12-13 14:33:16 -04:00
|
|
|
|
|
|
|
from twisted.python import log
|
|
|
|
from twisted.internet import reactor, defer
|
|
|
|
from twisted.internet.protocol import ReconnectingClientFactory
|
2024-04-01 20:49:01 -03:00
|
|
|
from twisted.protocols.basic import Int32StringReceiver
|
2023-07-05 06:43:53 -03:00
|
|
|
from .server import parse_services
|
2022-07-06 17:35:00 -03:00
|
|
|
from .common import abort_on_crash, exit_status
|
|
|
|
from .conf import settings
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2024-05-20 17:41:00 -03:00
|
|
|
_orig_stdout = sys.stdout
|
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
|
2024-05-20 17:41:00 -03:00
|
|
|
def set_window_title(s):
|
|
|
|
print("\033]2;%s\007" % (s,), file=_orig_stdout)
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2023-07-06 06:57:07 -03:00
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
def ignore_curses_err(f):
|
|
|
|
def _f(*args, **kwargs):
|
|
|
|
try:
|
|
|
|
return f(*args, **kwargs)
|
|
|
|
except curses.error:
|
|
|
|
pass
|
|
|
|
return _f
|
|
|
|
|
|
|
|
|
|
|
|
@ignore_curses_err
|
|
|
|
def addstr_noerr(window, y, x, s, *attrs):
|
|
|
|
for i, c in enumerate(s, x):
|
|
|
|
window.addch(y, i, c, *attrs)
|
2023-07-06 06:57:07 -03:00
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
|
|
|
|
def addstr_centered(window, s, attrs=0):
|
2024-05-20 17:41:00 -03:00
|
|
|
h, w = window.getmaxyx()
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_noerr(window, h // 2, max((w - len(s)) // 2, 0), s, attrs)
|
|
|
|
|
2024-05-20 17:41:00 -03:00
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
@ignore_curses_err
|
|
|
|
def addstr_markup(window, y, x, s, attrs=0):
|
2024-05-21 07:35:16 -03:00
|
|
|
for c in s:
|
|
|
|
if c == '{':
|
|
|
|
attrs |= curses.A_BOLD
|
|
|
|
continue
|
|
|
|
if c == '}':
|
|
|
|
attrs &= ~curses.A_BOLD
|
|
|
|
continue
|
|
|
|
if c == '(':
|
|
|
|
attrs |= curses.A_DIM
|
|
|
|
continue
|
|
|
|
if c == ')':
|
|
|
|
attrs &= ~curses.A_DIM
|
|
|
|
continue
|
|
|
|
if c == '^':
|
|
|
|
attrs |= curses.A_REVERSE
|
|
|
|
continue
|
|
|
|
if c == '$':
|
|
|
|
attrs &= ~curses.A_REVERSE
|
|
|
|
continue
|
|
|
|
|
|
|
|
window.addch(y, x, c, attrs)
|
|
|
|
x += 1
|
2023-07-06 06:57:07 -03:00
|
|
|
|
|
|
|
def rectangle(win, uly, ulx, lry, lrx):
|
|
|
|
"""Draw a rectangle with corners at the provided upper-left
|
|
|
|
and lower-right coordinates.
|
|
|
|
"""
|
|
|
|
win.vline(uly+1, ulx, curses.ACS_VLINE, lry - uly - 1)
|
|
|
|
win.hline(uly, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
|
|
|
|
win.hline(lry, ulx+1, curses.ACS_HLINE, lrx - ulx - 1)
|
|
|
|
win.vline(uly+1, lrx, curses.ACS_VLINE, lry - uly - 1)
|
|
|
|
win.addch(uly, ulx, curses.ACS_ULCORNER)
|
|
|
|
win.addch(uly, lrx, curses.ACS_URCORNER)
|
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
# Workarond for ncurses bug that show error on output to the last position on the screen
|
2024-03-24 10:00:09 -03:00
|
|
|
try:
|
2024-05-21 14:20:03 -03:00
|
|
|
win.addch(lry, lrx, curses.ACS_LRCORNER)
|
2024-03-24 10:00:09 -03:00
|
|
|
except curses.error:
|
|
|
|
pass
|
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
win.addch(lry, ulx, curses.ACS_LLCORNER)
|
2024-03-24 10:00:09 -03:00
|
|
|
|
2024-05-20 17:41:00 -03:00
|
|
|
|
|
|
|
def human_rate(r):
|
|
|
|
rate = r * 8
|
|
|
|
|
2024-05-21 07:35:16 -03:00
|
|
|
if rate >= 1000 * 1000:
|
2024-05-20 17:41:00 -03:00
|
|
|
rate = rate / 1024 / 1024
|
|
|
|
mod = 'mbit/s'
|
|
|
|
else:
|
|
|
|
rate = rate / 1024
|
|
|
|
mod = 'kbit/s'
|
|
|
|
|
|
|
|
if rate < 10:
|
|
|
|
return '%0.1f %s' % (rate, mod)
|
|
|
|
else:
|
|
|
|
return '%3d %s' % (rate, mod)
|
|
|
|
|
|
|
|
|
2024-04-01 20:49:01 -03:00
|
|
|
class AntennaStat(Int32StringReceiver):
|
|
|
|
MAX_LENGTH = 1024 * 1024
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2024-04-01 20:49:01 -03:00
|
|
|
def stringReceived(self, string):
|
|
|
|
attrs = msgpack.unpackb(string, strict_map_key=False, use_list=False)
|
2024-03-21 13:57:19 -03:00
|
|
|
|
|
|
|
if attrs['type'] == 'rx':
|
|
|
|
self.draw_rx(attrs)
|
|
|
|
elif attrs['type'] == 'tx':
|
|
|
|
self.draw_tx(attrs)
|
2024-05-20 17:41:00 -03:00
|
|
|
elif attrs['type'] == 'cli_title':
|
|
|
|
set_window_title(attrs['cli_title'])
|
2024-03-21 13:57:19 -03:00
|
|
|
|
|
|
|
def draw_rx(self, attrs):
|
2018-12-13 14:33:16 -04:00
|
|
|
p = attrs['packets']
|
2024-05-20 17:41:00 -03:00
|
|
|
session_d = attrs['session']
|
2024-04-01 20:49:01 -03:00
|
|
|
stats_d = attrs['rx_ant_stats']
|
2023-07-05 06:43:53 -03:00
|
|
|
tx_ant = attrs.get('tx_ant')
|
|
|
|
rx_id = attrs['id']
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2023-07-05 06:43:53 -03:00
|
|
|
window = self.factory.windows.get(rx_id)
|
|
|
|
if window is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
window.erase()
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_markup(window, 0, 0, ' {pkt/s pkt}')
|
2022-02-03 06:42:17 -04:00
|
|
|
|
2024-05-21 07:35:16 -03:00
|
|
|
msg_l = (('{recv} %4d$ (%d)' % tuple(p['all']), 0),
|
|
|
|
('{udp} %4d$ (%d)' % tuple(p['out']), 0),
|
|
|
|
('fec_r %4d$ (%d)' % tuple(p['fec_rec']), curses.A_REVERSE if p['fec_rec'][0] else 0),
|
|
|
|
('lost %4d$ (%d)' % tuple(p['lost']), curses.A_REVERSE if p['lost'][0] else 0),
|
|
|
|
('d_err %4d$ (%d)' % tuple(p['dec_err']), curses.A_REVERSE if p['dec_err'][0] else 0),
|
|
|
|
('bad %4d$ (%d)' % tuple(p['bad']), curses.A_REVERSE if p['bad'][0] else 0))
|
2022-02-03 06:42:17 -04:00
|
|
|
|
2023-07-05 06:43:53 -03:00
|
|
|
ymax = window.getmaxyx()[0]
|
2022-02-03 06:42:17 -04:00
|
|
|
for y, (msg, attr) in enumerate(msg_l, 1):
|
|
|
|
if y < ymax:
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_markup(window, y, 0, msg, attr)
|
|
|
|
|
2024-05-21 07:35:16 -03:00
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
flow_str = '{Flow:} %s -> %s ' % \
|
|
|
|
(human_rate(p['all_bytes'][0]),
|
|
|
|
human_rate(p['out_bytes'][0]))
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2024-05-20 17:41:00 -03:00
|
|
|
if session_d:
|
2024-05-21 14:20:03 -03:00
|
|
|
flow_str += '{FEC:} %(fec_k)d/%(fec_n)d' % (session_d)
|
2024-05-20 17:41:00 -03:00
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_markup(window, 0, 20, flow_str)
|
2024-05-20 17:41:00 -03:00
|
|
|
|
2024-04-01 20:49:01 -03:00
|
|
|
if stats_d:
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_markup(window, 2, 20, '{Freq MCS BW [ANT] pkt/s} {RSSI} [dBm] {SNR} [dB]')
|
2024-05-20 17:41:00 -03:00
|
|
|
for y, (((freq, mcs_index, bandwith), ant_id), v) in enumerate(sorted(stats_d.items()), 3):
|
2024-04-01 20:49:01 -03:00
|
|
|
pkt_s, rssi_min, rssi_avg, rssi_max, snr_min, snr_avg, snr_max = v
|
2022-02-03 06:42:17 -04:00
|
|
|
if y < ymax:
|
2024-05-21 07:35:16 -03:00
|
|
|
active_tx = (ant_id >> 8) == tx_ant
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_markup(window, y, 20, '%04d %3d %2d %s%04x%s %4d %3d < {%3d} < %3d %3d < {%3d} < %3d' % \
|
2024-05-21 07:51:53 -03:00
|
|
|
(freq, mcs_index, bandwith,
|
|
|
|
'{' if active_tx else '', ant_id, '}' if active_tx else '',
|
|
|
|
pkt_s,
|
2024-04-01 20:49:01 -03:00
|
|
|
rssi_min, rssi_avg, rssi_max,
|
2024-05-21 07:35:16 -03:00
|
|
|
snr_min, snr_avg, snr_max), 0 if active_tx else curses.A_DIM)
|
2018-12-13 14:33:16 -04:00
|
|
|
else:
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_noerr(window, 2, 20, '[No data]', curses.A_REVERSE)
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2023-07-05 06:43:53 -03:00
|
|
|
window.refresh()
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2024-03-21 13:57:19 -03:00
|
|
|
def draw_tx(self, attrs):
|
|
|
|
p = attrs['packets']
|
|
|
|
latency_d = attrs['latency']
|
|
|
|
tx_id = attrs['id']
|
2024-05-28 17:24:20 -03:00
|
|
|
rf_temperature = attrs['rf_temperature']
|
2024-03-21 13:57:19 -03:00
|
|
|
|
|
|
|
window = self.factory.windows.get(tx_id)
|
|
|
|
if window is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
window.erase()
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_noerr(window, 0, 0, ' pkt/s pkt', curses.A_BOLD)
|
2024-03-21 13:57:19 -03:00
|
|
|
|
2024-05-21 07:35:16 -03:00
|
|
|
msg_l = (('{sent} %4d$ (%d)' % tuple(p['injected']), 0),
|
|
|
|
('{udp} %4d$ (%d)' % tuple(p['incoming']), 0),
|
|
|
|
('fec_t %4d$ (%d)' % tuple(p['fec_timeouts']), 0),
|
|
|
|
('drop %4d$ (%d)' % tuple(p['dropped']), curses.A_REVERSE if p['dropped'][0] else 0),
|
|
|
|
('trunc %4d$ (%d)' % tuple(p['truncated']), curses.A_REVERSE if p['truncated'][0] else 0))
|
2024-03-21 13:57:19 -03:00
|
|
|
|
|
|
|
ymax = window.getmaxyx()[0]
|
|
|
|
for y, (msg, attr) in enumerate(msg_l, 1):
|
|
|
|
if y < ymax:
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_markup(window, y, 0, msg, attr)
|
2024-03-21 13:57:19 -03:00
|
|
|
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_markup(window, 0, 20,
|
|
|
|
'{Flow:} %s -> %s' % \
|
|
|
|
(human_rate(p['incoming_bytes'][0]),
|
|
|
|
human_rate(p['injected_bytes'][0])))
|
2024-05-20 17:41:00 -03:00
|
|
|
|
2024-03-21 13:57:19 -03:00
|
|
|
if latency_d:
|
2024-05-28 17:24:20 -03:00
|
|
|
addstr_markup(window, 2, 20, '{[ANT] pkt/s} {\u00b0C} {Injection} [us]')
|
2024-05-20 17:41:00 -03:00
|
|
|
for y, (k, v) in enumerate(sorted(latency_d.items()), 3):
|
2024-04-01 20:49:01 -03:00
|
|
|
k = int(k) # json doesn't support int keys
|
2024-03-21 13:57:19 -03:00
|
|
|
injected, dropped, lat_min, lat_avg, lat_max = v
|
2024-05-28 17:24:20 -03:00
|
|
|
|
|
|
|
# Show max temperature from all RF paths
|
|
|
|
temp = max((_v for _k, _v in rf_temperature.items() if (_k >> 8) == (k >> 8)), default=None)
|
|
|
|
if temp is not None:
|
|
|
|
if temp >= settings.common.temp_overheat_warning:
|
|
|
|
temp = '{%d}' % (temp,)
|
|
|
|
else:
|
|
|
|
temp = str(temp)
|
|
|
|
else:
|
|
|
|
temp = ' (--)'
|
|
|
|
|
2024-03-21 13:57:19 -03:00
|
|
|
if y < ymax:
|
2024-05-28 17:24:20 -03:00
|
|
|
addstr_markup(window, y, 21, '{%02x}(XX) %4d %3s %4d < {%4d} < %4d' % (k >> 8, injected, temp, lat_min, lat_avg, lat_max))
|
2024-03-21 13:57:19 -03:00
|
|
|
else:
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_noerr(window, 2, 20, '[No data]', curses.A_REVERSE)
|
2024-03-21 13:57:19 -03:00
|
|
|
|
|
|
|
|
|
|
|
window.refresh()
|
2018-12-13 14:33:16 -04:00
|
|
|
|
|
|
|
|
|
|
|
class AntennaStatClientFactory(ReconnectingClientFactory):
|
|
|
|
noisy = False
|
|
|
|
maxDelay = 1.0
|
|
|
|
|
2024-03-24 10:00:09 -03:00
|
|
|
def __init__(self, stdscr, profile):
|
|
|
|
self.stdscr = stdscr
|
|
|
|
self.profile = profile
|
|
|
|
self.windows = {}
|
|
|
|
self.init_windows()
|
|
|
|
|
|
|
|
def init_windows(self):
|
|
|
|
self.windows.clear()
|
2024-04-08 14:28:14 -03:00
|
|
|
# python < 3.11 doesn't have termios.tcgetwinsize
|
|
|
|
height, width = struct.unpack('hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b' ' * 4))
|
2024-03-24 10:00:09 -03:00
|
|
|
curses.resize_term(height, width)
|
|
|
|
self.stdscr.clear()
|
|
|
|
|
2024-05-20 17:41:00 -03:00
|
|
|
service_list = list((s_name, cfg.stream_rx is not None, cfg.stream_tx is not None) for s_name, _, cfg in parse_services(self.profile))
|
2024-03-24 10:00:09 -03:00
|
|
|
|
|
|
|
if not service_list:
|
|
|
|
rectangle(self.stdscr, 0, 0, height - 1, width - 1)
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_noerr(self.stdscr, 0, 3, '[%s not configured]' % (self.profile,), curses.A_REVERSE)
|
2024-03-24 10:00:09 -03:00
|
|
|
self.stdscr.refresh()
|
|
|
|
return
|
|
|
|
|
|
|
|
n_exp = 0
|
|
|
|
h_exp = height
|
|
|
|
h_fixed = 3
|
|
|
|
|
|
|
|
for _, show_rx_stats, show_tx_stats in service_list:
|
|
|
|
if show_rx_stats or show_tx_stats:
|
|
|
|
n_exp += 1
|
|
|
|
else:
|
|
|
|
h_exp -= h_fixed
|
|
|
|
|
|
|
|
if n_exp > 0:
|
|
|
|
h_exp = h_exp / n_exp
|
|
|
|
|
|
|
|
hoff_int = 0
|
|
|
|
hoff_float = 0
|
|
|
|
|
|
|
|
for name, show_rx_stats, show_tx_stats in service_list:
|
|
|
|
if show_rx_stats or show_tx_stats:
|
|
|
|
hoff_float += h_exp
|
|
|
|
else:
|
|
|
|
hoff_float += h_fixed
|
|
|
|
|
|
|
|
whl = []
|
2024-05-20 17:41:00 -03:00
|
|
|
for ww, xoff, txrx, show_stats in [((width * 4 // 7 - 1), 0, 'rx', show_rx_stats),
|
|
|
|
((width - width * 4 // 7 - 1), width * 4 // 7, 'tx', show_tx_stats)]:
|
|
|
|
if not show_stats:
|
|
|
|
whl.append(0)
|
|
|
|
continue
|
|
|
|
|
|
|
|
err = round(hoff_float) - (hoff_int + int(h_exp))
|
|
|
|
wh = int(h_exp) + err
|
|
|
|
if wh < h_fixed:
|
|
|
|
raise Exception('Terminal height is too small')
|
2024-03-24 10:00:09 -03:00
|
|
|
|
|
|
|
window = self.stdscr.subpad(wh - 2, ww - 2, hoff_int + 1, xoff + 1)
|
|
|
|
window.idlok(1)
|
|
|
|
window.scrollok(1)
|
|
|
|
|
|
|
|
rectangle(self.stdscr, hoff_int, xoff, hoff_int + wh - 1, xoff + ww)
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_noerr(self.stdscr, hoff_int, 3 + xoff, '[%s: %s %s]' % (txrx.upper(), self.profile, name), curses.A_BOLD)
|
2024-03-24 10:00:09 -03:00
|
|
|
|
2024-05-20 17:41:00 -03:00
|
|
|
self.windows['%s %s' % (name, txrx)] = window
|
2024-03-24 10:00:09 -03:00
|
|
|
whl.append(wh)
|
2024-05-20 17:41:00 -03:00
|
|
|
|
2024-03-24 10:00:09 -03:00
|
|
|
hoff_int += max(whl)
|
|
|
|
self.stdscr.refresh()
|
2018-12-13 14:33:16 -04:00
|
|
|
|
|
|
|
def startedConnecting(self, connector):
|
2024-05-20 17:41:00 -03:00
|
|
|
set_window_title('Connecting to %s:%d ...' % (connector.host, connector.port))
|
2023-07-05 06:43:53 -03:00
|
|
|
|
|
|
|
for window in self.windows.values():
|
|
|
|
window.erase()
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_centered(window, 'Connecting...', curses.A_DIM)
|
2023-07-05 06:43:53 -03:00
|
|
|
window.refresh()
|
2018-12-13 14:33:16 -04:00
|
|
|
|
|
|
|
def buildProtocol(self, addr):
|
2024-05-20 17:41:00 -03:00
|
|
|
set_window_title('Connected to %s' % (addr,))
|
2023-07-05 06:43:53 -03:00
|
|
|
|
|
|
|
for window in self.windows.values():
|
|
|
|
window.erase()
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_centered(window, 'Waiting for data...', curses.A_DIM)
|
2023-07-05 06:43:53 -03:00
|
|
|
window.refresh()
|
|
|
|
|
2018-12-13 14:33:16 -04:00
|
|
|
self.resetDelay()
|
|
|
|
p = AntennaStat()
|
|
|
|
p.factory = self
|
|
|
|
return p
|
|
|
|
|
|
|
|
def clientConnectionLost(self, connector, reason):
|
2024-05-20 17:41:00 -03:00
|
|
|
set_window_title('Connection lost: %s' % (reason.value,))
|
2023-07-05 06:43:53 -03:00
|
|
|
|
|
|
|
for window in self.windows.values():
|
|
|
|
window.erase()
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_centered(window, '[Connection lost]', curses.A_REVERSE)
|
2023-07-05 06:43:53 -03:00
|
|
|
window.refresh()
|
|
|
|
|
2018-12-13 14:33:16 -04:00
|
|
|
ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
|
|
|
|
|
|
|
|
def clientConnectionFailed(self, connector, reason):
|
2024-05-20 17:41:00 -03:00
|
|
|
set_window_title('Connection failed: %s' % (reason.value,))
|
2023-07-05 06:43:53 -03:00
|
|
|
|
|
|
|
for window in self.windows.values():
|
|
|
|
window.erase()
|
2024-05-21 14:20:03 -03:00
|
|
|
addstr_centered(window, '[Connection failed]', curses.A_REVERSE)
|
2023-07-05 06:43:53 -03:00
|
|
|
window.refresh()
|
|
|
|
|
2018-12-13 14:33:16 -04:00
|
|
|
ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
|
|
|
|
|
|
|
|
|
|
|
|
def init(stdscr, profile):
|
2024-03-24 10:00:09 -03:00
|
|
|
stats_port = getattr(settings, profile).stats_port
|
|
|
|
f = AntennaStatClientFactory(stdscr, profile)
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2024-03-24 10:00:09 -03:00
|
|
|
# Resize windows on terminal size change
|
|
|
|
def sigwinch_handler(signum, sigstack):
|
|
|
|
reactor.callFromThread(lambda: defer.maybeDeferred(f.init_windows).addErrback(abort_on_crash))
|
2018-12-13 14:33:16 -04:00
|
|
|
|
2024-03-24 10:00:09 -03:00
|
|
|
signal.signal(signal.SIGWINCH, sigwinch_handler)
|
|
|
|
reactor.connectTCP('127.0.0.1', stats_port, f)
|
2024-03-21 13:57:19 -03:00
|
|
|
|
2023-07-05 06:43:53 -03:00
|
|
|
|
2018-12-13 14:33:16 -04:00
|
|
|
|
|
|
|
def main():
|
|
|
|
stderr = sys.stderr
|
|
|
|
|
|
|
|
if len(sys.argv) != 2:
|
2019-03-26 12:02:27 -03:00
|
|
|
print("Usage: %s <profile>" % (sys.argv[0],), file=stderr)
|
2018-12-13 14:33:16 -04:00
|
|
|
sys.exit(1)
|
|
|
|
|
2022-02-03 11:34:31 -04:00
|
|
|
fd = tempfile.TemporaryFile(mode='w+', encoding='utf-8')
|
2018-12-13 14:33:16 -04:00
|
|
|
log.startLogging(fd)
|
|
|
|
|
|
|
|
stdscr = curses.initscr()
|
2022-02-03 11:34:31 -04:00
|
|
|
try:
|
|
|
|
curses.noecho()
|
|
|
|
curses.cbreak()
|
|
|
|
curses.curs_set(0)
|
|
|
|
stdscr.keypad(True)
|
|
|
|
reactor.callWhenRunning(lambda: defer.maybeDeferred(init, stdscr, sys.argv[1])\
|
2018-12-13 14:33:16 -04:00
|
|
|
.addErrback(abort_on_crash))
|
2022-02-03 11:34:31 -04:00
|
|
|
reactor.run()
|
|
|
|
finally:
|
|
|
|
curses.endwin()
|
|
|
|
|
2018-12-13 14:33:16 -04:00
|
|
|
rc = exit_status()
|
|
|
|
|
|
|
|
if rc:
|
|
|
|
log.msg('Exiting with code %d' % rc)
|
2023-07-06 06:57:07 -03:00
|
|
|
|
|
|
|
fd.seek(0)
|
|
|
|
for l in fd:
|
|
|
|
stderr.write(l)
|
2018-12-13 14:33:16 -04:00
|
|
|
|
|
|
|
sys.exit(rc)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|