From df43c1a2700234897fad471b4ba4223c69e225d0 Mon Sep 17 00:00:00 2001 From: Vasily Evseenko Date: Tue, 2 Apr 2024 02:49:01 +0300 Subject: [PATCH] Add SNR and frequency reporting to RX. Code refactoring. --- src/keygen.c | 2 +- src/rx.cpp | 50 +++++++++++++++------- src/rx.hpp | 50 ++++++++++++++++++---- src/tx.cpp | 2 +- src/tx.hpp | 2 +- src/wifibroadcast.cpp | 2 +- src/wifibroadcast.hpp | 4 +- stdeb.cfg | 2 +- wfb_ng/cli.py | 34 ++++++++------- wfb_ng/common.py | 2 +- wfb_ng/config_parser.py | 2 +- wfb_ng/latency_test.py | 2 +- wfb_ng/mavlink_protocol.py | 2 +- wfb_ng/proxy.py | 2 +- wfb_ng/server.py | 85 ++++++++++++++++++++++++++------------ wfb_ng/tuntap.py | 2 +- 16 files changed, 168 insertions(+), 77 deletions(-) diff --git a/src/keygen.c b/src/keygen.c index 9954973..2108115 100644 --- a/src/keygen.c +++ b/src/keygen.c @@ -1,6 +1,6 @@ /* -*- c -*- */ -// Copyright (C) 2017 - 2022 Vasily Evseenko +// Copyright (C) 2017 - 2024 Vasily Evseenko /* * This program is free software; you can redistribute it and/or modify diff --git a/src/rx.cpp b/src/rx.cpp index fe340ea..4d53950 100644 --- a/src/rx.cpp +++ b/src/rx.cpp @@ -1,6 +1,6 @@ // -*- C++ -*- // -// Copyright (C) 2017 - 2022 Vasily Evseenko +// Copyright (C) 2017 - 2024 Vasily Evseenko /* * This program is free software; you can redistribute it and/or modify @@ -111,8 +111,10 @@ void Receiver::loop_iter(void) int pktlen = hdr.caplen; // int pkt_rate = 0 int ant_idx = 0; + uint32_t freq = 0; uint8_t antenna[RX_ANT_MAX]; int8_t rssi[RX_ANT_MAX]; + int8_t noise[RX_ANT_MAX]; uint8_t flags = 0; bool self_injected = false; struct ieee80211_radiotap_iterator iterator; @@ -122,6 +124,8 @@ void Receiver::loop_iter(void) memset(antenna, 0xff, sizeof(antenna)); // Fill all rssi slots with minimum value memset(rssi, SCHAR_MIN, sizeof(rssi)); + // Fill all noise slots with maximum value + memset(noise, SCHAR_MAX, sizeof(noise)); while (ret == 0 && ant_idx < RX_ANT_MAX) { ret = ieee80211_radiotap_iterator_next(&iterator); @@ -145,11 +149,19 @@ void Receiver::loop_iter(void) ant_idx += 1; break; + case IEEE80211_RADIOTAP_CHANNEL: + // drop channel flags - they are redundant for freq to chan convertion + freq = le32toh(*(uint32_t*)(iterator.this_arg)) & 0xffff; + break; + case IEEE80211_RADIOTAP_DBM_ANTSIGNAL: - // Some cards can provide rssi for multiple antennas in one packet, so we should select maximum value rssi[ant_idx] = *(int8_t*)(iterator.this_arg); break; + case IEEE80211_RADIOTAP_DBM_ANTNOISE: + noise[ant_idx] = *(int8_t*)(iterator.this_arg); + break; + case IEEE80211_RADIOTAP_FLAGS: flags = *(uint8_t*)(iterator.this_arg); break; @@ -191,7 +203,7 @@ void Receiver::loop_iter(void) if (pktlen > (int)sizeof(ieee80211_header)) { - agg->process_packet(pkt + sizeof(ieee80211_header), pktlen - sizeof(ieee80211_header), wlan_idx, antenna, rssi, NULL); + agg->process_packet(pkt + sizeof(ieee80211_header), pktlen - sizeof(ieee80211_header), wlan_idx, antenna, rssi, noise, freq, NULL); } else { fprintf(stderr, "Short packet (ieee header)\n"); continue; @@ -295,12 +307,14 @@ Forwarder::Forwarder(const string &client_addr, int client_port) } -void Forwarder::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, sockaddr_in *sockaddr) +void Forwarder::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, const int8_t *noise, uint16_t freq, sockaddr_in *sockaddr) { - wrxfwd_t fwd_hdr = { .wlan_idx = wlan_idx }; + wrxfwd_t fwd_hdr = { .wlan_idx = wlan_idx, + .freq = freq }; memcpy(fwd_hdr.antenna, antenna, RX_ANT_MAX * sizeof(uint8_t)); memcpy(fwd_hdr.rssi, rssi, RX_ANT_MAX * sizeof(int8_t)); + memcpy(fwd_hdr.noise, noise, RX_ANT_MAX * sizeof(int8_t)); struct iovec iov[2] = {{ .iov_base = (void*)&fwd_hdr, .iov_len = sizeof(fwd_hdr)}, @@ -400,7 +414,10 @@ void Aggregator::dump_stats(FILE *fp) for(rx_antenna_stat_t::iterator it = antenna_stat.begin(); it != antenna_stat.end(); it++) { - fprintf(fp, "%" PRIu64 "\tRX_ANT\t%" PRIx64 "\t%d:%d:%d:%d\n", ts, it->first, it->second.count_all, it->second.rssi_min, it->second.rssi_sum / it->second.count_all, it->second.rssi_max); + fprintf(fp, "%" PRIu64 "\tRX_ANT\t%u\t%" PRIx64 "\t%d" ":%d:%d:%d" ":%d:%d:%d\n", + ts, it->first.freq, it->first.antenna_id, it->second.count_all, + it->second.rssi_min, it->second.rssi_sum / it->second.count_all, it->second.rssi_max, + it->second.snr_min, it->second.snr_sum / it->second.count_all, it->second.snr_max); } antenna_stat.clear(); @@ -428,25 +445,26 @@ void Aggregator::dump_stats(FILE *fp) } -void Aggregator::log_rssi(const sockaddr_in *sockaddr, uint8_t wlan_idx, const uint8_t *ant, const int8_t *rssi) +void Aggregator::log_rssi(const sockaddr_in *sockaddr, uint8_t wlan_idx, const uint8_t *ant, const int8_t *rssi, const int8_t *noise, uint16_t freq) { for(int i = 0; i < RX_ANT_MAX && ant[i] != 0xff; i++) { - // key: addr + port + wlan_idx + ant - uint64_t key = 0; + // antenna_id: addr + port + wlan_idx + ant + rxAntennaKey key = {.freq = freq, .antenna_id = 0}; + if (sockaddr != NULL && sockaddr->sin_family == AF_INET) { - key = ((uint64_t)ntohl(sockaddr->sin_addr.s_addr) << 32 | (uint64_t)ntohs(sockaddr->sin_port) << 16); + key.antenna_id = ((uint64_t)ntohl(sockaddr->sin_addr.s_addr) << 32 | (uint64_t)ntohs(sockaddr->sin_port) << 16); } - key |= ((uint64_t)wlan_idx << 8 | (uint64_t)ant[i]); + key.antenna_id |= ((uint64_t)wlan_idx << 8 | (uint64_t)ant[i]); - antenna_stat[key].log_rssi(rssi[i]); + antenna_stat[key].log_rssi(rssi[i], noise[i]); } } -void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, sockaddr_in *sockaddr) +void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, const int8_t *noise, uint16_t freq, sockaddr_in *sockaddr) { wsession_data_t new_session_data; count_p_all += 1; @@ -526,7 +544,7 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id } count_p_dec_ok += 1; - log_rssi(sockaddr, wlan_idx, antenna, rssi); + log_rssi(sockaddr, wlan_idx, antenna, rssi, noise, freq); if (memcmp(session_key, new_session_data.session_key, sizeof(session_key)) != 0) { @@ -569,7 +587,7 @@ void Aggregator::process_packet(const uint8_t *buf, size_t size, uint8_t wlan_id } count_p_dec_ok += 1; - log_rssi(sockaddr, wlan_idx, antenna, rssi); + log_rssi(sockaddr, wlan_idx, antenna, rssi, noise, freq); assert(decrypted_len <= MAX_FEC_PAYLOAD); @@ -872,7 +890,7 @@ void network_loop(int srv_port, Aggregator &agg, int log_interval, int rcv_buf_s fprintf(stderr, "Short packet (rx fwd header)\n"); continue; } - agg.process_packet(buf, rsize - sizeof(wrxfwd_t), fwd_hdr.wlan_idx, fwd_hdr.antenna, fwd_hdr.rssi, &sockaddr); + agg.process_packet(buf, rsize - sizeof(wrxfwd_t), fwd_hdr.wlan_idx, fwd_hdr.antenna, fwd_hdr.rssi, fwd_hdr.noise, fwd_hdr.freq, &sockaddr); } if(errno != EWOULDBLOCK) throw runtime_error(string_format("Error receiving packet: %s", strerror(errno))); } diff --git a/src/rx.hpp b/src/rx.hpp index 5ba9a52..84dc109 100644 --- a/src/rx.hpp +++ b/src/rx.hpp @@ -1,6 +1,6 @@ // -*- C++ -*- // -// Copyright (C) 2017 - 2022 Vasily Evseenko +// Copyright (C) 2017 - 2024 Vasily Evseenko /* * This program is free software; you can redistribute it and/or modify @@ -39,7 +39,7 @@ typedef enum { class BaseAggregator { public: - virtual void process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, sockaddr_in *sockaddr) = 0; + virtual void process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, const int8_t *noise, uint16_t freq, sockaddr_in *sockaddr) = 0; virtual void dump_stats(FILE *fp) = 0; protected: int open_udp_socket_for_tx(const std::string &client_addr, int client_port) @@ -67,7 +67,7 @@ class Forwarder : public BaseAggregator public: Forwarder(const std::string &client_addr, int client_port); ~Forwarder(); - virtual void process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, sockaddr_in *sockaddr); + virtual void process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, const int8_t *noise, uint16_t freq, sockaddr_in *sockaddr); virtual void dump_stats(FILE *) {} private: int sockfd; @@ -93,17 +93,26 @@ static inline int modN(int x, int base) class rxAntennaItem { public: - rxAntennaItem(void) : count_all(0), rssi_sum(0), rssi_min(0), rssi_max(0) {} + rxAntennaItem(void) : count_all(0), + rssi_sum(0), rssi_min(0), rssi_max(0), + snr_sum(0), snr_min(0), snr_max(0) {} + + void log_rssi(int8_t rssi, int8_t noise){ + int8_t snr = (noise != SCHAR_MAX) ? rssi - noise : 0; - void log_rssi(int8_t rssi){ if(count_all == 0){ rssi_min = rssi; rssi_max = rssi; + snr_min = snr; + snr_max = snr; } else { rssi_min = std::min(rssi, rssi_min); rssi_max = std::max(rssi, rssi_max); + snr_min = std::min(snr, snr_min); + snr_max = std::max(snr, snr_max); } rssi_sum += rssi; + snr_sum += snr; count_all += 1; } @@ -111,23 +120,48 @@ public: int32_t rssi_sum; int8_t rssi_min; int8_t rssi_max; + int32_t snr_sum; + int8_t snr_min; + int8_t snr_max; }; -typedef std::unordered_map rx_antenna_stat_t; +struct rxAntennaKey +{ + uint16_t freq; + uint64_t antenna_id; + + bool operator==(const rxAntennaKey &other) const + { + return (freq == other.freq && antenna_id == other.antenna_id); + } +}; + +template<> +struct std::hash +{ + std::size_t operator()(const rxAntennaKey& k) const noexcept + { + std::size_t h1 = std::hash{}(k.freq); + std::size_t h2 = std::hash{}(k.antenna_id); + return h1 ^ (h2 << 1); // combine hashes + } +}; + +typedef std::unordered_map rx_antenna_stat_t; class Aggregator : public BaseAggregator { public: Aggregator(const std::string &client_addr, int client_port, const std::string &keypair, uint64_t epoch, uint32_t channel_id); ~Aggregator(); - virtual void process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, sockaddr_in *sockaddr); + virtual void process_packet(const uint8_t *buf, size_t size, uint8_t wlan_idx, const uint8_t *antenna, const int8_t *rssi, const int8_t *noise, uint16_t freq, sockaddr_in *sockaddr); virtual void dump_stats(FILE *fp); private: void init_fec(int k, int n); void deinit_fec(void); void send_packet(int ring_idx, int fragment_idx); void apply_fec(int ring_idx); - void log_rssi(const sockaddr_in *sockaddr, uint8_t wlan_idx, const uint8_t *ant, const int8_t *rssi); + void log_rssi(const sockaddr_in *sockaddr, uint8_t wlan_idx, const uint8_t *ant, const int8_t *rssi, const int8_t *noise, uint16_t freq); int get_block_ring_idx(uint64_t block_idx); int rx_ring_push(void); fec_t* fec_p; diff --git a/src/tx.cpp b/src/tx.cpp index 7d24b22..36874ec 100644 --- a/src/tx.cpp +++ b/src/tx.cpp @@ -1,6 +1,6 @@ // -*- C++ -*- // -// Copyright (C) 2017 - 2022 Vasily Evseenko +// Copyright (C) 2017 - 2024 Vasily Evseenko /* * This program is free software; you can redistribute it and/or modify diff --git a/src/tx.hpp b/src/tx.hpp index 4cf7423..9ed9baa 100644 --- a/src/tx.hpp +++ b/src/tx.hpp @@ -1,6 +1,6 @@ // -*- C++ -*- // -// Copyright (C) 2017 - 2022 Vasily Evseenko +// Copyright (C) 2017 - 2024 Vasily Evseenko /* * This program is free software; you can redistribute it and/or modify diff --git a/src/wifibroadcast.cpp b/src/wifibroadcast.cpp index ed59aeb..bcda53f 100644 --- a/src/wifibroadcast.cpp +++ b/src/wifibroadcast.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2017 - 2022 Vasily Evseenko +// Copyright (C) 2017 - 2024 Vasily Evseenko /* * This program is free software; you can redistribute it and/or modify diff --git a/src/wifibroadcast.hpp b/src/wifibroadcast.hpp index 86eb141..63a3323 100644 --- a/src/wifibroadcast.hpp +++ b/src/wifibroadcast.hpp @@ -1,4 +1,4 @@ -// Copyright (C) 2017 - 2022 Vasily Evseenko +// Copyright (C) 2017 - 2024 Vasily Evseenko /* * This program is free software; you can redistribute it and/or modify @@ -149,6 +149,8 @@ typedef struct { uint8_t wlan_idx; uint8_t antenna[RX_ANT_MAX]; //RADIOTAP_ANTENNA, list of antenna idx, 0xff for unused slot int8_t rssi[RX_ANT_MAX]; //RADIOTAP_DBM_ANTSIGNAL, list of rssi for corresponding antenna idx + int8_t noise[RX_ANT_MAX]; //RADIOTAP_DBM_ANTNOISE, list of (rssi - snr) for corresponding antenna idx + uint16_t freq; //IEEE80211_RADIOTAP_CHANNEL -- channel frequency in MHz } __attribute__ ((packed)) wrxfwd_t; // Network packet headers. All numbers are in network (big endian) format diff --git a/stdeb.cfg b/stdeb.cfg index 0ba7419..279676c 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,5 +1,5 @@ [DEFAULT] -Depends3: python3-twisted, libpcap-dev, libsodium-dev, python3-pyroute2, python3-future, python3-serial +Depends3: python3-twisted, libpcap-dev, libsodium-dev, python3-pyroute2, python3-future, python3-serial, python3-msgpack Package3: wfb-ng Replaces3: wifibroadcast Maintainer: Vasily Evseenko diff --git a/wfb_ng/cli.py b/wfb_ng/cli.py index 86fac96..a68ad88 100644 --- a/wfb_ng/cli.py +++ b/wfb_ng/cli.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018-2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify @@ -20,7 +20,7 @@ import sys import curses -import json +import msgpack import tempfile import signal import termios @@ -28,7 +28,7 @@ import termios from twisted.python import log from twisted.internet import reactor, defer from twisted.internet.protocol import ReconnectingClientFactory -from twisted.protocols.basic import LineReceiver +from twisted.protocols.basic import Int32StringReceiver from .server import parse_services from .common import abort_on_crash, exit_status from .conf import settings @@ -65,11 +65,11 @@ def addstr(window, y, x, s, *attrs): pass -class AntennaStat(LineReceiver): - delimiter = b'\n' +class AntennaStat(Int32StringReceiver): + MAX_LENGTH = 1024 * 1024 - def lineReceived(self, line): - attrs = json.loads(line) + def stringReceived(self, string): + attrs = msgpack.unpackb(string, strict_map_key=False, use_list=False) if attrs['type'] == 'rx': self.draw_rx(attrs) @@ -78,7 +78,7 @@ class AntennaStat(LineReceiver): def draw_rx(self, attrs): p = attrs['packets'] - rssi_d = attrs['rssi'] + stats_d = attrs['rx_ant_stats'] tx_ant = attrs.get('tx_ant') rx_id = attrs['id'] @@ -101,13 +101,16 @@ class AntennaStat(LineReceiver): if y < ymax: addstr(window, y, 0, msg, attr) - if rssi_d: - addstr(window, 0, 25, '[ANT] pkt/s RSSI [dBm]') - for y, (k, v) in enumerate(sorted(rssi_d.items()), 1): - pkt_s, rssi_min, rssi_avg, rssi_max = v + if stats_d: + addstr(window, 0, 24, 'Freq [ANT] pkt/s RSSI [dBm] SNR [dB]') + for y, ((freq, ant_id), v) in enumerate(sorted(stats_d.items()), 1): + pkt_s, rssi_min, rssi_avg, rssi_max, snr_min, snr_avg, snr_max = v if y < ymax: - active_tx = '*' if (int(k, 16) >> 8) == tx_ant else ' ' - addstr(window, y, 24, '%s%04x: %4d %3d < %3d < %3d' % (active_tx, int(k, 16), pkt_s, rssi_min, rssi_avg, rssi_max)) + active_tx = '*' if (ant_id >> 8) == tx_ant else ' ' + addstr(window, y, 24, '%04d %s%04x %4d %3d < %3d < %3d %3d < %3d < %3d' % \ + (freq, active_tx, ant_id, pkt_s, + rssi_min, rssi_avg, rssi_max, + snr_min, snr_avg, snr_max)) else: addstr(window, 0, 25, '[No data]', curses.A_REVERSE) @@ -139,9 +142,10 @@ class AntennaStat(LineReceiver): if latency_d: addstr(window, 0, 25, '[ANT] pkt/s Injection [us]') for y, (k, v) in enumerate(sorted(latency_d.items()), 1): + k = int(k) # json doesn't support int keys injected, dropped, lat_min, lat_avg, lat_max = v if y < ymax: - addstr(window, y, 25, '%04x: %4d %4d < %4d < %4d' % (int(k, 16), injected, lat_min, lat_avg, lat_max)) + addstr(window, y, 25, '%04x: %4d %4d < %4d < %4d' % (k, injected, lat_min, lat_avg, lat_max)) else: addstr(window, 0, 25, '[No data]', curses.A_REVERSE) diff --git a/wfb_ng/common.py b/wfb_ng/common.py index f1e66b3..eb151a1 100644 --- a/wfb_ng/common.py +++ b/wfb_ng/common.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018-2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify diff --git a/wfb_ng/config_parser.py b/wfb_ng/config_parser.py index 67baf9b..a5772cd 100644 --- a/wfb_ng/config_parser.py +++ b/wfb_ng/config_parser.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018 - 2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify diff --git a/wfb_ng/latency_test.py b/wfb_ng/latency_test.py index 2a9b48a..c2d9d2a 100755 --- a/wfb_ng/latency_test.py +++ b/wfb_ng/latency_test.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018-2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify diff --git a/wfb_ng/mavlink_protocol.py b/wfb_ng/mavlink_protocol.py index e227552..c240ed0 100644 --- a/wfb_ng/mavlink_protocol.py +++ b/wfb_ng/mavlink_protocol.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018 - 2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify diff --git a/wfb_ng/proxy.py b/wfb_ng/proxy.py index a00d06e..a2de1a1 100644 --- a/wfb_ng/proxy.py +++ b/wfb_ng/proxy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018-2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify diff --git a/wfb_ng/server.py b/wfb_ng/server.py index 65358b8..ee8d556 100644 --- a/wfb_ng/server.py +++ b/wfb_ng/server.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018-2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ # import sys -import json +import msgpack import os import re import hashlib @@ -29,7 +29,7 @@ from twisted.python import log, failure from twisted.python.logfile import LogFile from twisted.internet import reactor, defer, main as ti_main from twisted.internet.protocol import ProcessProtocol, Protocol, Factory -from twisted.protocols.basic import LineReceiver +from twisted.protocols.basic import LineReceiver, Int32StringReceiver from twisted.internet.serialport import SerialPort from . import _log_msg, ConsoleObserver, call_and_check_rc, ExecError @@ -57,18 +57,20 @@ class WFBFlags(object): fec_types = {1: 'VDM_RS'} -class StatisticsProtocol(Protocol): +class StatisticsProtocol(Int32StringReceiver): + MAX_LENGTH = 1024 * 1024 + def connectionMade(self): self.factory.ui_sessions.append(self) - def dataReceived(self, data): + def stringReceived(self, string): pass def connectionLost(self, reason): self.factory.ui_sessions.remove(self) def send_stats(self, data): - self.transport.write(json.dumps(data).encode('utf-8') + b'\n') + self.sendString(msgpack.packb(data)) class StatsAndSelectorFactory(Factory): @@ -97,11 +99,41 @@ class StatsAndSelectorFactory(Factory): def add_rssi_cb(self, rssi_cb): self.rssi_cb_l.append(rssi_cb) - def select_tx_antenna(self, ant_rssi): + def _stats_agg_by_freq(self, ant_stats): + stats_agg = {} + + for (freq, ant_id), (pkt_s, + rssi_min, rssi_avg, rssi_max, + snr_min, snr_avg, snr_max) in ant_stats.items(): + + if ant_id not in stats_agg: + stats_agg[ant_id] = (pkt_s, + rssi_min, rssi_avg * pkt_s, rssi_max, + snr_min, snr_avg * pkt_s, snr_max) + else: + tmp = stats_agg[ant_id] + stats_agg[ant_id] = (pkt_s + tmp[0], + min(rssi_min, tmp[1]), + rssi_avg * pkt_s + tmp[2], + max(rssi_max, tmp[3]), + min(snr_min, tmp[4]), + snr_avg * pkt_s + tmp[5], + max(snr_max, tmp[6])) + + return dict((ant_id, (pkt_s, + rssi_min, rssi_avg // pkt_s, rssi_max, + snr_min, snr_avg // pkt_s, snr_max)) \ + for ant_id, (pkt_s, + rssi_min, rssi_avg, rssi_max, + snr_min, snr_avg, snr_max) in stats_agg.items()) + + def select_tx_antenna(self, stats_agg): wlan_rssi = {} - for k, grp in groupby(sorted(((int(ant_id, 16) >> 8) & 0xff, rssi_avg) \ - for ant_id, (pkt_s, rssi_min, rssi_avg, rssi_max) in ant_rssi.items()), + for k, grp in groupby(sorted(((ant_id >> 8) & 0xff, rssi_avg) \ + for ant_id, (pkt_s, + rssi_min, rssi_avg, rssi_max, + snr_min, snr_avg, snr_max) in stats_agg.items()), lambda x: x[0]): # Select max average rssi [dBm] from all wlan's antennas wlan_rssi[k] = max(rssi for _, rssi in grp) @@ -125,42 +157,43 @@ class StatsAndSelectorFactory(Factory): self.tx_sel = max_rssi_ant - def update_rx_stats(self, rx_id, packet_stats, ant_rssi): - mav_rssi = [] + def update_rx_stats(self, rx_id, packet_stats, ant_stats): + stats_agg = self._stats_agg_by_freq(ant_stats) + card_rssi_l = list(rssi_avg + for pkt_s, + rssi_min, rssi_avg, rssi_max, + snr_min, snr_avg, snr_max + in stats_agg.values()) - for i, (k, v) in enumerate(sorted(ant_rssi.items())): - pkt_s, rssi_min, rssi_avg, rssi_max = v - mav_rssi.append(rssi_avg) - - rssi = (max(mav_rssi) if mav_rssi else -128) % 256 - - if ant_rssi and self.ant_sel_cb_list: - self.select_tx_antenna(ant_rssi) + if stats_agg and self.ant_sel_cb_list: + self.select_tx_antenna(stats_agg) if self.rssi_cb_l: _idx = 0 if settings.common.mavlink_err_rate else 1 flags = 0 - if not mav_rssi: + if not card_rssi_l: flags |= WFBFlags.LINK_LOST + elif packet_stats['dec_err'][0] + packet_stats['bad'][0] > 0: flags |= WFBFlags.LINK_JAMMED rx_errors = min(packet_stats['dec_err'][_idx] + packet_stats['bad'][_idx] + packet_stats['lost'][_idx], 65535) rx_fec = min(packet_stats['fec_rec'][_idx], 65535) + mav_rssi = (max(card_rssi_l) if card_rssi_l else -128) % 256 for rssi_cb in self.rssi_cb_l: try: - rssi_cb(rx_id, rssi, rx_errors, rx_fec, flags) + rssi_cb(rx_id, mav_rssi, rx_errors, rx_fec, flags) except Exception: log.err() if settings.common.debug: - log.msg('%s rssi %s tx#%d %s %s' % (rx_id, max(mav_rssi) if mav_rssi else 'N/A', self.tx_sel, packet_stats, ant_rssi)) + log.msg('%s rssi %s tx#%d %s %s' % (rx_id, max(card_rssi_l) if card_rssi_l else 'N/A', self.tx_sel, packet_stats, ant_stats)) # Send stats to CLI sessions for s in self.ui_sessions: - s.send_stats(dict(type='rx', id=rx_id, tx_ant=self.tx_sel, packets=packet_stats, rssi=ant_rssi)) + s.send_stats(dict(type='rx', id=rx_id, tx_ant=self.tx_sel, packets=packet_stats, rx_ant_stats=ant_stats)) def update_tx_stats(self, tx_id, packet_stats, ant_latency): if settings.common.debug: @@ -197,9 +230,9 @@ class RXAntennaProtocol(LineReceiver): cmd = cols[1] if cmd == 'RX_ANT': - if len(cols) != 4: + if len(cols) != 5: raise BadTelemetry() - self.ant[cols[2]] = tuple(int(i) for i in cols[3].split(':')) + self.ant[(int(cols[2]), int(cols[3], 16))] = tuple(int(i) for i in cols[4].split(':')) elif cmd == 'PKT': if len(cols) != 3: @@ -280,7 +313,7 @@ class TXAntennaProtocol(LineReceiver): elif cmd == 'TX_ANT': if len(cols) != 4: raise BadTelemetry() - self.ant[cols[2]] = tuple(int(i) for i in cols[3].split(':')) + self.ant[int(cols[2], 16)] = tuple(int(i) for i in cols[3].split(':')) elif cmd == 'PKT': if len(cols) != 3: diff --git a/wfb_ng/tuntap.py b/wfb_ng/tuntap.py index 5c8245a..29f056b 100644 --- a/wfb_ng/tuntap.py +++ b/wfb_ng/tuntap.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2018-2022 Vasily Evseenko +# Copyright (C) 2018-2024 Vasily Evseenko # # This program is free software; you can redistribute it and/or modify