wfb-ng/telemetry/cli.py
2019-03-26 18:06:38 +03:00

204 lines
6.9 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (C) 2018, 2019 Vasily Evseenko <svpcom@p2ptech.org>
#
# 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.
#
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import absolute_import
from future import standard_library
standard_library.install_aliases()
from builtins import *
import sys
import curses
import curses.textpad
import json
import tempfile
from twisted.python import log
from twisted.internet import reactor, defer
from twisted.internet.protocol import ReconnectingClientFactory
from twisted.protocols.basic import LineReceiver
from telemetry.common import abort_on_crash, exit_status
from telemetry.conf import settings
class AntennaStat(LineReceiver):
delimiter = b'\n'
def lineReceived(self, line):
attrs = json.loads(line)
p = attrs['packets']
rssi_d = attrs['rssi']
self.factory.window.clear()
self.factory.window.addstr(0, 0, 'TX ANT %02x**' % (attrs['tx_ant'],) if self.factory.has_tx else 'NO TX')
self.factory.window.addstr(1, 0, 'RX PKT: recv %d d_ok %d fec_r %d lost %d d_err %d bad %d\n' % \
(p['all'][1], p['dec_ok'][1], p['fec_rec'][1], p['lost'][1], p['dec_err'][1], p['bad'][1]))
msg_l = (('RX PKT/s: recv %d d_ok %d ' % (p['all'][0], p['dec_ok'][0]), 0),
('fec_r %d' % p['fec_rec'][0], curses.A_REVERSE if p['fec_rec'][0] else 0),
(' ', 0),
('lost %d' % p['lost'][0], curses.A_REVERSE if p['lost'][0] else 0),
(' ', 0),
('d_err %d' % p['dec_err'][0], curses.A_REVERSE if p['dec_err'][0] else 0),
(' ', 0),
('bad %d\n' % p['bad'][0], curses.A_REVERSE if p['bad'][0] else 0))
x = 0
xmax = self.factory.window.getmaxyx()[1]
for msg, attr in msg_l:
if x < xmax:
self.factory.window.addstr(2, x, msg, attr)
x += len(msg)
if rssi_d:
for i, (k, v) in enumerate(sorted(rssi_d.items())):
pkt_s, rssi_min, rssi_avg, rssi_max = v
self.factory.window.addstr(i + 4, 0, '%04x: %d pkt/s, rssi %d < %d < %d\n' % (int(k, 16), pkt_s, rssi_min, rssi_avg, rssi_max))
else:
self.factory.window.addstr(4, 0, 'Link lost!', curses.A_REVERSE)
self.factory.window.refresh()
class AntennaStatClientFactory(ReconnectingClientFactory):
noisy = False
maxDelay = 1.0
def __init__(self, window, has_tx):
self.window = window
self.has_tx = has_tx
def startedConnecting(self, connector):
log.msg('Connecting to %s:%d ...' % (connector.host, connector.port))
self.window.clear()
self.window.addstr(0, 0, 'Connecting...')
self.window.refresh()
def buildProtocol(self, addr):
log.msg('Connected to %s' % (addr,))
self.window.clear()
self.window.addstr(0, 0, 'Waiting for data...')
self.window.refresh()
self.resetDelay()
p = AntennaStat()
p.factory = self
return p
def clientConnectionLost(self, connector, reason):
log.msg('Connection lost: %s' % (reason.value,))
self.window.clear()
self.window.addstr(0, 0, 'Connection lost: %s' % (reason.value,))
self.window.refresh()
ReconnectingClientFactory.clientConnectionLost(self, connector, reason)
def clientConnectionFailed(self, connector, reason):
log.msg('Connection failed: %s' % (reason.value,))
self.window.clear()
self.window.addstr(0, 0, 'Connection failed: %s' % (reason.value,))
self.window.refresh()
ReconnectingClientFactory.clientConnectionFailed(self, connector, reason)
def init(stdscr, profile):
cfg_video = getattr(settings, '%s_video' % (profile,))
cfg_telem = getattr(settings, '%s_mavlink' % (profile,))
cfg_tunnel = getattr(settings, '%s_tunnel' % (profile,))
height, width = stdscr.getmaxyx()
height -= 1
w1h = height // 3
w1w = width
w2h = height // 3
w2w = width
w3h = height - w1h - w2h
w3w = width
status_win1 = stdscr.subpad(w1h - 2, w1w - 2, 1, 1)
status_win2 = stdscr.subpad(w2h - 2, w2w - 2, w1h + 1, 1)
status_win3 = stdscr.subpad(w3h - 2, w3w - 2, w1h + w2h + 1, 1)
curses.textpad.rectangle(stdscr, 0, 0, w1h - 1, w1w - 1)
curses.textpad.rectangle(stdscr, w1h, 0, w1h + w2h - 1, w2w - 1)
curses.textpad.rectangle(stdscr, w1h + w2h, 0, w1h + w2h + w3h - 1, w3w - 1)
stdscr.addstr(0, 3, '[video]')
stdscr.addstr(w1h, 3, '[telem]')
stdscr.addstr(w1h + w2h, 3, '[tunnel]')
stdscr.refresh()
for i in (status_win1, status_win2, status_win3):
i.idlok(1)
i.scrollok(1)
if cfg_video.stats_port is not None:
reactor.connectTCP('127.0.0.1', cfg_video.stats_port, AntennaStatClientFactory(status_win1, cfg_video.peer.startswith('listen:')))
else:
status_win1.addstr(0, 0, '[statistics disabled]', curses.A_REVERSE)
status_win1.refresh()
if cfg_telem.stats_port is not None:
reactor.connectTCP('127.0.0.1', cfg_telem.stats_port, AntennaStatClientFactory(status_win2, True))
else:
status_win2.addstr(0, 0, '[statistics disabled]', curses.A_REVERSE)
status_win2.refresh()
if cfg_tunnel.stats_port is not None:
reactor.connectTCP('127.0.0.1', cfg_tunnel.stats_port, AntennaStatClientFactory(status_win3, True))
else:
status_win3.addstr(0, 0, '[statistics disabled]', curses.A_REVERSE)
status_win3.refresh()
def main():
stderr = sys.stderr
if len(sys.argv) != 2:
print("Usage: %s <profile>" % (sys.argv[0],), file=stderr)
sys.exit(1)
fd = tempfile.TemporaryFile()
log.startLogging(fd)
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
curses.curs_set(0)
stdscr.keypad(1)
reactor.callWhenRunning(lambda: defer.maybeDeferred(init, stdscr, sys.argv[1])\
.addErrback(abort_on_crash))
reactor.run()
curses.endwin()
rc = exit_status()
if rc:
log.msg('Exiting with code %d' % rc)
fd.seek(0)
for l in fd:
stderr.write(l)
sys.exit(rc)
if __name__ == '__main__':
main()