mirror of
https://github.com/svpcom/wfb-ng.git
synced 2025-02-20 15:33:48 -04:00
Add SNR and frequency reporting to RX. Code refactoring.
This commit is contained in:
parent
b10a65f77a
commit
df43c1a270
@ -1,6 +1,6 @@
|
||||
/* -*- c -*-
|
||||
*/
|
||||
// Copyright (C) 2017 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
// Copyright (C) 2017 - 2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
50
src/rx.cpp
50
src/rx.cpp
@ -1,6 +1,6 @@
|
||||
// -*- C++ -*-
|
||||
//
|
||||
// Copyright (C) 2017 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
// Copyright (C) 2017 - 2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
/*
|
||||
* 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)));
|
||||
}
|
||||
|
50
src/rx.hpp
50
src/rx.hpp
@ -1,6 +1,6 @@
|
||||
// -*- C++ -*-
|
||||
//
|
||||
// Copyright (C) 2017 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
// Copyright (C) 2017 - 2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
/*
|
||||
* 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<uint64_t, rxAntennaItem> 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<rxAntennaKey>
|
||||
{
|
||||
std::size_t operator()(const rxAntennaKey& k) const noexcept
|
||||
{
|
||||
std::size_t h1 = std::hash<uint16_t>{}(k.freq);
|
||||
std::size_t h2 = std::hash<uint64_t>{}(k.antenna_id);
|
||||
return h1 ^ (h2 << 1); // combine hashes
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::unordered_map<rxAntennaKey, rxAntennaItem> 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;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// -*- C++ -*-
|
||||
//
|
||||
// Copyright (C) 2017 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
// Copyright (C) 2017 - 2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,6 +1,6 @@
|
||||
// -*- C++ -*-
|
||||
//
|
||||
// Copyright (C) 2017 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
// Copyright (C) 2017 - 2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2017 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
// Copyright (C) 2017 - 2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright (C) 2017 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
// Copyright (C) 2017 - 2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
/*
|
||||
* 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
|
||||
|
@ -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 <svpcom@p2ptech.org>
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018-2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# 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)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018-2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018-2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018 - 2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018-2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018-2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# 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:
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2018-2022 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
# Copyright (C) 2018-2024 Vasily Evseenko <svpcom@p2ptech.org>
|
||||
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
|
Loading…
Reference in New Issue
Block a user